giada-0.14.5/000077500000000000000000000000001322662744500126615ustar00rootroot00000000000000giada-0.14.5/.travis/000077500000000000000000000000001322662744500142475ustar00rootroot00000000000000giada-0.14.5/.travis/after_success.sh000077500000000000000000000003721322662744500174410ustar00rootroot00000000000000#!/usr/bin/env bash mkdir build if [[ $TRAVIS_OS_NAME == 'osx' ]]; then cp giada_osx ./build upx --best ./build/giada_osx elif [[ $TRAVIS_OS_NAME == 'linux' ]]; then : # null command - nothing to do # TODO # cp giada_lin ./build figiada-0.14.5/.travis/before_install.sh000077500000000000000000000003271322662744500176000ustar00rootroot00000000000000#!/usr/bin/env bash if [[ $TRAVIS_OS_NAME == 'osx' ]]; then echo "" elif [[ $TRAVIS_OS_NAME == 'linux' ]]; then sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y # for gcc 6 sudo apt-get update -qq figiada-0.14.5/.travis/before_script.sh000077500000000000000000000003401322662744500174310ustar00rootroot00000000000000#!/usr/bin/env bash if [[ $TRAVIS_OS_NAME == 'osx' ]]; then ./autogen.sh ./configure --target=osx --enable-vst elif [[ $TRAVIS_OS_NAME == 'linux' ]]; then ./autogen.sh ./configure --target=linux #--enable-vst figiada-0.14.5/.travis/install.sh000077500000000000000000000057261322662744500162660ustar00rootroot00000000000000#!/usr/bin/env bash if [[ $TRAVIS_OS_NAME == 'osx' ]]; then brew update brew install rtmidi brew install jansson brew install libsamplerate brew install fltk brew install libsndfile brew install upx #ls Remove dynamic libraries to force static linking. rm -rf /usr/local/lib/librtmidi.dylib rm -rf /usr/local/lib/librtmidi.4.dylib rm -rf /usr/local/lib/libjansson.dylib rm -rf /usr/local/lib/libjansson.4.dylib rm -rf /usr/local/lib/libsamplerate.dylib rm -rf /usr/local/lib/libsamplerate.0.dylib rm -rf /usr/local/lib/libfltk.1.3.dylib rm -rf /usr/local/lib/libfltk.dylib rm -rf /usr/local/lib/libfltk_forms.1.3.dylib rm -rf /usr/local/lib/libfltk_forms.dylib rm -rf /usr/local/lib/libfltk_forms.dylib rm -rf /usr/local/lib/libfltk_gl.1.3.dylib rm -rf /usr/local/lib/libfltk_gl.dylib rm -rf /usr/local/lib/libfltk_images.1.3.dylib rm -rf /usr/local/lib/libfltk_images.dylib rm -rf /usr/local/lib/libsndfile.1.dylib rm -rf /usr/local/lib/libsndfile.dylib rm -rf /usr/local/lib/libFLAC++.6.dylib rm -rf /usr/local/lib/libFLAC++.dylib rm -rf /usr/local/lib/libFLAC.8.dylib rm -rf /usr/local/lib/libFLAC.dylib rm -rf /usr/local/lib/libogg.0.dylib rm -rf /usr/local/lib/libogg.dylib rm -rf /usr/local/lib/libvorbis.0.dylib rm -rf /usr/local/lib/libvorbis.dylib rm -rf /usr/local/lib/libvorbisenc.2.dylib rm -rf /usr/local/lib/libvorbisenc.dylib # TODO - what about midimaps? elif [[ $TRAVIS_OS_NAME == 'linux' ]]; then sudo apt-get install -y gcc-6 g++-6 libsndfile1-dev libsamplerate0-dev \ libfltk1.3-dev libasound2-dev libxpm-dev libpulse-dev libjack-dev \ libxrandr-dev libx11-dev libxinerama-dev libxcursor-dev # Symlink gcc in order to use the latest version sudo ln -f -s /usr/bin/g++-6 /usr/bin/g++ # Download and build latest version of RtMidi wget https://github.com/thestk/rtmidi/archive/master.zip unzip master.zip cd rtmidi-master && ./autogen.sh && ./configure --with-jack --with-alsa && make && sudo make install || true cd .. #wget http://www.music.mcgill.ca/~gary/rtmidi/release/rtmidi-2.1.1.tar.gz #tar -xvf rtmidi-2.1.1.tar.gz #cd rtmidi-2.1.1 && ./configure --with-jack --with-alsa && make && sudo make install || true #cd .. # Download and install latest version of Jansson # TODO - no longer needed! Use apt instead wget http://www.digip.org/jansson/releases/jansson-2.7.tar.gz tar -xvf jansson-2.7.tar.gz cd jansson-2.7 && ./configure && make && sudo make install || true sudo ldconfig cd .. # Download midimaps package for testing purposes wget https://github.com/monocasual/giada-midimaps/archive/master.zip -O giada-midimaps-master.zip unzip giada-midimaps-master.zip mkdir -p $HOME/.giada/midimaps cp giada-midimaps-master/midimaps/* $HOME/.giada/midimaps # Download vst plugin for testing purposes #- wget http://www.discodsp.com/download/?id=18 -O bliss-linux.zip #- unzip bliss-linux.zip -d bliss-linux #- cp bliss-linux/64-bit/Bliss64Demo.so . figiada-0.14.5/.travis/script.sh000077500000000000000000000000721322662744500161110ustar00rootroot00000000000000#!/usr/bin/env bash make -j 2 make rename make check -j 2giada-0.14.5/COPYING000066400000000000000000001045131322662744500137200ustar00rootroot00000000000000 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 . giada-0.14.5/ChangeLog000066400000000000000000001134221322662744500144360ustar00rootroot00000000000000 -------------------------------------------------------------------------------- Giada - Your Hardcore Loopmachine. Developed by Monocasual Laboratories www.giadamusic.com CHANGELOG -------------------------------------------------------------------------------- 0.14.5 --- 2018 . 01 . 15 - OS X builds on Travis CI - AppImage executable for Linux - Support for multiple plug-in directories - New directory browser for adding plug-in directories - Update plug-in's parameters on program change in plug-in's window - Improved MIDI action management in Piano Roll - Simplified conditional rules in Makefile.am - Fix crash on MIDI learn for plug-in parameters - Fix crash in MIDI input window if MIDI in params are 0 - Fix unwanted new action when dragging piano items in Piano Roll - Fix crash while recording on existing project (GitHub #161) - Fix crash on startup in Windows build 0.14.4 --- 2017 . 10 . 28 - Renameable channels - Portable VST path - [Sample Editor] Sample shift tool - [Linux/Mac] Don't skip '/' path when navigating to upper folders - Ability to process more than one plug-in instrument at once - Beautify Configuration Window - Bring VST window to front when opening UI - Save 'arm' status to patch/project file - Revamped Beats and Bpm input windows - Simplified audio samples' storage in project folders - Update JUCE to version 5.1.2 - UI-less plug-in window refinements - Update UI-less plug-in window on MIDI parameter's change - Strip .gptc/.gprj extention from patch name - [Sample Editor] Fix non-working 'cut' operation - Fix missed MIDI events with more than 1 plug-in in the stack - Fix File Browser path widget drawn incorrectly in OS X - Fix missing MIDI learn for 'Arm channel' and 'Kill channel' 0.14.3 --- 2017 . 09 . 18 - [Sample Editor] New "reverse selection" function - [Sample Editor] New "normalize hard" function - [Sample Editor] New "copy to channel" function - [Sample Editor] New "copy & paste" function - [Sample Editor] Double click on waveform selects all - [Sample Editor] Fix garbled characters in window's title - [Sample Editor] Fix wrong result on "set pitch to song/bar" - Resizable channels - Remove calls to malloc/free in Mixer (use new/delete instead) - Improved UI management of VST plugins - Fix infinite loop for one shot retrig samples with quantizer > 0 - Fix wrong geChannel count while saving a patch - Fix missing greyed-out options in Sample Channel's menu when loading a wrong sample - Fix crash while audio recording with BPM set below the default 120 - Print correct octave numbers in Piano Roll 0.14.2 --- 2017 . 08 . 14 - [Sample Editor] Audible preview (with optional loop mode) - [Sample Editor] Frame-precise editing - [Sample Editor] Show sample's information - [Sample Editor] Improved fade out algorithm - [Sample Editor] Process both left and right channel's data while drawing - Better Wave objects handling - Improved channels' memory management - Improved empty columns cleanup algorithm - Update Catch version - Update JUCE version (5.1.1) - Update Jansson version (2.10) - Fix missing tempo update on reset to init state - Fix wrong memory allocation for UI-less plugins 0.14.1 --- 2017 . 07 . 16 - Update JUCE library to 5.0.2 - Show play head in Sample Editor - Refactor pop up menu in Sample Editor - Many small fixes and optimizations in waveform drawing routine - Makefile cleanup - Fix crash while recording with beats/bars greater than 4/1 (GitHub #134) 0.14.0 --- 2017 . 05 . 29 - Sample Editor reorganized and refactored - Removed support for old ini-based patch files - Improved and simplified pan algorithm - Ability to toggle input monitoring while recording audio - Lots of code refactoring - Convert all .h headers to C++ headers - Update Libsndfile to version 1.0.28 - Fix crash when recording audio - Fix wrong file path when exporting samples - Fix a bug that prevented begin/end handles to work in Sample Editor - Fix Sample Editor's grid value not being stored properly on close 0.13.4 --- 2017 . 04 . 23 - Removed support for old ini-based MIDImap files - Initial support for channel-based MIDI filtering - New Orphaned MIDI events in Piano Roll editor - Improve action filtering in Piano Roll editor - Lots of code refactoring - New test suite for Action Recorder - Fix obscure bug when overdubbing actions and a null loop occurs - Fix "clear all actions" menu refresh when removing items on Piano Roll 0.13.3 --- 2017 . 03 . 25 - Strip VST folder from Git repository - Fix 'Close' button's position inside MIDI input window - Update RtMidi to version 2.1.1 - Improve 'free channel' function (GitHub #105) - New 'Clock' structure for timing operations - New Jack implementation with BPM sync and Rewind (GitHub #89) - Fix missing tracker reset on 'free channel' function (GitHub #99) 0.13.2 --- 2017 . 01 . 14 - MIDI learn for plugins parameters - Toggle hidden files in File Browser - Fix broken compilation when build without VST support - Make sure PluginChooser window has a sane size - Decouple Recorder from any global variable - Better source code organization - Make plugin creation more robust - More source code reorganization - Fix crash on clicking scrollbar arrows (GitHub #53) - Fix crash when doubling/dividing length while recording (GitHub #110) 0.13.1 --- 2016 . 11 . 16 - Input MIDI to MIDI channels/plugins - Refinements to show/hide 'R' button's dynamics - Increase piano roll items' height - Set input volume to max by default - Start live-recorded sample channels right away - Avoid potential crashes when loading samples on running channels - Generate metronome during output post-processing - Better widgets' layout in Sample Editor - Lots of source code optimizations and cleanups - Fix inverted 'R' button's status (GitHub #94) - Better handling of 'R' button's status when the sequencer is off (GitHub #95) - Fix non-playing samples if live-recorded and 'R' button is on (GitHub #93) - Reset button statuses once channels have been freed (GitHub #100) - Fix missing ASIO and WASAPI APIs on Windows (GitHub #96) - Missing RtMidi libs on Linux (GitHub #102) - Fix fade-in/fade-out editing not triggering alert on save (GitHub #101) 0.13.0 --- 2016 . 09 . 20 - Deep file browser refactoring - Save browser's scroll position and last item selected on opening - Load patches/projects/samples on double click - 64 bit builds for Windows - Prevent deprecated patch from crashing if a plugin is not found in the stack - Force logger to flush to file on Windows - Add more default values for windows' dimensions and positions - Avoid crashes on Configuration panel if no midimaps were selected - Fix missing keyRelease actions in action editor - Update JUCE to version 4.2.3 - Don't include JUCE on tests without VST support (GitHub #75) - Fix compilation errors on GCC 6 (GitHub #82) - Fix includes on OSX (GitHub #92) - Fix wrong channel's actions count that prevented "R" button to be toggled properly - Fixed a bug that prevented actions on frame 0 to being properly reproduced - Make Recorder a proper class - Better naming convention for ActionEditor's children classes - Source code reorganization 0.12.2 --- 2016 . 06 . 02 - Update RtAudio to version 4.1.2 - Add WASAPI support on Windows - Sortable plugins list - Simplify custom RtAudio build and inclusion on Linux - Fix crashes on startup on OS X El Capitan - Store position and size of Available Plugins window - Untangle Channels' code from global variables 0.12.1 --- 2016 . 05 . 06 - Show percentage progress for plugin scan - Notify if plugins are missing - Notify if unknown plugins are present - Fix potential segfault on MasterIn/MasterOut plugins loading - Proper cleanup of JUCE resources - Internal refactoring on PluginHost's global variables 0.12.0 --- 2016 . 03 . 07 - Port to JUCE Framework for audio plugin management - Increase global font size - Minor UI fixes and cleanups - Add ability to run tests outside Travis CI - Switch to C++11 - 64 bit binaries for OS X - Use new constant for global font size 0.11.2 --- 2016 . 01 . 16 - New JSON-based midimap files - Add new channel by right-clicking anywhere on a column - Show warning if patch is using the deprecated file format - Do not force 32 bit compilation on OS X - Fix warnings and errors on GCC 5.3 - Fix a bug that prevented MIDI Jack from being selected on Linux 0.11.1 --- 2015 . 12 . 22 - Ability to clone channels - New JSON-based configuration file - Port all vectors from old gVector to std::vector - Deactivate all other MIDI fields when changing MIDI system in Config window - Minor optimizations in configuration panel, Audio tab - Assume 'none' as default sound system - Include Catch header file in source package - Update Travis CI environment to Ubuntu Trusty - Fix missing sanitization after reading configuration file - Fix garbage text in device info window - Fix wrong config value if no midimaps are available - Fix garbage text while printing device and port names 0.11.0 --- 2015 . 12 . 02 - New JSON-based patch system - Properly store column width in patch - Port all const char* strings to std::string in patch/project glue layer - Switch to SemVer-like internal versioning system - More source code reorganization - Fix potential memory leaks in Mixer - Fix missing static link of RtMidi on Linux - Unable to store pitch values > 2.0 (fixed) - Missing assigned key after opening patch (fixed) 0.10.2 --- 2015 . 10 . 21 - Setup Travis CI automated builds - Add base framework for unit testing (with Catch) - Improve behavior of Loop Once family when the sequencer is halted - Fix empty sample path in sample channels when saving a Project - Fix disabled "edit actions" for sample channels - Fix missing pthreadGC2.dll in Windows build 0.10.1 --- 2015 . 08 . 26 - Massive source folders refactoring - Improved usability of "play" buttons for channels - Remove support for patches created with Giada < 0.6.x - Fix check for configured soundsystem (would break compilation on g++5) - Small fixes and cleanup in Makefile.am 0.10.0 --- 2015 . 07 . 05 - MIDI lightning output - Other minor fixes 0.9.6 --- 2015 . 05 . 11 - Keyboard binding for MIDI channels - Support for multiple files in drag-n-drop operations - Different color for wait/end statuses - Small improvements to Keyboard grabber widget - Fix random crashes with Jack enabled - Fix weird behavior with multiple drag and drop - Code refactoring 0.9.5 --- 2015 . 03 . 28 - Better column resize algorithm - New patch loading system with permanent MIDI mapping - Ability to clear assigned keys (keyboard mode) - Improved zoom icons in editors - Fix deprecation warning in configure.ac 0.9.4 --- 2015 . 02 . 24 - Drag-n-drop now works also in existing channels - Store 'resize recordings' flag in giada.conf - Better management of duplicate samples - Add more VST debug information - Minor fixes and tweaks 0.9.3 --- 2015 . 02 . 01 - New GUI improvement: responsive and resizable columns - Upgrade to FLTK 1.3.3 - More robust column handling mechanism - Support for MIDI devices without note-off message (@blablack) - Fix segfaults when saving a patch with missing plugins - Fix many minor graphical bugs - Fix wrong vector assignment in MIDI send event - Fix reloaded patches with no right tempo/beats displayed - Fix random odd frames when adding/moving events in Piano Roll - Minor internal cleanup 0.9.2 --- 2014 . 11 . 29 - New grid layout in Sample Editor - Load samples via drag n drop - Add new utility functions: gTrim and gStripFileUrl - Fix "normalize" button position in Sample Editor - Minor waveform drawing optimizations - Add missing files for RtAudio-mod compilation - All one-shot mode, if fired manually, get the first frame truncated (fixed) 0.9.1 --- 2014 . 09 . 24 - Bring back custom version of rtAudio in source package - Automatically turn up volume when adding new channel - Updated 'misc' tab in configuration panel - Fix startup crash on OS X - Fix missing jack headers 0.9.0 --- 2014 . 08 . 18 - New full-screen GUI - Multi-column support - Advanced logging system - Upgrade to RtAudio 4.1.1 and RtMidi 2.1.0 - Removed embedded RtAudio (thanks to Arty) - Fix wrong processing of VST MIDI events on 64 bit version - Fix stretched buttons when resizing sample editor window - "Clear all samples" destroys channels (fixed) - "Free channel" messes up loop / mute buttons (fixes) - Fix potential recordings with odd frames 0.8.4 --- 2014 . 03 . 27 - New mode 'Loop Bar Once' - Several small improvements and cleanups to internal utils functions - Fixed missing title in several subwindows - (win) Fix runtime error when loading a new project - Fix chan reset when clicking on waveform - Properly close subwindows after a channel has been deleted - Fix 'reload' button not working for samples with updated names 0.8.3 --- 2014 . 02 . 14 - Experimental MIDI timing output with MTC and MIDI clock - Expose Sequencer x2 and /2 via MIDI - New pitch operators x2 and /2 - Internal xfade process restored - "set key..." becomes "setup keyboard input" for sample channels - MIDI events are now saved as unsigned int in patch - Same expression on both sides of '|' in recorder.cpp (fixed) - Muted channels leak some glitches on 'kill' event (fixed) - Piano roll can't be edited anymore if beats == 32 (fixed) - Noise when adding new MIDI channel (fixed) - Boost and Normalize not working (fixed) - Multiple copies of every file used by the patch (fixed) - Samples with -1, -2, ... -n suffix are not included in patch (fixed) - Segfaults when quantizing samples (fixed) 0.8.2 --- 2014 . 01 . 13 - Pitch control exposed via MIDI - New tools in Sample Editor (linear fade in/out, smooth edges) - Implemented vstEvent->deltaFrames, gaining more precision with vst MIDI events - Add Fl::lock/Fl::unlock dynamics to glue_ calls where needed - Avoid pitch sliding when changing pitch of a sample in status OFF - Update copyright info in source files - Internal fade in and fade out restored - Add 'Giada' keyword to desktop file - Fix annoying glitches when playing very short samples - Fix random crashes when controlling giada via MIDI - Fix missing MIDI mapping for read-actions button 0.8.1 --- 2013 . 12 . 09 - New, high-quality pitch control based on libsamplerate - New set of functions 'spread sample to beat/song' [known issues] - Internal crossfades have been temporarily disabled. Some clicks may occur 0.8.0 --- 2013 . 11 . 03 - Initial MIDI input support - Fix freeze when recording audio inputs on a second channel - Fix 'R' button to show up even if the channel has no actions - Fix weird drawings of keypress actions in action editor - Free channel: delete 'R' button as well - Shift+key does not kill loop mode channels in a wait status - Fix issue with 'R' button and newly added actions - Remove "left"/"right" labels from main buttons 0.7.3 --- 2013 . 09 . 14 - Experimental 64 bit compilation (Linux only) - Massive internal cleanup of channel/gui channel layers - Set default mode to full volume on sample load - Set default mode to oneshot basic - Faster drawings in piano roll - Visual aids in piano roll - Scroll to pointer in piano roll - Several minor improvements in piano roll's usability - Revised VST Carbon window popup system - Minor improvements in startInputRec/stopInputRec procedure - Fix compile error using local type Plugin* in Channel's constructor - Fix segfault in OSX when working with VST windows 0.7.2 --- 2013 . 07 . 27 - Initial MIDI output support - Mute now affects channels with VSTi signals - Lots of deb package improvements - Complete rewrite of VST GUI part on OS X - Don't send MIDI mute on sample channels - Send MIDI mute for MIDI channels in play mode - Fix wrong looping due to VST processing in mixer::masterPlay - Fix jack crashes when using Giada with ALSA - Fix VST random crashes on OSX, bus error - Fix input device set to -1 after a system change 0.7.1 --- 2013 . 06 . 27 - Initial Jack Transport support - Send global note off when sequencer is being stopped - Send note off when deleting notes in Piano Roll - Store position and size of Piano Roll in conf file - Avoid overlap MIDI notes in Piano Roll - MIDI channel refactoring - MIDI channels now behave like loop-mode ones - Fix graphical bugs in Action Editor, sample mode - Fix refresh issue in Piano Roll when deleting items - Lots of invisible cleanups and improvements 0.7.0 --- 2013 . 06 . 05 - Initial MIDI output implementation - Initial VSTi (instrument) support - New piano roll widget in action editor - New chan mode: MIDI vs SAMPLE - Fix E-MU Tracker Pre not correctly listed in audio in/output 0.6.4 --- 2013 . 05 . 07 - Resizable plugin parameter window - New and standard package name format -. - Implement RtAudio::getCompiledApi() to fetch compiled APIs - Implement audioMasterGetSampleRate, audioMasterGetLanguage VST opcodes - Add drop-down menu for buffer size values in config panel - Enhance project portability between OSes - Lots of fixes and improvements for VST strings and parameters - Avoid segfault when loading recs from a patch with files not found - Always remember selected program when shifting up/down plugins - Fix wrong size of single_press displayed in action editor - Fix volume actions resized with value set to zero - Fix volume envelope always over the cover area - Fix src package extracts to current dir - Fix segfault in loadpatch process if plugin GUIs are open - Fix segfault when closing patch with plugins in BAD status 0.6.3 --- 2013 . 04 . 23 - New 'solo' button - Portable project system - New 'Single Endless' channel mode - GUI enhancements for channels in WAIT or ENDING status - Minor fixes & cleanups 0.6.2 --- 2013 . 04 . 05 - New volume envelope widget - Zoom with mouse wheel in the action editor - Graphical enhancements & speedups for the action editor - Loop-repeat doesn't stop when put in ending mode (fixed) - Fix draw errors when zooming too much the action editor - Set silence in wave editor messes up the waveform (fixed) - Wrong slashes in file path when saving a patch in Windows (fixed) - Many, many code improvements and bugs fixed 0.6.1 --- 2013 . 03 . 21 - Unlimited number of channels - Deep internal refactoring, mixer/GUI layers - Fix random crashes on exit - Fix crashes when closing Giada with VST windows opened - Always free Master In plugin stack on exit - Lots of other minor bugs fixed and small enhancements 0.6.0 --- 2013 . 03 . 02 - New, full-screen, redesigned sample editor - Zoom with mouse wheel in sample editor - Use kernelAudio::defaultIn/defaultOut for DEFAULT_SOUNDDEV_OUT - Volume knob in main window now updates the editor - Sound system issues in OS X (fixed) - Output device info dialog refers to wrong device (fixed) 0.5.8 --- 2013 . 02 . 07 - Internal samplerate conversion (with libsamplerate) - Bring channels automatically to full volume on sample load - Ability to set the audio device frequency - New "internal mute" feature - fix for deprecated VST opcode 14 - fix deb package issues on Ubuntu 12.10 / KXStudio 0.5.7 --- 2013 . 01 . 21 - visual grid + snapping in the action editor - implement more audioMasterCanDo's in pluginHost - limit zoom in actionEditor - revise zoom behavior in actionEditor, now more comfortable - fix forward declaration & inclusion of several headers - implemented VST opcode 32 - implemented VST opcode 33 - implemented VST opcode 34 - update website link in tar files - update copyright info for 2013 0.5.6 --- 2013 . 01 . 03 - New overdub mode for live recording - Support for VST programs, aka presets - Lots of VST opcodes implemented - Fix crash when removing a plugin from the stack - Fix pops when going to beat 0 - Fix compilation issues without --enable-vst - Many invisible optimizations and small bugs fixed 0.5.5 --- 2012 . 12 . 15 - "Hear what you're playing" feature - Fx processing on the input side - Ability to add different action types (Action Editor) - Desktop integration on Linux (via deb package) - Upgrade to FLTK 1.3.2 - Remove "the action might stop the channel" when loading new samples - Fix wrong positioning of zoom tools (Action Editor) - Fix unwanted interactions on the grey area (Action Editor) - Fix wrong memory alloc during the VST processing - VST don't show up in OS X (fixed) - Minor internal refactoring + bugfixing 0.5.4 --- 2012 . 11 . 24 - VST GUI support - Better subwindow management - Implemented many other VST opcodes - Missing plugins are now shown in the list with a 'dead' state - Refresh action editor when changing beats (via beat operator or beat window) - Graphical improvements in the action editor - Resizable action editor doesn't work well (fixed) - Fix auto fadeout for SINGLE_PRESS channels - Fix compilation without --enable-vst - Fix for a wrong prototype definition of the VST hostCallback 0.5.3 --- 2012 . 10 . 26 - Live beat manipulators (x2)(/2) - New sub-windows management, faster and more comfortable - New optional hard limiter on the output side - Action Editor window recalls x,y,w,h zoom and position - Usability improvements while handling an action (action editor) - Refresh actionEditor window when switching channel mode or delete actions - Unable to delete a killchan action (action editor) (fixed) - Don't show ACTION_KILLCHAN in a singlepress channel (action editor) - Libsndfile no longer statically linked in Linux - Fixed a typo in config: "when the sequeCer is halted" - redefinition of DEFAULT_PITCH in wingdi.h (windows) (fixed) - Upgrade to FLTK 1.3.0 - Other internal optimizations - Other small bugs fixed 0.5.2 --- 2012 . 10 . 05 - Add ability to handle actions for loop-mode channels - Add ability to record live mute actions for loop-mode channels - Lots of live action recording improvements - Enhanced usability for the action editor - More verbose output if kernel audio fails to start - Several internal optimizations 0.5.1 --- 2012 . 09 . 13 - First implementation of the Action Editor - Added compatibility with Ubuntu >= 10.04 0.5.0 --- 2012 . 07 . 23 - New custom project folder (.gprj) - Sample names are now made unique - Fixed unwanted time stretching while exporting a mono sample - Lots of minor internal improvements 0.4.12 --- 2012 . 07 . 01 - VST parameters and stacks are now stored in patch file - Upgrade to RtAudio 0.4.11 - PulseAudio support in Linux (thanks to RtAudio 0.4.11) - Revised .deb package - Enhanced "normalize" function in wave editor - Several memory issues fixed - Internal enhancements and minor bugs fixed 0.4.11 --- 2012 . 06 . 10 - VST stack for each channel - Custom paths for plugins, samples and patches - Crash in config panel if device is busy (fixed) - Graphical bug in the input meter (fixed) - ParamLabel added in the VST parameter list 0.4.10 --- 2012 . 05 . 30 - Ability to shift up an down VST plugins - Enhanced patch/conf architecture - Ability to edit a sample while playing - Mutex controls in VST processing - Lots of security issues fixed while changing pitch dinamically - Enhanced sub-window system - Several minor bugs fixed 0.4.9 --- 2012 . 05 . 12 - No more mandatory inputs - Pitch value properly stored inside the patch - Several small VST host improvements - Enhanced window management - Ability to browse files while playing with main GUI (non-modal browser) - Improved error checking in KernelAudio - Wrong style for lower scrollbar in Browser (fixed) - Fixed compilation on 64 bit systems (thanks to Speps@Archlinux) - Samplerate no longer hardcoded, auto-detected with JACK - Minor internal improvements and bugfixing 0.4.8 --- 2012 . 04 . 21 - Initial VST support (experimental) - Pitch controller (experimental, no filtering) - OSX bundles are now correctly handled by the file browser - Fixed several memory leaks - Minor internal improvements 0.4.7 --- 2012 . 03 . 31 - Cut, trim & silence operations in sample editor - New "Reload sample" button added - Lots of optimizations in the waveform drawing routines - The sample is no longer editable while in play mode - Fixed potential startup crashes while using Giada with Jack Audio - Other minor fixes applied to the configuration panel - Fixed compilation on 64 bit systems (thanks to Speps@Archlinux) 0.4.6 --- 2012 . 03 . 11 - New device information panel - The device configuration now shows only active and available devices - Channel panel no longer pops up during a recording process - GUI beautifications and other minor graphical fixes - Program icon added in all subwindows - Action records no longer available during a take, and vice versa - Fixed a serious bug that swapped input and output devices - Fixed loop behavior in ending mode - Fixed clicks when stopping a muted channel in loop 0.4.5 --- 2012 . 02 . 25 - Complete GUI redesign - New "start/stop action recs" button - Lots of internal cleanups and micro refactorings - Small drawing glithes in Editor and status box (fixed) - An invalid patch puts Giada to init state (fixed) - Fixed button repeat on start/stop, action rec, input rec - Checks against takes with unique name - Message "this action may stop the channel" always shown (fixed) - Channel no longer freeable while a take is in progress 0.4.4 --- 2012 . 02 . 04 - New input/output channel selector - Rewind bypasses the quantizer if triggered via mouse (fixed) - Fixed library paths in configure and makefile (thanks to Yann C.) - Added AUTHORS and NEWS files to the source package (thanks to Yann C.) - More robust sample export procedure - Issues with mute buttons when opening a patch (fixed) - Several usability improvements - Minor code cleanups and optimizations 0.4.3 --- 2012 . 01 . 21 - New "save project" feature - Ability to export a single sample to disk - More feedback when removing/clearing actions and samples - Sequencer starts automatically when action-rec button is pressed - Alert if patch name is empty while saving it - Channels now store internally the name of the samples - Missing "--no devices found--" in input devices menu (fixed) - Alert added if there are no empty channels for recording - "Edit->Clear all actions" no longer works (fixed) - END button could be used as a channel trigger (fixed) - Recorders are available even if device status is wrong (fixed) - Missing sample rewind if channel is muted (fixed) - Quantizer doesn't work if framesize is odd (fixed) - Random segfault when closing Giada (fixed) - Lots of code cleanups - Other minor improvements and optimizations 0.4.2 --- 2012 . 01 . 09 - Live sampling from external input with meter and delay compensation - Check against uneven values and overflow in buffersize field - Wrong normalized values if volume level is 0.0 (fixed) - Boost dial goes crazy if normalized > 20.0 dB (fixed) - Boost dial goes crazy if normalized < 0.0 dB (fixed) - Unwanted noise click if a muted channel is being rewinded (fixed) - Mute doesn't work well for single-shot samples (fixed) - Wrong FLTK headers (fixed, thanks to Yann C.) - Moving chanStart/chanEnd swaps stereo image (fixed) - Reset to init state doesn't reset mute buttons (fixed) - Wrong chanStart value if > 0 (fixed) 0.4.1 --- 2011 . 12 . 07 - Complete mixer engine refactoring - Faster audio buffer allocation - Global beat system revisited - Autocrossfade between samples is now enabled by default - No more recorded actions on odd frames - Unintentional channel swapping fixed - Unable to list all sound systems and sound devs under OSX (fixed) - Missing graceful stop of audio streaming under OSX (fixed) 0.4.0 --- 2011 . 11 . 16 - Support for all major uncompressed file formats (with libsndfile) - Enhanced mono > stereo conversion - Fixed drawing issues for the start/stop labels inside the waveform - Enhanced backward compatibility with old patches - Support for compilation on OS X and Windows 0.3.6 --- 2011 . 11 . 02 - Initial Mac OS X release - (Windows) Ability to list and browse all active drives - Change some internal routines plus minor optimizations - Added -pedantic and -Werror flag to the compiler - Crash if clicking on mute in an empty channel (fixed) - Chan status changes if an empty channel is being muted (fixed) 0.3.5 --- 2011 . 10 . 22 - Pan controller added - New GNU-style source code packaging - Revamped .deb package - Program icon missing under Windows (fixed) - Crash if a sample in patch is missing from the filesystem (fixed) - Unable to rewind to beat 1 if quantizer is on and seq stopped (fixed) - Several minor glitches fixed 0.3.4 --- 2011 . 10 . 10 - Full source code released under GPL license - Autosmooth is now toggleable via setup - Faster loading process of patch files - Various internal cleanups and optimizations - Fixed incorrect reading of boost values from patch - Fixed a potential bug that prevented the config panel to appear - Fixed stereo swap bug - Minor graphical revisions 0.3.3 --- 2011 . 09 . 28 - New "normalize" function - More editing tools added inside the sample editor - Waveform beautifications - Fixed interaction bugs for boost and volume controls 0.3.2 --- 2011 . 09 . 19 - New "mute" button inside the main window - Waveform is now updated when the boost value changes - Zoomin/zoomout relative to the scrollbar position - Fixed garbage output if the volume was "-inf" (windows version) - Fixed several rendering issues for short waveforms 0.3.1 --- 2011 . 09 . 12 - Boost volume + fine volume control in sample editor - Start/End handles inside the editor are now draggable via mouse - Fixed scrollbar issues in sample editor - Start/end points are now always drawn in the foreground - Waveform no longer overflow if a value is greater than the window - (linux) giada.conf is saved inside the hidden folder /home/.giada - patch loading process is now faster and cleaner - Update to rtAudio 4.0.10 0.3.0 --- 2011 . 09 . 01 - New sample editor window - Ability to set start/end points within a sample - Update to rtAudio 4.0.9 - Fixed an string overflow inside a patch - Fixed a missing memory free if a sample is unreadable - Several internal updates and optimizations 0.2.7 --- 2011 . 07. 22 - New way to handle recorded channels as loops - Fixed retrig for backspace key (rewind) - Enhanced rewind with quantization support - Main and alert windows now appear centered on screen - Sanity check against old patches without metronome information - Rewind now affects loops in rec-reading mode 0.2.6 --- 2011 . 07 . 11 - Internal metronome - Fixed some glitches in config panel - Minor cleanups 0.2.5 --- 2011 . 06 . 20 - Configuration panel redesign - Several new control options - Progress feedback when loading patches - Internal optimizations - Updated docs 0.2.4 --- 2011 . 06 . 08 - New loop repeat mode - Ability to save patches anywhere in the filesystem - Sub-beat management - Sound meter has been revisited and improved - Several patch enhancements - Core audio optimizations 0.2.3 --- 2011 . 05 . 18 - ASIO support for Windows version - Enhanced security when reading values from a patch - Ability to disable the recordings when the sequencer is paused - Master volume and rec status are now saved inside the patch - Device selection fixed and improved - Sequencer flickering in Windows has been fixed - Feedback added if a sample from a patch is unreadable or corrupted - Minor internal optimizations 0.2.2 --- 2011 . 05 . 04 - New open-source patch system - A patch can now be loaded from any location of the filesystem - Enhanced file browser coords system - Lots of minor improvements to the sample loading/unloading procedure - (win) Init path of file browser now starts from %userProfile%/Desktop - Wrong handling of "/" chars fixed in config menu - Fixed potential hangs on quit - Fixed clicks when stopping sequencer/sample - Minor gui beautifications 0.2.1 --- 2011 . 04 . 26 - Windows version 0.2.0 --- 2011 . 04 . 19 - Full JACK and ALSA support with RtAudio - New list of sound devices in menu window - Enhanced shutdown procedure to prevent potential crashes - Some GUI glitches fixed - Fixed random locks when the screensaver is active 0.1.8 --- 2011 . 04 . 13 - new functions: free al samples/recordings, reset to init patch - main menu redesign - the file browser is now resizable - GUI feedback for samples in play mode - some fixes when unloading a sample 0.1.7 --- 2011 . 04 . 07 - Ability to remove only action recordings or mute recordings - Shift+key now stops the sample if the master play is deactivated - Frame 0 was always processed at the end of the sequencer - Minor internal improvements 0.1.6 --- 2011 . 03 . 29 - Autocrossfade to prevent clicks - Internal improvements and bugfixing 0.1.5 --- 2011 . 03 . 10 - decimal bpm adjustment - ability to shrink/expand actions when changing the global beats - improved GUI for beats and bpm controllers - improved routines for action management - actions are now updated when you change bpm 0.1.4 --- 2011 . 03 . 04 - ability to save recorded actions - status box now shows if a recorded chan is deactivated - recorder is reset correctly when you load a new patch - minor improvements 0.1.3 --- 2011 . 02 . 26 - action recorder (first implementation) - quantization procedure slightly optimized - minor graphical adjustments - expanded documentation 0.1.2 --- 2011 . 02 . 08 - master volume controller - improved sound meter with more accuracy - improved verifications when reading or writing a patch - beat counter is now always reset to 1 after a patch is loaded - made loading wave files more robust, plus memory optimizations - minor crashes fixed 0.1.1 --- 2011 . 01 . 26 - expansion to 32 channels - GUI restyling - live quantizer - fixed wrong handling of "mute" value when loading a patch - minor internal improvements 0.1.0 --- 2011 . 01 . 18 - ability to mute channels - stop and rewind buttons now affect only channels in loop mode - undo for ending loops - internal patch improvements to provide backward compatibility - better behaviour when exceeding the total amount of available memory - fixed random reversals of stereo field at the end of the beat bar - fixed a potential segmentation fault when freeing a sample 0.0.12 --- 2011 . 01 . 11 - ability to free a channel - "stop" button to suspend the general program - new "stop-to-end" mode for looped channels - new "full stop" key combination - enhanced mouse interaction - minor bugfixing 0.0.11 --- 2010 . 12 . 28 - customizable keys - GUI layer optimizations and improvements - overwrite confirmation when saving a patch - the browser always displays the patch folder when loading a new patch - browser url is now read-only to prevent manipulations 0.0.10 --- 2010 . 12 . 16 - new "single-mode retrig" mode added - expansion to 16 channels - new advanced file browser with the ability to navigate the filesystem - audio configuration now uses the "default" device, if not changed - graphical restyling for audio channels - fixed a random crash on startup, due to a wrong thread synch 0.0.9 --- 2010 . 12 . 08 - new loop once mode - new graphical beat meter - rewind-program button added - heavy buttons and controls restyling - reinforced header verification when a new patch is opened for reading - some bugfixing for the loading procedure of a patch - fixed a potential crash while a new sample is being loaded 0.0.8 --- 2010 . 11 . 28 - fixed a critical crash while loading a sample - GUI warning when loading a sample or a patch into an active channel - little optimization during the search for data into waves - all popup windows are now modal (always on top) - fixed a potential crash in case of malformed wave files 0.0.7 --- 2010 . 11 . 18 - new peak meter with clip warning and system status report - any "ok" button is associated to the "return" key (for fast inputs) - graphical improvements for checkboxes, buttons, smaller fonts in browsers - graphical feedback for missing samples - internal optimizations 0.0.6 --- 2010 . 11 . 01 - new 32 bit floating point audio engine - support for any wave bit-rate, from 8 bit pcm to 32 float - Giada now prompts when a sound card error occurs - removed the hard-limiting system, now useless - the "save patch" panel now shows the actual patchname in use - alphabetic sort into the file browser - fixed an annoying gui flickering - patch volume information are now handled correctly - minor internal optimizations - fixed a memory leak when loading a new patch - other memory optimizations 0.0.5 --- 2010 . 10 . 21 - Patch-based system: load/save your setup from/to a binary file - New audio configuration panel - New configuration file (giada.conf) where to store data - Complete implementation of the double click startup - Fixed a bug related to the confirm-on-quit window - Minor GUI beautifications - Extended documentation 0.0.4 --- 2010 . 10 . 11 - New internal sample-accurate loop engine - Ability to configure the period size through ini file - First implementation of the double click startup - Debug information are now properly tagged, reporting the interested layer 0.0.3 --- 2010 . 10 . 02 - (giada) New official logo - (giada) Ability to load single-channel samples - (giada) Capital letter consistency between GUI buttons - (giada) Added "cancel" button to the browser window - (giada) Endianness verification - (giada) Cleanup of the audio initialization procedure - (giada) Several internal optimization for audio playback - (giada) ALSA layer now tells if an underrun occurs - (giada) Internal memory allocation improvements - (giada) Fixed an unallocated hardware parameter into ALSA configuration - (wa) Information about wave endianness - Added a "Requirements" section to the readme file 0.0.2 --- 2010 . 09 . 17 - (giada) More visual feedbacks if a key is pressed - (giada) Added a graphical alert if a sample is in an incorrect format - (giada) Confirm on exit - (giada) Graphical improvements for the browser window - (giada) Browser window doesn't close itself anymore if a sample format is incorrect - (giada) Added "-- no sample --" for empty channels - (giada) Startup no longer fails if a sample from the ini file is not found - (giada) Internal optimization for the sample loading routine - (giada) More graphical consistency between subwindows - (giada) The sample name is now trucated to fit into its box, preventing overflow - (giada) Other minor GUI tweaks - (giada) Internal memory improvements to prevent a bad bug of allocation with malformed wave files - (wa) More information about sample size - (wa) Added calculations and comparison between data sizes 0.0.1 --- 2010 . 09 . 06 (initial release) giada-0.14.5/Makefile.am000066400000000000000000000377271322662744500147350ustar00rootroot00000000000000AUTOMAKE_OPTIONS = foreign # Define conditional variables ------------------------------------------------- # WITH_VST, LINUX, WINDOWS and OSX are varibles defined via AM_CONDITIONAL # inside configure.ac. extraSources = cppFlags = cxxFlags = -std=c++11 -Wall -Werror ldAdd = ldFlags = if WITH_VST extraSources += \ src/deps/juce/modules/juce_audio_basics/juce_audio_basics.cpp \ src/deps/juce/modules/juce_audio_processors/juce_audio_processors.cpp \ src/deps/juce/modules/juce_core/juce_core.cpp \ src/deps/juce/modules/juce_data_structures/juce_data_structures.cpp \ src/deps/juce/modules/juce_events/juce_events.cpp \ src/deps/juce/modules/juce_graphics/juce_graphics.cpp \ src/deps/juce/modules/juce_gui_basics/juce_gui_basics.cpp \ src/deps/juce/modules/juce_gui_extra/juce_gui_extra.cpp cppFlags += \ -I./src/deps/juce/modules \ -I./src/deps/vst \ -I/usr/include \ -I/usr/include/freetype2 \ -DJUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1 \ -DJUCE_STANDALONE_APPLICATION=1 \ -DJUCE_PLUGINHOST_VST=1 \ -DJUCE_PLUGINHOST_VST3=0 \ -DJUCE_PLUGINHOST_AU=0 \ -DJUCE_WEB_BROWSER=0 endif if WINDOWS extraSources += \ src/deps/rtaudio-mod/include/asio.h \ src/deps/rtaudio-mod/include/asio.cpp \ src/deps/rtaudio-mod/include/asiolist.h \ src/deps/rtaudio-mod/include/asiolist.cpp \ src/deps/rtaudio-mod/include/asiodrivers.h \ src/deps/rtaudio-mod/include/asiodrivers.cpp \ src/deps/rtaudio-mod/include/iasiothiscallresolver.h \ src/deps/rtaudio-mod/include/iasiothiscallresolver.cpp \ resource.rc cppFlags += \ -I./src/deps/rtaudio-mod/include \ -D__WINDOWS_ASIO__ \ -D__WINDOWS_WASAPI__ \ -D__WINDOWS_DS__ cxxFlags += -Wno-error ldAdd += -ldsound -lwsock32 -lm -lfltk -lwininet -lgdi32 -lshell32 -lvfw32 \ -lrpcrt4 -luuid -lcomctl32 -lole32 -lws2_32 -lsndfile -lsamplerate -lrtmidi \ -lwinmm -lsetupapi -lksuser -ljansson -limm32 -lglu32 -lshell32 -lversion \ -lopengl32 -loleaut32 -lshlwapi -lcomdlg32 # Generate a GUI application (-mwindows), make the build static (-static). ldFlags += -mwindows -static endif if LINUX # Add preprocessor flags to enable ALSA, Pulse and JACK in RtAudio. cppFlags += -D__LINUX_ALSA__ -D__LINUX_PULSE__ -D__UNIX_JACK__ # Don't stop on JUCE's unused functions. cxxFlags += -Wno-error=unused-function ldAdd += -lsndfile -lfltk -lXext -lX11 -lXft -lXpm -lm -ljack -lasound \ -lpthread -ldl -lpulse-simple -lpulse -lsamplerate -lrtmidi -ljansson \ -lfreetype endif if OSX extraSources += src/utils/cocoa.mm src/utils/cocoa.h # -ObjC++: Juce requires to build some Objective C code cxxFlags += -ObjC++ -Wno-auto-var-id ldAdd += -lsndfile -lfltk -lrtmidi -lsamplerate -ljansson -lm -lpthread \ -lFLAC -logg -lvorbis -lvorbisenc ldFlags += -framework CoreAudio -framework Cocoa -framework Carbon \ -framework CoreMIDI -framework CoreFoundation -framework Accelerate \ -framework WebKit -framework QuartzCore -framework IOKit endif # make giada ------------------------------------------------------------------- bin_PROGRAMS = giada giada_SOURCES = \ src/main.cpp \ src/core/const.h \ src/core/channel.h \ src/core/channel.cpp \ src/core/sampleChannel.h \ src/core/sampleChannel.cpp \ src/core/midiDispatcher.h \ src/core/midiDispatcher.cpp \ src/core/midiChannel.h \ src/core/midiChannel.cpp \ src/core/midiMapConf.h \ src/core/midiMapConf.cpp \ src/core/midiEvent.h \ src/core/midiEvent.cpp \ src/core/conf.h \ src/core/conf.cpp \ src/core/kernelAudio.h \ src/core/kernelAudio.cpp \ src/core/pluginHost.h \ src/core/pluginHost.cpp \ src/core/mixerHandler.h \ src/core/mixerHandler.cpp \ src/core/init.h \ src/core/init.cpp \ src/core/plugin.h \ src/core/plugin.cpp \ src/core/wave.h \ src/core/wave.cpp \ src/core/waveFx.h \ src/core/waveFx.cpp \ src/core/kernelMidi.h \ src/core/kernelMidi.cpp \ src/core/graphics.h \ src/core/graphics.cpp \ src/core/patch.h \ src/core/patch.cpp \ src/core/recorder.h \ src/core/recorder.cpp \ src/core/mixer.h \ src/core/mixer.cpp \ src/core/storager.h \ src/core/storager.cpp \ src/core/clock.h \ src/core/clock.cpp \ src/core/waveManager.h \ src/core/waveManager.cpp \ src/glue/main.h \ src/glue/main.cpp \ src/glue/io.h \ src/glue/io.cpp \ src/glue/storage.h \ src/glue/storage.cpp \ src/glue/channel.h \ src/glue/channel.cpp \ src/glue/plugin.h \ src/glue/plugin.cpp \ src/glue/transport.h \ src/glue/transport.cpp \ src/glue/recorder.h \ src/glue/recorder.cpp \ src/glue/sampleEditor.h \ src/glue/sampleEditor.cpp \ src/gui/dialogs/window.h \ src/gui/dialogs/window.cpp \ src/gui/dialogs/gd_keyGrabber.h \ src/gui/dialogs/gd_keyGrabber.cpp \ src/gui/dialogs/gd_about.h \ src/gui/dialogs/gd_about.cpp \ src/gui/dialogs/gd_mainWindow.h \ src/gui/dialogs/gd_mainWindow.cpp \ src/gui/dialogs/beatsInput.h \ src/gui/dialogs/beatsInput.cpp \ src/gui/dialogs/gd_warnings.h \ src/gui/dialogs/gd_warnings.cpp \ src/gui/dialogs/bpmInput.h \ src/gui/dialogs/bpmInput.cpp \ src/gui/dialogs/channelNameInput.h \ src/gui/dialogs/channelNameInput.cpp \ src/gui/dialogs/gd_config.h \ src/gui/dialogs/gd_config.cpp \ src/gui/dialogs/gd_devInfo.h \ src/gui/dialogs/gd_devInfo.cpp \ src/gui/dialogs/pluginList.h \ src/gui/dialogs/pluginList.cpp \ src/gui/dialogs/pluginWindow.h \ src/gui/dialogs/pluginWindow.cpp \ src/gui/dialogs/sampleEditor.h \ src/gui/dialogs/sampleEditor.cpp \ src/gui/dialogs/pluginWindowGUI.h \ src/gui/dialogs/pluginWindowGUI.cpp \ src/gui/dialogs/gd_actionEditor.h \ src/gui/dialogs/gd_actionEditor.cpp \ src/gui/dialogs/pluginChooser.h \ src/gui/dialogs/pluginChooser.cpp \ src/gui/dialogs/browser/browserBase.h \ src/gui/dialogs/browser/browserBase.cpp \ src/gui/dialogs/browser/browserDir.h \ src/gui/dialogs/browser/browserDir.cpp \ src/gui/dialogs/browser/browserLoad.h \ src/gui/dialogs/browser/browserLoad.cpp \ src/gui/dialogs/browser/browserSave.h \ src/gui/dialogs/browser/browserSave.cpp \ src/gui/dialogs/midiIO/midiOutputBase.h \ src/gui/dialogs/midiIO/midiOutputBase.cpp \ src/gui/dialogs/midiIO/midiOutputSampleCh.h \ src/gui/dialogs/midiIO/midiOutputSampleCh.cpp \ src/gui/dialogs/midiIO/midiOutputMidiCh.h \ src/gui/dialogs/midiIO/midiOutputMidiCh.cpp \ src/gui/dialogs/midiIO/midiInputBase.h \ src/gui/dialogs/midiIO/midiInputBase.cpp \ src/gui/dialogs/midiIO/midiInputChannel.h \ src/gui/dialogs/midiIO/midiInputChannel.cpp \ src/gui/dialogs/midiIO/midiInputMaster.h \ src/gui/dialogs/midiIO/midiInputMaster.cpp \ src/gui/elems/midiLearner.h \ src/gui/elems/midiLearner.cpp \ src/gui/elems/browser.h \ src/gui/elems/browser.cpp \ src/gui/elems/soundMeter.h \ src/gui/elems/soundMeter.cpp \ src/gui/elems/plugin/pluginBrowser.h \ src/gui/elems/plugin/pluginBrowser.cpp \ src/gui/elems/plugin/pluginParameter.h \ src/gui/elems/plugin/pluginParameter.cpp \ src/gui/elems/sampleEditor/waveform.h \ src/gui/elems/sampleEditor/waveform.cpp \ src/gui/elems/sampleEditor/waveTools.h \ src/gui/elems/sampleEditor/waveTools.cpp \ src/gui/elems/sampleEditor/volumeTool.h \ src/gui/elems/sampleEditor/volumeTool.cpp \ src/gui/elems/sampleEditor/boostTool.h \ src/gui/elems/sampleEditor/boostTool.cpp \ src/gui/elems/sampleEditor/panTool.h \ src/gui/elems/sampleEditor/panTool.cpp \ src/gui/elems/sampleEditor/pitchTool.h \ src/gui/elems/sampleEditor/pitchTool.cpp \ src/gui/elems/sampleEditor/rangeTool.h \ src/gui/elems/sampleEditor/rangeTool.cpp \ src/gui/elems/sampleEditor/shiftTool.h \ src/gui/elems/sampleEditor/shiftTool.cpp \ src/gui/elems/actionEditor/baseActionEditor.h \ src/gui/elems/actionEditor/baseActionEditor.cpp \ src/gui/elems/actionEditor/envelopeEditor.h \ src/gui/elems/actionEditor/envelopeEditor.cpp \ src/gui/elems/actionEditor/pianoRoll.h \ src/gui/elems/actionEditor/pianoRoll.cpp \ src/gui/elems/actionEditor/noteEditor.h \ src/gui/elems/actionEditor/noteEditor.cpp \ src/gui/elems/actionEditor/basePianoItem.h \ src/gui/elems/actionEditor/basePianoItem.cpp \ src/gui/elems/actionEditor/pianoItem.h \ src/gui/elems/actionEditor/pianoItem.cpp \ src/gui/elems/actionEditor/pianoItemOrphaned.h \ src/gui/elems/actionEditor/pianoItemOrphaned.cpp \ src/gui/elems/actionEditor/muteEditor.h \ src/gui/elems/actionEditor/muteEditor.cpp \ src/gui/elems/actionEditor/actionEditor.h \ src/gui/elems/actionEditor/actionEditor.cpp \ src/gui/elems/actionEditor/action.h \ src/gui/elems/actionEditor/action.cpp \ src/gui/elems/actionEditor/gridTool.h \ src/gui/elems/actionEditor/gridTool.cpp \ src/gui/elems/mainWindow/mainIO.h \ src/gui/elems/mainWindow/mainIO.cpp \ src/gui/elems/mainWindow/mainMenu.h \ src/gui/elems/mainWindow/mainMenu.cpp \ src/gui/elems/mainWindow/mainTimer.h \ src/gui/elems/mainWindow/mainTimer.cpp \ src/gui/elems/mainWindow/mainTransport.h \ src/gui/elems/mainWindow/mainTransport.cpp \ src/gui/elems/mainWindow/beatMeter.h \ src/gui/elems/mainWindow/beatMeter.cpp \ src/gui/elems/mainWindow/keyboard/channelMode.h \ src/gui/elems/mainWindow/keyboard/channelMode.cpp \ src/gui/elems/mainWindow/keyboard/channelButton.h \ src/gui/elems/mainWindow/keyboard/channelButton.cpp \ src/gui/elems/mainWindow/keyboard/channelStatus.h \ src/gui/elems/mainWindow/keyboard/channelStatus.cpp \ src/gui/elems/mainWindow/keyboard/keyboard.h \ src/gui/elems/mainWindow/keyboard/keyboard.cpp \ src/gui/elems/mainWindow/keyboard/column.h \ src/gui/elems/mainWindow/keyboard/column.cpp \ src/gui/elems/mainWindow/keyboard/sampleChannel.h \ src/gui/elems/mainWindow/keyboard/sampleChannel.cpp \ src/gui/elems/mainWindow/keyboard/midiChannel.h \ src/gui/elems/mainWindow/keyboard/midiChannel.cpp \ src/gui/elems/mainWindow/keyboard/channel.h \ src/gui/elems/mainWindow/keyboard/channel.cpp \ src/gui/elems/mainWindow/keyboard/sampleChannelButton.h \ src/gui/elems/mainWindow/keyboard/sampleChannelButton.cpp \ src/gui/elems/mainWindow/keyboard/midiChannelButton.h \ src/gui/elems/mainWindow/keyboard/midiChannelButton.cpp \ src/gui/elems/config/tabMisc.h \ src/gui/elems/config/tabMisc.cpp \ src/gui/elems/config/tabMidi.h \ src/gui/elems/config/tabMidi.cpp \ src/gui/elems/config/tabAudio.h \ src/gui/elems/config/tabAudio.cpp \ src/gui/elems/config/tabBehaviors.h \ src/gui/elems/config/tabBehaviors.cpp \ src/gui/elems/config/tabPlugins.h \ src/gui/elems/config/tabPlugins.cpp \ src/gui/elems/basics/scroll.h \ src/gui/elems/basics/scroll.cpp \ src/gui/elems/basics/boxtypes.h \ src/gui/elems/basics/boxtypes.cpp \ src/gui/elems/basics/baseButton.h \ src/gui/elems/basics/baseButton.cpp \ src/gui/elems/basics/statusButton.h \ src/gui/elems/basics/statusButton.cpp \ src/gui/elems/basics/button.h \ src/gui/elems/basics/button.cpp \ src/gui/elems/basics/idButton.h \ src/gui/elems/basics/idButton.cpp \ src/gui/elems/basics/resizerBar.h \ src/gui/elems/basics/resizerBar.cpp \ src/gui/elems/basics/input.h \ src/gui/elems/basics/input.cpp \ src/gui/elems/basics/liquidScroll.h \ src/gui/elems/basics/liquidScroll.cpp \ src/gui/elems/basics/choice.h \ src/gui/elems/basics/choice.cpp \ src/gui/elems/basics/dial.h \ src/gui/elems/basics/dial.cpp \ src/gui/elems/basics/box.h \ src/gui/elems/basics/box.cpp \ src/gui/elems/basics/slider.h \ src/gui/elems/basics/slider.cpp \ src/gui/elems/basics/progress.h \ src/gui/elems/basics/progress.cpp \ src/gui/elems/basics/check.h \ src/gui/elems/basics/check.cpp \ src/gui/elems/basics/radio.h \ src/gui/elems/basics/radio.cpp \ src/utils/log.h \ src/utils/log.cpp \ src/utils/time.h \ src/utils/time.cpp \ src/utils/math.h \ src/utils/math.cpp \ src/utils/gui.h \ src/utils/gui.cpp \ src/utils/gvector.h \ src/utils/fs.h \ src/utils/fs.cpp \ src/utils/deps.h \ src/utils/deps.cpp \ src/utils/string.h \ src/utils/string.cpp \ src/deps/rtaudio-mod/RtAudio.h \ src/deps/rtaudio-mod/RtAudio.cpp giada_SOURCES += $(extraSources) giada_CPPFLAGS = $(cppFlags) giada_CXXFLAGS = $(cxxFlags) giada_LDADD = $(ldAdd) giada_LDFLAGS = $(ldFlags) # Used only under MinGW to compile the resource.rc file (program icon) resource.o: windres src/ext/resource.rc -o resource.o # make check ------------------------------------------------------------------- TESTS = giada_tests check_PROGRAMS = giada_tests giada_tests_SOURCES = \ tests/main.cpp \ tests/conf.cpp \ tests/wave.cpp \ tests/waveManager.cpp \ tests/patch.cpp \ tests/midiMapConf.cpp \ tests/pluginHost.cpp \ tests/utils.cpp \ tests/recorder.cpp \ tests/waveFx.cpp \ src/core/conf.cpp \ src/core/wave.cpp \ src/core/waveManager.cpp \ src/core/waveFx.cpp \ src/core/midiMapConf.cpp \ src/core/patch.cpp \ src/core/plugin.cpp \ src/core/storager.cpp \ src/core/recorder.cpp \ src/utils/fs.cpp \ src/utils/string.cpp \ src/utils/time.cpp \ src/utils/log.cpp giada_tests_SOURCES += $(extraSources) giada_tests_CPPFLAGS = $(cppFlags) giada_tests_CXXFLAGS = $(cxxFlags) giada_tests_LDADD = $(ldAdd) giada_tests_LDFLAGS = $(ldFlags) # make rename ------------------------------------------------------------------ if LINUX rename: mv giada giada_lin endif if WINDOWS rename: mv giada giada_win.exe endif if OSX rename: mv giada giada_osx endif giada-0.14.5/README.md000066400000000000000000000070231322662744500141420ustar00rootroot00000000000000# Giada - Your Hardcore Loopmachine Official website: http://www.giadamusic.com | Travis CI status: [![Build Status](https://travis-ci.org/monocasual/giada.svg?branch=master)](https://travis-ci.org/monocasual/giada) ## What is Giada? Giada is a free, minimal, hardcore audio tool for DJs, live performers and electronic musicians. How does it work? Just pick up your channel, fill it with samples or MIDI events and start the show by using this tiny piece of software as a loop machine, drum machine, sequencer, live sampler or yet as a plugin/effect host. Giada aims to be a compact and portable virtual device for Linux, Mac OS X and Windows for production use and live sets. ➔➔➔ [See Giada in action!](http://www.youtube.com/user/GiadaLoopMachine) ![Giada Loop Machine screenshot](http://giadamusic.com/public/img/screenshots/giada-loop-machine-screenshot-14-carousel.jpg) ## Main features * Ultra-lightweight internal design; * multi-thread/multi-core support; * 32-bit floating point audio engine; * ALSA, JACK + Transport, CoreAudio, ASIO and DirectSound full support; * high quality internal resampler; * unlimited number of channels (controllable via computer keyboard); * several playback modes and combinations; * BPM and beat sync with sample-accurate loop engine; * VST and VSTi (instrument) plug-in support; * MIDI input and output support, featuring custom [MIDI lightning messages](https://github.com/monocasual/giada-midimaps); * super-sleek, built-in wave editor; * live sampler from external inputs; * live action recorder with automatic quantizer; * piano Roll editor; * portable patch storage system, based on super-hackable JSON files; * support for all major uncompressed file formats; * test-driven development style supported by [Travis CI](https://travis-ci.org/monocasual/giada) and [Catch](https://github.com/philsquared/Catch) * under a constant stage of development; * 100% open-source GPL v3. ## License Giada is available under the terms of the GNU General Public License. Take a look at the COPYING file for further informations. ## Documentation Docs are available online on the official website: http://www.giadamusic.com/documentation Found a typo or a terrible mistake? Feel free to clone the [website repository](https://github.com/monocasual/giada-www) and send us your pull requests. ## Build Giada from source We do our best to make the compilation process as simple as possible. You can find all the information in the [official docs page](http://giadamusic.com/documentation/show/compiling-from-source). Something went wrong? Try our new [Docker image](https://github.com/monocasual/giada-docker) for building and running Giada without hurdles. ## Bugs, requests and questions for non-developers Feel free to ask anything on our end-user forum: http://www.giadamusic.com/forum ## Copyright Giada is Copyright (C) 2010-2018 by Giovanni A. Zuliani | Monocasual Giada - Your Hardcore Loopmachine 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. Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see . giada-0.14.5/autogen.sh000077500000000000000000001345321322662744500146720ustar00rootroot00000000000000#!/bin/sh # a u t o g e n . s h # # Copyright (c) 2005-2009 United States Government as represented by # the U.S. Army Research Laboratory. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # 3. The name of the author may not be used to endorse or promote # products derived from this software without specific prior written # permission. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS # OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE # GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # ### # # Script for automatically preparing the sources for compilation by # performing the myriad of necessary steps. The script attempts to # detect proper version support, and outputs warnings about particular # systems that have autotool peculiarities. # # Basically, if everything is set up and installed correctly, the # script will validate that minimum versions of the GNU Build System # tools are installed, account for several common configuration # issues, and then simply run autoreconf for you. # # If autoreconf fails, which can happen for many valid configurations, # this script proceeds to run manual preparation steps effectively # providing a POSIX shell script (mostly complete) reimplementation of # autoreconf. # # The AUTORECONF, AUTOCONF, AUTOMAKE, LIBTOOLIZE, ACLOCAL, AUTOHEADER # environment variables and corresponding _OPTIONS variables (e.g. # AUTORECONF_OPTIONS) may be used to override the default automatic # detection behaviors. Similarly the _VERSION variables will override # the minimum required version numbers. # # Examples: # # To obtain help on usage: # ./autogen.sh --help # # To obtain verbose output: # ./autogen.sh --verbose # # To skip autoreconf and prepare manually: # AUTORECONF=false ./autogen.sh # # To verbosely try running with an older (unsupported) autoconf: # AUTOCONF_VERSION=2.50 ./autogen.sh --verbose # # Author: # Christopher Sean Morrison # # Patches: # Sebastian Pipping # ###################################################################### # set to minimum acceptable version of autoconf if [ "x$AUTOCONF_VERSION" = "x" ] ; then AUTOCONF_VERSION=2.52 fi # set to minimum acceptable version of automake if [ "x$AUTOMAKE_VERSION" = "x" ] ; then AUTOMAKE_VERSION=1.6.0 fi # set to minimum acceptable version of libtool if [ "x$LIBTOOL_VERSION" = "x" ] ; then LIBTOOL_VERSION=1.4.2 fi ################## # ident function # ################## ident ( ) { # extract copyright from header __copyright="`grep Copyright $AUTOGEN_SH | head -${HEAD_N}1 | awk '{print $4}'`" if [ "x$__copyright" = "x" ] ; then __copyright="`date +%Y`" fi # extract version from CVS Id string __id="$Id: autogen.sh 33925 2009-03-01 23:27:06Z brlcad $" __version="`echo $__id | sed 's/.*\([0-9][0-9][0-9][0-9]\)[-\/]\([0-9][0-9]\)[-\/]\([0-9][0-9]\).*/\1\2\3/'`" if [ "x$__version" = "x" ] ; then __version="" fi echo "autogen.sh build preparation script by Christopher Sean Morrison" echo " + config.guess download patch by Sebastian Pipping (2008-12-03)" echo "revised 3-clause BSD-style license, copyright (c) $__copyright" echo "script version $__version, ISO/IEC 9945 POSIX shell script" } ################## # USAGE FUNCTION # ################## usage ( ) { echo "Usage: $AUTOGEN_SH [-h|--help] [-v|--verbose] [-q|--quiet] [-d|--download] [--version]" echo " --help Help on $NAME_OF_AUTOGEN usage" echo " --verbose Verbose progress output" echo " --quiet Quiet suppressed progress output" echo " --download Download the latest config.guess from gnulib" echo " --version Only perform GNU Build System version checks" echo echo "Description: This script will validate that minimum versions of the" echo "GNU Build System tools are installed and then run autoreconf for you." echo "Should autoreconf fail, manual preparation steps will be run" echo "potentially accounting for several common preparation issues. The" echo "AUTORECONF, AUTOCONF, AUTOMAKE, LIBTOOLIZE, ACLOCAL, AUTOHEADER," echo "PROJECT, & CONFIGURE environment variables and corresponding _OPTIONS" echo "variables (e.g. AUTORECONF_OPTIONS) may be used to override the" echo "default automatic detection behavior." echo ident return 0 } ########################## # VERSION_ERROR FUNCTION # ########################## version_error ( ) { if [ "x$1" = "x" ] ; then echo "INTERNAL ERROR: version_error was not provided a version" exit 1 fi if [ "x$2" = "x" ] ; then echo "INTERNAL ERROR: version_error was not provided an application name" exit 1 fi $ECHO $ECHO "ERROR: To prepare the ${PROJECT} build system from scratch," $ECHO " at least version $1 of $2 must be installed." $ECHO $ECHO "$NAME_OF_AUTOGEN does not need to be run on the same machine that will" $ECHO "run configure or make. Either the GNU Autotools will need to be installed" $ECHO "or upgraded on this system, or $NAME_OF_AUTOGEN must be run on the source" $ECHO "code on another system and then transferred to here. -- Cheers!" $ECHO } ########################## # VERSION_CHECK FUNCTION # ########################## version_check ( ) { if [ "x$1" = "x" ] ; then echo "INTERNAL ERROR: version_check was not provided a minimum version" exit 1 fi _min="$1" if [ "x$2" = "x" ] ; then echo "INTERNAL ERROR: version check was not provided a comparison version" exit 1 fi _cur="$2" # needed to handle versions like 1.10 and 1.4-p6 _min="`echo ${_min}. | sed 's/[^0-9]/./g' | sed 's/\.\././g'`" _cur="`echo ${_cur}. | sed 's/[^0-9]/./g' | sed 's/\.\././g'`" _min_major="`echo $_min | cut -d. -f1`" _min_minor="`echo $_min | cut -d. -f2`" _min_patch="`echo $_min | cut -d. -f3`" _cur_major="`echo $_cur | cut -d. -f1`" _cur_minor="`echo $_cur | cut -d. -f2`" _cur_patch="`echo $_cur | cut -d. -f3`" if [ "x$_min_major" = "x" ] ; then _min_major=0 fi if [ "x$_min_minor" = "x" ] ; then _min_minor=0 fi if [ "x$_min_patch" = "x" ] ; then _min_patch=0 fi if [ "x$_cur_minor" = "x" ] ; then _cur_major=0 fi if [ "x$_cur_minor" = "x" ] ; then _cur_minor=0 fi if [ "x$_cur_patch" = "x" ] ; then _cur_patch=0 fi $VERBOSE_ECHO "Checking if ${_cur_major}.${_cur_minor}.${_cur_patch} is greater than ${_min_major}.${_min_minor}.${_min_patch}" if [ $_min_major -lt $_cur_major ] ; then return 0 elif [ $_min_major -eq $_cur_major ] ; then if [ $_min_minor -lt $_cur_minor ] ; then return 0 elif [ $_min_minor -eq $_cur_minor ] ; then if [ $_min_patch -lt $_cur_patch ] ; then return 0 elif [ $_min_patch -eq $_cur_patch ] ; then return 0 fi fi fi return 1 } ###################################### # LOCATE_CONFIGURE_TEMPLATE FUNCTION # ###################################### locate_configure_template ( ) { _pwd="`pwd`" if test -f "./configure.ac" ; then echo "./configure.ac" elif test -f "./configure.in" ; then echo "./configure.in" elif test -f "$_pwd/configure.ac" ; then echo "$_pwd/configure.ac" elif test -f "$_pwd/configure.in" ; then echo "$_pwd/configure.in" elif test -f "$PATH_TO_AUTOGEN/configure.ac" ; then echo "$PATH_TO_AUTOGEN/configure.ac" elif test -f "$PATH_TO_AUTOGEN/configure.in" ; then echo "$PATH_TO_AUTOGEN/configure.in" fi } ################## # argument check # ################## ARGS="$*" PATH_TO_AUTOGEN="`dirname $0`" NAME_OF_AUTOGEN="`basename $0`" AUTOGEN_SH="$PATH_TO_AUTOGEN/$NAME_OF_AUTOGEN" LIBTOOL_M4="${PATH_TO_AUTOGEN}/misc/libtool.m4" if [ "x$HELP" = "x" ] ; then HELP=no fi if [ "x$QUIET" = "x" ] ; then QUIET=no fi if [ "x$VERBOSE" = "x" ] ; then VERBOSE=no fi if [ "x$VERSION_ONLY" = "x" ] ; then VERSION_ONLY=no fi if [ "x$DOWNLOAD" = "x" ] ; then DOWNLOAD=no fi if [ "x$AUTORECONF_OPTIONS" = "x" ] ; then AUTORECONF_OPTIONS="-i -f" fi if [ "x$AUTOCONF_OPTIONS" = "x" ] ; then AUTOCONF_OPTIONS="-f" fi if [ "x$AUTOMAKE_OPTIONS" = "x" ] ; then AUTOMAKE_OPTIONS="-a -c -f" fi ALT_AUTOMAKE_OPTIONS="-a -c" if [ "x$LIBTOOLIZE_OPTIONS" = "x" ] ; then LIBTOOLIZE_OPTIONS="--automake -c -f" fi ALT_LIBTOOLIZE_OPTIONS="--automake --copy --force" if [ "x$ACLOCAL_OPTIONS" = "x" ] ; then ACLOCAL_OPTIONS="" fi if [ "x$AUTOHEADER_OPTIONS" = "x" ] ; then AUTOHEADER_OPTIONS="" fi if [ "x$CONFIG_GUESS_URL" = "x" ] ; then CONFIG_GUESS_URL="http://git.savannah.gnu.org/gitweb/?p=gnulib.git;a=blob_plain;f=build-aux/config.guess;hb=HEAD" fi for arg in $ARGS ; do case "x$arg" in x--help) HELP=yes ;; x-[hH]) HELP=yes ;; x--quiet) QUIET=yes ;; x-[qQ]) QUIET=yes ;; x--verbose) VERBOSE=yes ;; x-[dD]) DOWNLOAD=yes ;; x--download) DOWNLOAD=yes ;; x-[vV]) VERBOSE=yes ;; x--version) VERSION_ONLY=yes ;; *) echo "Unknown option: $arg" echo usage exit 1 ;; esac done ##################### # environment check # ##################### # sanity check before recursions potentially begin if [ ! -f "$AUTOGEN_SH" ] ; then echo "INTERNAL ERROR: $AUTOGEN_SH does not exist" if [ ! "x$0" = "x$AUTOGEN_SH" ] ; then echo "INTERNAL ERROR: dirname/basename inconsistency: $0 != $AUTOGEN_SH" fi exit 1 fi # force locale setting to C so things like date output as expected LC_ALL=C # commands that this script expects for __cmd in echo head tail pwd ; do echo "test" | $__cmd > /dev/null 2>&1 if [ $? != 0 ] ; then echo "INTERNAL ERROR: '${__cmd}' command is required" exit 2 fi done echo "test" | grep "test" > /dev/null 2>&1 if test ! x$? = x0 ; then echo "INTERNAL ERROR: grep command is required" exit 1 fi echo "test" | sed "s/test/test/" > /dev/null 2>&1 if test ! x$? = x0 ; then echo "INTERNAL ERROR: sed command is required" exit 1 fi # determine the behavior of echo case `echo "testing\c"; echo 1,2,3`,`echo -n testing; echo 1,2,3` in *c*,-n*) ECHO_N= ECHO_C=' ' ECHO_T=' ' ;; *c*,* ) ECHO_N=-n ECHO_C= ECHO_T= ;; *) ECHO_N= ECHO_C='\c' ECHO_T= ;; esac # determine the behavior of head case "x`echo 'head' | head -n 1 2>&1`" in *xhead*) HEAD_N="n " ;; *) HEAD_N="" ;; esac # determine the behavior of tail case "x`echo 'tail' | tail -n 1 2>&1`" in *xtail*) TAIL_N="n " ;; *) TAIL_N="" ;; esac VERBOSE_ECHO=: ECHO=: if [ "x$QUIET" = "xyes" ] ; then if [ "x$VERBOSE" = "xyes" ] ; then echo "Verbose output quelled by quiet option. Further output disabled." fi else ECHO=echo if [ "x$VERBOSE" = "xyes" ] ; then echo "Verbose output enabled" VERBOSE_ECHO=echo fi fi # allow a recursive run to disable further recursions if [ "x$RUN_RECURSIVE" = "x" ] ; then RUN_RECURSIVE=yes fi ################################################ # check for help arg and bypass version checks # ################################################ if [ "x`echo $ARGS | sed 's/.*[hH][eE][lL][pP].*/help/'`" = "xhelp" ] ; then HELP=yes fi if [ "x$HELP" = "xyes" ] ; then usage $ECHO "---" $ECHO "Help was requested. No preparation or configuration will be performed." exit 0 fi ####################### # set up signal traps # ####################### untrap_abnormal ( ) { for sig in 1 2 13 15; do trap - $sig done } # do this cleanup whenever we exit. trap ' # start from the root if test -d "$START_PATH" ; then cd "$START_PATH" fi # restore/delete backup files if test "x$PFC_INIT" = "x1" ; then recursive_restore fi ' 0 # trap SIGHUP (1), SIGINT (2), SIGPIPE (13), SIGTERM (15) for sig in 1 2 13 15; do trap ' $ECHO "" $ECHO "Aborting $NAME_OF_AUTOGEN: caught signal '$sig'" # start from the root if test -d "$START_PATH" ; then cd "$START_PATH" fi # clean up on abnormal exit $VERBOSE_ECHO "rm -rf autom4te.cache" rm -rf autom4te.cache if test -f "acinclude.m4.$$.backup" ; then $VERBOSE_ECHO "cat acinclude.m4.$$.backup > acinclude.m4" chmod u+w acinclude.m4 cat acinclude.m4.$$.backup > acinclude.m4 $VERBOSE_ECHO "rm -f acinclude.m4.$$.backup" rm -f acinclude.m4.$$.backup fi { (exit 1); exit 1; } ' $sig done ############################# # look for a configure file # ############################# if [ "x$CONFIGURE" = "x" ] ; then CONFIGURE="`locate_configure_template`" if [ ! "x$CONFIGURE" = "x" ] ; then $VERBOSE_ECHO "Found a configure template: $CONFIGURE" fi else $ECHO "Using CONFIGURE environment variable override: $CONFIGURE" fi if [ "x$CONFIGURE" = "x" ] ; then if [ "x$VERSION_ONLY" = "xyes" ] ; then CONFIGURE=/dev/null else $ECHO $ECHO "A configure.ac or configure.in file could not be located implying" $ECHO "that the GNU Build System is at least not used in this directory. In" $ECHO "any case, there is nothing to do here without one of those files." $ECHO $ECHO "ERROR: No configure.in or configure.ac file found in `pwd`" exit 1 fi fi #################### # get project name # #################### if [ "x$PROJECT" = "x" ] ; then PROJECT="`grep AC_INIT $CONFIGURE | grep -v '.*#.*AC_INIT' | tail -${TAIL_N}1 | sed 's/^[ ]*AC_INIT(\([^,)]*\).*/\1/' | sed 's/.*\[\(.*\)\].*/\1/'`" if [ "x$PROJECT" = "xAC_INIT" ] ; then # projects might be using the older/deprecated arg-less AC_INIT .. look for AM_INIT_AUTOMAKE instead PROJECT="`grep AM_INIT_AUTOMAKE $CONFIGURE | grep -v '.*#.*AM_INIT_AUTOMAKE' | tail -${TAIL_N}1 | sed 's/^[ ]*AM_INIT_AUTOMAKE(\([^,)]*\).*/\1/' | sed 's/.*\[\(.*\)\].*/\1/'`" fi if [ "x$PROJECT" = "xAM_INIT_AUTOMAKE" ] ; then PROJECT="project" fi if [ "x$PROJECT" = "x" ] ; then PROJECT="project" fi else $ECHO "Using PROJECT environment variable override: $PROJECT" fi $ECHO "Preparing the $PROJECT build system...please wait" $ECHO ######################## # check for autoreconf # ######################## HAVE_AUTORECONF=no if [ "x$AUTORECONF" = "x" ] ; then for AUTORECONF in autoreconf ; do $VERBOSE_ECHO "Checking autoreconf version: $AUTORECONF --version" $AUTORECONF --version > /dev/null 2>&1 if [ $? = 0 ] ; then HAVE_AUTORECONF=yes break fi done else HAVE_AUTORECONF=yes $ECHO "Using AUTORECONF environment variable override: $AUTORECONF" fi ########################## # autoconf version check # ########################## _acfound=no if [ "x$AUTOCONF" = "x" ] ; then for AUTOCONF in autoconf ; do $VERBOSE_ECHO "Checking autoconf version: $AUTOCONF --version" $AUTOCONF --version > /dev/null 2>&1 if [ $? = 0 ] ; then _acfound=yes break fi done else _acfound=yes $ECHO "Using AUTOCONF environment variable override: $AUTOCONF" fi _report_error=no if [ ! "x$_acfound" = "xyes" ] ; then $ECHO "ERROR: Unable to locate GNU Autoconf." _report_error=yes else _version="`$AUTOCONF --version | head -${HEAD_N}1 | sed 's/[^0-9]*\([0-9\.][0-9\.]*\)/\1/'`" if [ "x$_version" = "x" ] ; then _version="0.0.0" fi $ECHO "Found GNU Autoconf version $_version" version_check "$AUTOCONF_VERSION" "$_version" if [ $? -ne 0 ] ; then _report_error=yes fi fi if [ "x$_report_error" = "xyes" ] ; then version_error "$AUTOCONF_VERSION" "GNU Autoconf" exit 1 fi ########################## # automake version check # ########################## _amfound=no if [ "x$AUTOMAKE" = "x" ] ; then for AUTOMAKE in automake ; do $VERBOSE_ECHO "Checking automake version: $AUTOMAKE --version" $AUTOMAKE --version > /dev/null 2>&1 if [ $? = 0 ] ; then _amfound=yes break fi done else _amfound=yes $ECHO "Using AUTOMAKE environment variable override: $AUTOMAKE" fi _report_error=no if [ ! "x$_amfound" = "xyes" ] ; then $ECHO $ECHO "ERROR: Unable to locate GNU Automake." _report_error=yes else _version="`$AUTOMAKE --version | head -${HEAD_N}1 | sed 's/[^0-9]*\([0-9\.][0-9\.]*\)/\1/'`" if [ "x$_version" = "x" ] ; then _version="0.0.0" fi $ECHO "Found GNU Automake version $_version" version_check "$AUTOMAKE_VERSION" "$_version" if [ $? -ne 0 ] ; then _report_error=yes fi fi if [ "x$_report_error" = "xyes" ] ; then version_error "$AUTOMAKE_VERSION" "GNU Automake" exit 1 fi ######################## # check for libtoolize # ######################## HAVE_LIBTOOLIZE=yes HAVE_ALT_LIBTOOLIZE=no _ltfound=no if [ "x$LIBTOOLIZE" = "x" ] ; then LIBTOOLIZE=libtoolize $VERBOSE_ECHO "Checking libtoolize version: $LIBTOOLIZE --version" $LIBTOOLIZE --version > /dev/null 2>&1 if [ ! $? = 0 ] ; then HAVE_LIBTOOLIZE=no $ECHO if [ "x$HAVE_AUTORECONF" = "xno" ] ; then $ECHO "Warning: libtoolize does not appear to be available." else $ECHO "Warning: libtoolize does not appear to be available. This means that" $ECHO "the automatic build preparation via autoreconf will probably not work." $ECHO "Preparing the build by running each step individually, however, should" $ECHO "work and will be done automatically for you if autoreconf fails." fi # look for some alternates for tool in glibtoolize libtoolize15 libtoolize14 libtoolize13 ; do $VERBOSE_ECHO "Checking libtoolize alternate: $tool --version" _glibtoolize="`$tool --version > /dev/null 2>&1`" if [ $? = 0 ] ; then $VERBOSE_ECHO "Found $tool --version" _glti="`which $tool`" if [ "x$_glti" = "x" ] ; then $VERBOSE_ECHO "Cannot find $tool with which" continue; fi if test ! -f "$_glti" ; then $VERBOSE_ECHO "Cannot use $tool, $_glti is not a file" continue; fi _gltidir="`dirname $_glti`" if [ "x$_gltidir" = "x" ] ; then $VERBOSE_ECHO "Cannot find $tool path with dirname of $_glti" continue; fi if test ! -d "$_gltidir" ; then $VERBOSE_ECHO "Cannot use $tool, $_gltidir is not a directory" continue; fi HAVE_ALT_LIBTOOLIZE=yes LIBTOOLIZE="$tool" $ECHO $ECHO "Fortunately, $tool was found which means that your system may simply" $ECHO "have a non-standard or incomplete GNU Autotools install. If you have" $ECHO "sufficient system access, it may be possible to quell this warning by" $ECHO "running:" $ECHO sudo -V > /dev/null 2>&1 if [ $? = 0 ] ; then $ECHO " sudo ln -s $_glti $_gltidir/libtoolize" $ECHO else $ECHO " ln -s $_glti $_gltidir/libtoolize" $ECHO $ECHO "Run that as root or with proper permissions to the $_gltidir directory" $ECHO fi _ltfound=yes break fi done else _ltfound=yes fi else _ltfound=yes $ECHO "Using LIBTOOLIZE environment variable override: $LIBTOOLIZE" fi ############################ # libtoolize version check # ############################ _report_error=no if [ ! "x$_ltfound" = "xyes" ] ; then $ECHO $ECHO "ERROR: Unable to locate GNU Libtool." _report_error=yes else _version="`$LIBTOOLIZE --version | head -${HEAD_N}1 | sed 's/[^0-9]*\([0-9\.][0-9\.]*\)/\1/'`" if [ "x$_version" = "x" ] ; then _version="0.0.0" fi $ECHO "Found GNU Libtool version $_version" version_check "$LIBTOOL_VERSION" "$_version" if [ $? -ne 0 ] ; then _report_error=yes fi fi if [ "x$_report_error" = "xyes" ] ; then version_error "$LIBTOOL_VERSION" "GNU Libtool" exit 1 fi ##################### # check for aclocal # ##################### if [ "x$ACLOCAL" = "x" ] ; then for ACLOCAL in aclocal ; do $VERBOSE_ECHO "Checking aclocal version: $ACLOCAL --version" $ACLOCAL --version > /dev/null 2>&1 if [ $? = 0 ] ; then break fi done else $ECHO "Using ACLOCAL environment variable override: $ACLOCAL" fi ######################## # check for autoheader # ######################## if [ "x$AUTOHEADER" = "x" ] ; then for AUTOHEADER in autoheader ; do $VERBOSE_ECHO "Checking autoheader version: $AUTOHEADER --version" $AUTOHEADER --version > /dev/null 2>&1 if [ $? = 0 ] ; then break fi done else $ECHO "Using AUTOHEADER environment variable override: $AUTOHEADER" fi ######################### # check if version only # ######################### $VERBOSE_ECHO "Checking whether to only output version information" if [ "x$VERSION_ONLY" = "xyes" ] ; then $ECHO ident $ECHO "---" $ECHO "Version requested. No preparation or configuration will be performed." exit 0 fi ################################# # PROTECT_FROM_CLOBBER FUNCTION # ################################# protect_from_clobber ( ) { PFC_INIT=1 # protect COPYING & INSTALL from overwrite by automake. the # automake force option will (inappropriately) ignore the existing # contents of a COPYING and/or INSTALL files (depending on the # version) instead of just forcing *missing* files like it does # for AUTHORS, NEWS, and README. this is broken but extremely # prevalent behavior, so we protect against it by keeping a backup # of the file that can later be restored. for file in COPYING INSTALL ; do if test -f ${file} ; then if test -f ${file}.$$.protect_from_automake.backup ; then $VERBOSE_ECHO "Already backed up ${file} in `pwd`" else $VERBOSE_ECHO "Backing up ${file} in `pwd`" $VERBOSE_ECHO "cp -p ${file} ${file}.$$.protect_from_automake.backup" cp -p ${file} ${file}.$$.protect_from_automake.backup fi fi done } ############################## # RECURSIVE_PROTECT FUNCTION # ############################## recursive_protect ( ) { # for projects using recursive configure, run the build # preparation steps for the subdirectories. this function assumes # START_PATH was set to pwd before recursion begins so that # relative paths work. # git 'r done, protect COPYING and INSTALL from being clobbered protect_from_clobber if test -d autom4te.cache ; then $VERBOSE_ECHO "Found an autom4te.cache directory, deleting it" $VERBOSE_ECHO "rm -rf autom4te.cache" rm -rf autom4te.cache fi # find configure template _configure="`locate_configure_template`" if [ "x$_configure" = "x" ] ; then return fi # $VERBOSE_ECHO "Looking for configure template found `pwd`/$_configure" # look for subdirs # $VERBOSE_ECHO "Looking for subdirs in `pwd`" _det_config_subdirs="`grep AC_CONFIG_SUBDIRS $_configure | grep -v '.*#.*AC_CONFIG_SUBDIRS' | sed 's/^[ ]*AC_CONFIG_SUBDIRS(\(.*\)).*/\1/' | sed 's/.*\[\(.*\)\].*/\1/'`" CHECK_DIRS="" for dir in $_det_config_subdirs ; do if test -d "`pwd`/$dir" ; then CHECK_DIRS="$CHECK_DIRS \"`pwd`/$dir\"" fi done # process subdirs if [ ! "x$CHECK_DIRS" = "x" ] ; then $VERBOSE_ECHO "Recursively scanning the following directories:" $VERBOSE_ECHO " $CHECK_DIRS" for dir in $CHECK_DIRS ; do $VERBOSE_ECHO "Protecting files from automake in $dir" cd "$START_PATH" eval "cd $dir" # recursively git 'r done recursive_protect done fi } # end of recursive_protect ############################# # RESTORE_CLOBBERED FUNCION # ############################# restore_clobbered ( ) { # The automake (and autoreconf by extension) -f/--force-missing # option may overwrite COPYING and INSTALL even if they do exist. # Here we restore the files if necessary. spacer=no for file in COPYING INSTALL ; do if test -f ${file}.$$.protect_from_automake.backup ; then if test -f ${file} ; then # compare entire content, restore if needed if test "x`cat ${file}`" != "x`cat ${file}.$$.protect_from_automake.backup`" ; then if test "x$spacer" = "xno" ; then $VERBOSE_ECHO spacer=yes fi # restore the backup $VERBOSE_ECHO "Restoring ${file} from backup (automake -f likely clobbered it)" $VERBOSE_ECHO "rm -f ${file}" rm -f ${file} $VERBOSE_ECHO "mv ${file}.$$.protect_from_automake.backup ${file}" mv ${file}.$$.protect_from_automake.backup ${file} fi # check contents elif test -f ${file}.$$.protect_from_automake.backup ; then $VERBOSE_ECHO "mv ${file}.$$.protect_from_automake.backup ${file}" mv ${file}.$$.protect_from_automake.backup ${file} fi # -f ${file} # just in case $VERBOSE_ECHO "rm -f ${file}.$$.protect_from_automake.backup" rm -f ${file}.$$.protect_from_automake.backup fi # -f ${file}.$$.protect_from_automake.backup done CONFIGURE="`locate_configure_template`" if [ "x$CONFIGURE" = "x" ] ; then return fi _aux_dir="`grep AC_CONFIG_AUX_DIR $CONFIGURE | grep -v '.*#.*AC_CONFIG_AUX_DIR' | tail -${TAIL_N}1 | sed 's/^[ ]*AC_CONFIG_AUX_DIR(\(.*\)).*/\1/' | sed 's/.*\[\(.*\)\].*/\1/'`" if test ! -d "$_aux_dir" ; then _aux_dir=. fi for file in config.guess config.sub ltmain.sh ; do if test -f "${_aux_dir}/${file}" ; then $VERBOSE_ECHO "rm -f \"${_aux_dir}/${file}.backup\"" rm -f "${_aux_dir}/${file}.backup" fi done } # end of restore_clobbered ############################## # RECURSIVE_RESTORE FUNCTION # ############################## recursive_restore ( ) { # restore COPYING and INSTALL from backup if they were clobbered # for each directory recursively. # git 'r undone restore_clobbered # find configure template _configure="`locate_configure_template`" if [ "x$_configure" = "x" ] ; then return fi # look for subdirs _det_config_subdirs="`grep AC_CONFIG_SUBDIRS $_configure | grep -v '.*#.*AC_CONFIG_SUBDIRS' | sed 's/^[ ]*AC_CONFIG_SUBDIRS(\(.*\)).*/\1/' | sed 's/.*\[\(.*\)\].*/\1/'`" CHECK_DIRS="" for dir in $_det_config_subdirs ; do if test -d "`pwd`/$dir" ; then CHECK_DIRS="$CHECK_DIRS \"`pwd`/$dir\"" fi done # process subdirs if [ ! "x$CHECK_DIRS" = "x" ] ; then $VERBOSE_ECHO "Recursively scanning the following directories:" $VERBOSE_ECHO " $CHECK_DIRS" for dir in $CHECK_DIRS ; do $VERBOSE_ECHO "Checking files for automake damage in $dir" cd "$START_PATH" eval "cd $dir" # recursively git 'r undone recursive_restore done fi } # end of recursive_restore ####################### # INITIALIZE FUNCTION # ####################### initialize ( ) { # this routine performs a variety of directory-specific # initializations. some are sanity checks, some are preventive, # and some are necessary setup detection. # # this function sets: # CONFIGURE # SEARCH_DIRS # CONFIG_SUBDIRS ################################## # check for a configure template # ################################## CONFIGURE="`locate_configure_template`" if [ "x$CONFIGURE" = "x" ] ; then $ECHO $ECHO "A configure.ac or configure.in file could not be located implying" $ECHO "that the GNU Build System is at least not used in this directory. In" $ECHO "any case, there is nothing to do here without one of those files." $ECHO $ECHO "ERROR: No configure.in or configure.ac file found in `pwd`" exit 1 fi ##################### # detect an aux dir # ##################### _aux_dir="`grep AC_CONFIG_AUX_DIR $CONFIGURE | grep -v '.*#.*AC_CONFIG_AUX_DIR' | tail -${TAIL_N}1 | sed 's/^[ ]*AC_CONFIG_AUX_DIR(\(.*\)).*/\1/' | sed 's/.*\[\(.*\)\].*/\1/'`" if test ! -d "$_aux_dir" ; then _aux_dir=. else $VERBOSE_ECHO "Detected auxillary directory: $_aux_dir" fi ################################ # detect a recursive configure # ################################ CONFIG_SUBDIRS="" _det_config_subdirs="`grep AC_CONFIG_SUBDIRS $CONFIGURE | grep -v '.*#.*AC_CONFIG_SUBDIRS' | sed 's/^[ ]*AC_CONFIG_SUBDIRS(\(.*\)).*/\1/' | sed 's/.*\[\(.*\)\].*/\1/'`" for dir in $_det_config_subdirs ; do if test -d "`pwd`/$dir" ; then $VERBOSE_ECHO "Detected recursive configure directory: `pwd`/$dir" CONFIG_SUBDIRS="$CONFIG_SUBDIRS `pwd`/$dir" fi done ########################################################### # make sure certain required files exist for GNU projects # ########################################################### _marker_found="" _marker_found_message_intro='Detected non-GNU marker "' _marker_found_message_mid='" in ' for marker in foreign cygnus ; do _marker_found_message=${_marker_found_message_intro}${marker}${_marker_found_message_mid} _marker_found="`grep 'AM_INIT_AUTOMAKE.*'${marker} $CONFIGURE`" if [ ! "x$_marker_found" = "x" ] ; then $VERBOSE_ECHO "${_marker_found_message}`basename \"$CONFIGURE\"`" break fi if test -f "`dirname \"$CONFIGURE\"/Makefile.am`" ; then _marker_found="`grep 'AUTOMAKE_OPTIONS.*'${marker} Makefile.am`" if [ ! "x$_marker_found" = "x" ] ; then $VERBOSE_ECHO "${_marker_found_message}Makefile.am" break fi fi done if [ "x${_marker_found}" = "x" ] ; then _suggest_foreign=no for file in AUTHORS COPYING ChangeLog INSTALL NEWS README ; do if [ ! -f $file ] ; then $VERBOSE_ECHO "Touching ${file} since it does not exist" _suggest_foreign=yes touch $file fi done if [ "x${_suggest_foreign}" = "xyes" ] ; then $ECHO $ECHO "Warning: Several files expected of projects that conform to the GNU" $ECHO "coding standards were not found. The files were automatically added" $ECHO "for you since you do not have a 'foreign' declaration specified." $ECHO $ECHO "Considered adding 'foreign' to AM_INIT_AUTOMAKE in `basename \"$CONFIGURE\"`" if test -f "`dirname \"$CONFIGURE\"/Makefile.am`" ; then $ECHO "or to AUTOMAKE_OPTIONS in your top-level Makefile.am file." fi $ECHO fi fi ################################################## # make sure certain generated files do not exist # ################################################## for file in config.guess config.sub ltmain.sh ; do if test -f "${_aux_dir}/${file}" ; then $VERBOSE_ECHO "mv -f \"${_aux_dir}/${file}\" \"${_aux_dir}/${file}.backup\"" mv -f "${_aux_dir}/${file}" "${_aux_dir}/${file}.backup" fi done ############################ # search alternate m4 dirs # ############################ SEARCH_DIRS="" for dir in m4 ; do if [ -d $dir ] ; then $VERBOSE_ECHO "Found extra aclocal search directory: $dir" SEARCH_DIRS="$SEARCH_DIRS -I $dir" fi done ###################################### # remove any previous build products # ###################################### if test -d autom4te.cache ; then $VERBOSE_ECHO "Found an autom4te.cache directory, deleting it" $VERBOSE_ECHO "rm -rf autom4te.cache" rm -rf autom4te.cache fi # tcl/tk (and probably others) have a customized aclocal.m4, so can't delete it # if test -f aclocal.m4 ; then # $VERBOSE_ECHO "Found an aclocal.m4 file, deleting it" # $VERBOSE_ECHO "rm -f aclocal.m4" # rm -f aclocal.m4 # fi } # end of initialize() ############## # initialize # ############## # stash path START_PATH="`pwd`" # Before running autoreconf or manual steps, some prep detection work # is necessary or useful. Only needs to occur once per directory, but # does need to traverse the entire subconfigure hierarchy to protect # files from being clobbered even by autoreconf. recursive_protect # start from where we started cd "$START_PATH" # get ready to process initialize ######################################### # DOWNLOAD_GNULIB_CONFIG_GUESS FUNCTION # ######################################### # TODO - should make sure wget/curl exist and/or work before trying to # use them. download_gnulib_config_guess () { # abuse gitweb to download gnulib's latest config.guess via HTTP config_guess_temp="config.guess.$$.download" ret=1 for __cmd in wget curl fetch ; do $VERBOSE_ECHO "Checking for command ${__cmd}" ${__cmd} --version > /dev/null 2>&1 ret=$? if [ ! $ret = 0 ] ; then continue fi __cmd_version=`${__cmd} --version | head -n 1 | sed -e 's/^[^0-9]\+//' -e 's/ .*//'` $VERBOSE_ECHO "Found ${__cmd} ${__cmd_version}" opts="" case ${__cmd} in wget) opts="-O" ;; curl) opts="-o" ;; fetch) opts="-t 5 -f" ;; esac $VERBOSE_ECHO "Running $__cmd \"${CONFIG_GUESS_URL}\" $opts \"${config_guess_temp}\"" eval "$__cmd \"${CONFIG_GUESS_URL}\" $opts \"${config_guess_temp}\"" > /dev/null 2>&1 if [ $? = 0 ] ; then mv -f "${config_guess_temp}" ${_aux_dir}/config.guess ret=0 break fi done if [ ! $ret = 0 ] ; then $ECHO "Warning: config.guess download failed from: $CONFIG_GUESS_URL" rm -f "${config_guess_temp}" fi } ############################## # LIBTOOLIZE_NEEDED FUNCTION # ############################## libtoolize_needed () { ret=1 # means no, don't need libtoolize for feature in AC_PROG_LIBTOOL AM_PROG_LIBTOOL LT_INIT ; do $VERBOSE_ECHO "Searching for $feature in $CONFIGURE" found="`grep \"^$feature.*\" $CONFIGURE`" if [ ! "x$found" = "x" ] ; then ret=0 # means yes, need to run libtoolize break fi done return ${ret} } ############################################ # prepare build via autoreconf or manually # ############################################ reconfigure_manually=no if [ "x$HAVE_AUTORECONF" = "xyes" ] ; then $ECHO $ECHO $ECHO_N "Automatically preparing build ... $ECHO_C" $VERBOSE_ECHO "$AUTORECONF $SEARCH_DIRS $AUTORECONF_OPTIONS" autoreconf_output="`$AUTORECONF $SEARCH_DIRS $AUTORECONF_OPTIONS 2>&1`" ret=$? $VERBOSE_ECHO "$autoreconf_output" if [ ! $ret = 0 ] ; then if [ "x$HAVE_ALT_LIBTOOLIZE" = "xyes" ] ; then if [ ! "x`echo \"$autoreconf_output\" | grep libtoolize | grep \"No such file or directory\"`" = "x" ] ; then $ECHO $ECHO "Warning: autoreconf failed but due to what is usually a common libtool" $ECHO "misconfiguration issue. This problem is encountered on systems that" $ECHO "have installed libtoolize under a different name without providing a" $ECHO "symbolic link or without setting the LIBTOOLIZE environment variable." $ECHO $ECHO "Restarting the preparation steps with LIBTOOLIZE set to $LIBTOOLIZE" export LIBTOOLIZE RUN_RECURSIVE=no export RUN_RECURSIVE untrap_abnormal $VERBOSE_ECHO sh $AUTOGEN_SH "$1" "$2" "$3" "$4" "$5" "$6" "$7" "$8" "$9" sh "$AUTOGEN_SH" "$1" "$2" "$3" "$4" "$5" "$6" "$7" "$8" "$9" exit $? fi fi $ECHO "Warning: $AUTORECONF failed" if test -f ltmain.sh ; then $ECHO "libtoolize being run by autoreconf is not creating ltmain.sh in the auxillary directory like it should" fi $ECHO "Attempting to run the preparation steps individually" reconfigure_manually=yes else if [ "x$DOWNLOAD" = "xyes" ] ; then if libtoolize_needed ; then download_gnulib_config_guess fi fi fi else reconfigure_manually=yes fi ############################ # LIBTOOL_FAILURE FUNCTION # ############################ libtool_failure ( ) { # libtool is rather error-prone in comparison to the other # autotools and this routine attempts to compensate for some # common failures. the output after a libtoolize failure is # parsed for an error related to AC_PROG_LIBTOOL and if found, we # attempt to inject a project-provided libtool.m4 file. _autoconf_output="$1" if [ "x$RUN_RECURSIVE" = "xno" ] ; then # we already tried the libtool.m4, don't try again return 1 fi if test -f "$LIBTOOL_M4" ; then found_libtool="`$ECHO $_autoconf_output | grep AC_PROG_LIBTOOL`" if test ! "x$found_libtool" = "x" ; then if test -f acinclude.m4 ; then rm -f acinclude.m4.$$.backup $VERBOSE_ECHO "cat acinclude.m4 > acinclude.m4.$$.backup" cat acinclude.m4 > acinclude.m4.$$.backup fi $VERBOSE_ECHO "cat \"$LIBTOOL_M4\" >> acinclude.m4" chmod u+w acinclude.m4 cat "$LIBTOOL_M4" >> acinclude.m4 # don't keep doing this RUN_RECURSIVE=no export RUN_RECURSIVE untrap_abnormal $ECHO $ECHO "Restarting the preparation steps with libtool macros in acinclude.m4" $VERBOSE_ECHO sh $AUTOGEN_SH "$1" "$2" "$3" "$4" "$5" "$6" "$7" "$8" "$9" sh "$AUTOGEN_SH" "$1" "$2" "$3" "$4" "$5" "$6" "$7" "$8" "$9" exit $? fi fi } ########################### # MANUAL_AUTOGEN FUNCTION # ########################### manual_autogen ( ) { ################################################## # Manual preparation steps taken are as follows: # # aclocal [-I m4] # # libtoolize --automake -c -f # # aclocal [-I m4] # # autoconf -f # # autoheader # # automake -a -c -f # ################################################## ########### # aclocal # ########### $VERBOSE_ECHO "$ACLOCAL $SEARCH_DIRS $ACLOCAL_OPTIONS" aclocal_output="`$ACLOCAL $SEARCH_DIRS $ACLOCAL_OPTIONS 2>&1`" ret=$? $VERBOSE_ECHO "$aclocal_output" if [ ! $ret = 0 ] ; then $ECHO "ERROR: $ACLOCAL failed" && exit 2 ; fi ############## # libtoolize # ############## if libtoolize_needed ; then if [ "x$HAVE_LIBTOOLIZE" = "xyes" ] ; then $VERBOSE_ECHO "$LIBTOOLIZE $LIBTOOLIZE_OPTIONS" libtoolize_output="`$LIBTOOLIZE $LIBTOOLIZE_OPTIONS 2>&1`" ret=$? $VERBOSE_ECHO "$libtoolize_output" if [ ! $ret = 0 ] ; then $ECHO "ERROR: $LIBTOOLIZE failed" && exit 2 ; fi else if [ "x$HAVE_ALT_LIBTOOLIZE" = "xyes" ] ; then $VERBOSE_ECHO "$LIBTOOLIZE $ALT_LIBTOOLIZE_OPTIONS" libtoolize_output="`$LIBTOOLIZE $ALT_LIBTOOLIZE_OPTIONS 2>&1`" ret=$? $VERBOSE_ECHO "$libtoolize_output" if [ ! $ret = 0 ] ; then $ECHO "ERROR: $LIBTOOLIZE failed" && exit 2 ; fi fi fi ########### # aclocal # ########### # re-run again as instructed by libtoolize $VERBOSE_ECHO "$ACLOCAL $SEARCH_DIRS $ACLOCAL_OPTIONS" aclocal_output="`$ACLOCAL $SEARCH_DIRS $ACLOCAL_OPTIONS 2>&1`" ret=$? $VERBOSE_ECHO "$aclocal_output" # libtoolize might put ltmain.sh in the wrong place if test -f ltmain.sh ; then if test ! -f "${_aux_dir}/ltmain.sh" ; then $ECHO $ECHO "Warning: $LIBTOOLIZE is creating ltmain.sh in the wrong directory" $ECHO $ECHO "Fortunately, the problem can be worked around by simply copying the" $ECHO "file to the appropriate location (${_aux_dir}/). This has been done for you." $ECHO $VERBOSE_ECHO "cp -p ltmain.sh \"${_aux_dir}/ltmain.sh\"" cp -p ltmain.sh "${_aux_dir}/ltmain.sh" $ECHO $ECHO_N "Continuing build preparation ... $ECHO_C" fi fi # ltmain.sh if [ "x$DOWNLOAD" = "xyes" ] ; then download_gnulib_config_guess fi fi # libtoolize_needed ############ # autoconf # ############ $VERBOSE_ECHO $VERBOSE_ECHO "$AUTOCONF $AUTOCONF_OPTIONS" autoconf_output="`$AUTOCONF $AUTOCONF_OPTIONS 2>&1`" ret=$? $VERBOSE_ECHO "$autoconf_output" if [ ! $ret = 0 ] ; then # retry without the -f and check for usage of macros that are too new ac2_59_macros="AC_C_RESTRICT AC_INCLUDES_DEFAULT AC_LANG_ASSERT AC_LANG_WERROR AS_SET_CATFILE" ac2_55_macros="AC_COMPILER_IFELSE AC_FUNC_MBRTOWC AC_HEADER_STDBOOL AC_LANG_CONFTEST AC_LANG_SOURCE AC_LANG_PROGRAM AC_LANG_CALL AC_LANG_FUNC_TRY_LINK AC_MSG_FAILURE AC_PREPROC_IFELSE" ac2_54_macros="AC_C_BACKSLASH_A AC_CONFIG_LIBOBJ_DIR AC_GNU_SOURCE AC_PROG_EGREP AC_PROG_FGREP AC_REPLACE_FNMATCH AC_FUNC_FNMATCH_GNU AC_FUNC_REALLOC AC_TYPE_MBSTATE_T" macros_to_search="" ac_major="`echo ${AUTOCONF_VERSION}. | cut -d. -f1 | sed 's/[^0-9]//g'`" ac_minor="`echo ${AUTOCONF_VERSION}. | cut -d. -f2 | sed 's/[^0-9]//g'`" if [ $ac_major -lt 2 ] ; then macros_to_search="$ac2_59_macros $ac2_55_macros $ac2_54_macros" else if [ $ac_minor -lt 54 ] ; then macros_to_search="$ac2_59_macros $ac2_55_macros $ac2_54_macros" elif [ $ac_minor -lt 55 ] ; then macros_to_search="$ac2_59_macros $ac2_55_macros" elif [ $ac_minor -lt 59 ] ; then macros_to_search="$ac2_59_macros" fi fi configure_ac_macros=__none__ for feature in $macros_to_search ; do $VERBOSE_ECHO "Searching for $feature in $CONFIGURE" found="`grep \"^$feature.*\" $CONFIGURE`" if [ ! "x$found" = "x" ] ; then if [ "x$configure_ac_macros" = "x__none__" ] ; then configure_ac_macros="$feature" else configure_ac_macros="$feature $configure_ac_macros" fi fi done if [ ! "x$configure_ac_macros" = "x__none__" ] ; then $ECHO $ECHO "Warning: Unsupported macros were found in $CONFIGURE" $ECHO $ECHO "The `basename \"$CONFIGURE\"` file was scanned in order to determine if any" $ECHO "unsupported macros are used that exceed the minimum version" $ECHO "settings specified within this file. As such, the following macros" $ECHO "should be removed from configure.ac or the version numbers in this" $ECHO "file should be increased:" $ECHO $ECHO "$configure_ac_macros" $ECHO $ECHO $ECHO_N "Ignorantly continuing build preparation ... $ECHO_C" fi ################### # autoconf, retry # ################### $VERBOSE_ECHO $VERBOSE_ECHO "$AUTOCONF" autoconf_output="`$AUTOCONF 2>&1`" ret=$? $VERBOSE_ECHO "$autoconf_output" if [ ! $ret = 0 ] ; then # test if libtool is busted libtool_failure "$autoconf_output" # let the user know what went wrong cat <"]) fi case "$target" in linux) os=linux ;; windows) os=windows ;; osx) os=osx ;; *) AC_MSG_ERROR(["Unrecognised target OS: $target"]) ;; esac AM_CONDITIONAL(LINUX, test "x$os" = "xlinux") AM_CONDITIONAL(WINDOWS, test "x$os" = "xwindows") AM_CONDITIONAL(OSX, test "x$os" = "xosx") # ------------------------------------------------------------------------------ # --enable-vst. VST compilation is disabled by default # # WITH_VST, if present, will be passed to gcc as -DWITH_VST # # AC_ARG_ENABLE ( # feature, [--enable-] + [feature], eg --enable-vst # help-string, # [action-if-given], == gcc ... -DWITH_VST # [action-if-not-given]) not used here AC_ARG_ENABLE( [vst], AS_HELP_STRING([--enable-vst], [enable vst support]), [AC_DEFINE(WITH_VST) AM_CONDITIONAL(WITH_VST, true)], [AM_CONDITIONAL(WITH_VST, false)] ) # ------------------------------------------------------------------------------ # --debug. Enable debug compilation AC_ARG_ENABLE( [debug], AS_HELP_STRING([--enable-debug], [enable debug mode (asserts, ...)]), [], [AC_DEFINE(NDEBUG)] ) # ------------------------------------------------------------------------------ # test if files needed for Travis CI are present. If so, define a new macro # RUN_TESTS_WITH_LOCAL_FILES used during the test suite if test -f "giada-midimaps-master.zip" && test -f "dexed.tar.xz" ; then AC_DEFINE(RUN_TESTS_WITH_LOCAL_FILES) fi # ------------------------------------------------------------------------------ # Check for C++ compiler AC_PROG_CXX # Check for Objective-C++ compiler AC_PROG_OBJCXX # Check for C compiler (TODO - is that really needed?) AC_PROG_CC # Check for make AC_PROG_MAKE_SET # ------------------------------------------------------------------------------ # Check for libraries. AC_CHECK_LIB( [pthread], [pthread_exit], [], [AC_MSG_ERROR([error: library 'pthread' not found!])] ) # ------------------------------------------------------------------------------ # Check for generic headers (fltk, rtaudio and libsndfile are static, # we ask if headers are available) AC_LANG_PUSH([C++]) AC_CHECK_HEADER( [FL/Fl.H], [], [AC_MSG_ERROR([library 'fltk' not found!])] ) AC_LANG_POP if test "x$os" = "xosx"; then AC_LANG_PUSH([C++]) AC_CHECK_HEADER( [RtMidi.h], [], [AC_MSG_ERROR([library 'rtMidi' not found!])] ) AC_LANG_POP else AC_LANG_PUSH([C++]) AC_CHECK_HEADER( [rtmidi/RtMidi.h], [], [AC_MSG_ERROR([library 'rtMidi' not found!])] ) AC_LANG_POP fi AC_LANG_PUSH([C++]) AC_CHECK_HEADER( [jansson.h], [], [AC_MSG_ERROR([library 'Jansson' not found!])] ) AC_LANG_POP AC_LANG_PUSH([C++]) AC_CHECK_HEADER( [sndfile.h], [], [AC_MSG_ERROR([library 'libsndfile' not found!])] ) AC_LANG_POP #~ AC_LANG_PUSH([C++]) #~ AC_CHECK_HEADER( #~ [RtAudio.h], #~ [], #~ [AC_MSG_ERROR([library 'RtAudio' not found!])] #~ ) #~ AC_LANG_POP AC_LANG_PUSH([C++]) AC_CHECK_HEADER( [samplerate.h], [], [AC_MSG_ERROR([library 'samplerate' not found!])] ) AC_LANG_POP # ------------------------------------------------------------------------------ # Check for linux header files. if test "x$os" = "xlinux"; then AC_LANG_PUSH([C++]) AC_CHECK_HEADER( [X11/xpm.h], [], [AC_MSG_ERROR([missing xpm.h, maybe you need to install the libxpm-dev package?])] ) AC_LANG_POP fi # ------------------------------------------------------------------------------ # finalizing AC_CONFIG_FILES([Makefile]) AC_OUTPUT giada-0.14.5/src/000077500000000000000000000000001322662744500134505ustar00rootroot00000000000000giada-0.14.5/src/core/000077500000000000000000000000001322662744500144005ustar00rootroot00000000000000giada-0.14.5/src/core/.dirstamp000066400000000000000000000000001322662744500162120ustar00rootroot00000000000000giada-0.14.5/src/core/channel.cpp000066400000000000000000000275401322662744500165240ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include #include #include "../utils/log.h" #include "../gui/elems/mainWindow/keyboard/channel.h" #include "const.h" #include "channel.h" #include "pluginHost.h" #include "plugin.h" #include "kernelMidi.h" #include "patch.h" #include "clock.h" #include "wave.h" #include "mixer.h" #include "mixerHandler.h" #include "conf.h" #include "patch.h" #include "waveFx.h" #include "midiMapConf.h" using std::string; using namespace giada::m; Channel::Channel(int type, int status, int bufferSize) : bufferSize (bufferSize), midiInFilter (-1), previewMode (G_PREVIEW_NONE), pan (0.5f), armed (false), type (type), status (status), key (0), volume (G_DEFAULT_VOL), volume_i (1.0f), volume_d (0.0f), mute_i (false), mute_s (false), mute (false), solo (false), hasActions (false), readActions (false), recStatus (REC_STOPPED), vChan (nullptr), guiChannel (nullptr), midiIn (true), midiInKeyPress (0x0), midiInKeyRel (0x0), midiInKill (0x0), midiInArm (0x0), midiInVolume (0x0), midiInMute (0x0), midiInSolo (0x0), midiOutL (false), midiOutLplaying(0x0), midiOutLmute (0x0), midiOutLsolo (0x0) { } /* -------------------------------------------------------------------------- */ Channel::~Channel() { status = STATUS_OFF; if (vChan != nullptr) delete[] vChan; } /* -------------------------------------------------------------------------- */ bool Channel::allocBuffers() { vChan = new (std::nothrow) float[bufferSize]; if (vChan == nullptr) { gu_log("[Channel::allocBuffers] unable to alloc memory for vChan!\n"); return false; } std::memset(vChan, 0, bufferSize * sizeof(float)); return true; } /* -------------------------------------------------------------------------- */ void Channel::copy(const Channel *src, pthread_mutex_t *pluginMutex) { key = src->key; volume = src->volume; volume_i = src->volume_i; volume_d = src->volume_d; pan = src->pan; mute_i = src->mute_i; mute_s = src->mute_s; mute = src->mute; solo = src->solo; hasActions = src->hasActions; recStatus = src->recStatus; midiIn = src->midiIn; midiInKeyPress = src->midiInKeyPress; midiInKeyRel = src->midiInKeyRel; midiInKill = src->midiInKill; midiInArm = src->midiInArm; midiInVolume = src->midiInVolume; midiInMute = src->midiInMute; midiInSolo = src->midiInSolo; midiOutL = src->midiOutL; midiOutLplaying = src->midiOutLplaying; midiOutLmute = src->midiOutLmute; midiOutLsolo = src->midiOutLsolo; /* clone plugins */ #ifdef WITH_VST for (unsigned i=0; iplugins.size(); i++) pluginHost::clonePlugin(src->plugins.at(i), pluginHost::CHANNEL, pluginMutex, this); #endif /* clone actions */ for (unsigned i=0; ichan == src->index) { recorder::rec(index, a->type, a->frame, a->iValue, a->fValue); hasActions = true; } } } } /* -------------------------------------------------------------------------- */ void Channel::sendMidiLmessage(uint32_t learn, const midimap::message_t& msg) { gu_log("[channel::sendMidiLmessage] learn=%#X, chan=%d, msg=%#X, offset=%d\n", learn, msg.channel, msg.value, msg.offset); /* isolate 'channel' from learnt message and offset it as requested by 'nn' * in the midimap configuration file. */ uint32_t out = ((learn & 0x00FF0000) >> 16) << msg.offset; /* merge the previously prepared channel into final message, and finally * send it. */ out |= msg.value | (msg.channel << 24); kernelMidi::send(out); } /* -------------------------------------------------------------------------- */ bool Channel::isPlaying() const { return status & (STATUS_PLAY | STATUS_ENDING); } /* -------------------------------------------------------------------------- */ int Channel::writePatch(int i, bool isProject) { patch::channel_t pch; pch.type = type; pch.index = index; pch.size = guiChannel->getSize(); pch.name = name; pch.key = key; pch.armed = armed; pch.column = guiChannel->getColumnIndex(); pch.mute = mute; pch.mute_s = mute_s; pch.solo = solo; pch.volume = volume; pch.pan = pan; pch.midiIn = midiIn; pch.midiInKeyPress = midiInKeyPress; pch.midiInKeyRel = midiInKeyRel; pch.midiInKill = midiInKill; pch.midiInArm = midiInArm; pch.midiInVolume = midiInVolume; pch.midiInMute = midiInMute; pch.midiInFilter = midiInFilter; pch.midiInSolo = midiInSolo; pch.midiOutL = midiOutL; pch.midiOutLplaying = midiOutLplaying; pch.midiOutLmute = midiOutLmute; pch.midiOutLsolo = midiOutLsolo; for (unsigned i=0; ichan == index) { patch::action_t pac; pac.type = action->type; pac.frame = action->frame; pac.fValue = action->fValue; pac.iValue = action->iValue; pch.actions.push_back(pac); } } } #ifdef WITH_VST unsigned numPlugs = pluginHost::countPlugins(pluginHost::CHANNEL, this); for (unsigned i=0; igetUniqueId(); pp.bypass = pPlugin->isBypassed(); for (int k=0; kgetNumParameters(); k++) pp.params.push_back(pPlugin->getParameter(k)); for (unsigned k=0; kmidiInParams.size(); k++) pp.midiInParams.push_back(pPlugin->midiInParams.at(k)); pch.plugins.push_back(pp); } #endif patch::channels.push_back(pch); return patch::channels.size() - 1; } /* -------------------------------------------------------------------------- */ int Channel::readPatch(const string& path, int i, pthread_mutex_t* pluginMutex, int samplerate, int rsmpQuality) { int ret = 1; patch::channel_t* pch = &patch::channels.at(i); key = pch->key; armed = pch->armed; type = pch->type; name = pch->name; index = pch->index; mute = pch->mute; mute_s = pch->mute_s; solo = pch->solo; volume = pch->volume; pan = pch->pan; midiIn = pch->midiIn; midiInKeyPress = pch->midiInKeyPress; midiInKeyRel = pch->midiInKeyRel; midiInKill = pch->midiInKill; midiInVolume = pch->midiInVolume; midiInMute = pch->midiInMute; midiInFilter = pch->midiInFilter; midiInSolo = pch->midiInSolo; midiOutL = pch->midiOutL; midiOutLplaying = pch->midiOutLplaying; midiOutLmute = pch->midiOutLmute; midiOutLsolo = pch->midiOutLsolo; for (const patch::action_t& ac : pch->actions) { recorder::rec(index, ac.type, ac.frame, ac.iValue, ac.fValue); hasActions = true; } #ifdef WITH_VST for (const patch::plugin_t& ppl : pch->plugins) { Plugin* plugin = pluginHost::addPlugin(ppl.path, pluginHost::CHANNEL, pluginMutex, this); if (plugin == nullptr) { ret &= 0; continue; } plugin->setBypass(ppl.bypass); for (unsigned j=0; jsetParameter(j, ppl.params.at(j)); /* Don't fill Channel::midiInParam if Patch::midiInParams are 0: it would wipe out the current default 0x0 values. */ if (!ppl.midiInParams.empty()) { plugin->midiInParams.clear(); for (uint32_t midiInParam : ppl.midiInParams) plugin->midiInParams.push_back(midiInParam); } ret &= 1; } #endif return ret; } /* -------------------------------------------------------------------------- */ void Channel::sendMidiLmute() { if (!midiOutL || midiOutLmute == 0x0) return; if (mute) sendMidiLmessage(midiOutLsolo, midimap::muteOn); else sendMidiLmessage(midiOutLsolo, midimap::muteOff); } /* -------------------------------------------------------------------------- */ void Channel::sendMidiLsolo() { if (!midiOutL || midiOutLsolo == 0x0) return; if (solo) sendMidiLmessage(midiOutLsolo, midimap::soloOn); else sendMidiLmessage(midiOutLsolo, midimap::soloOff); } /* -------------------------------------------------------------------------- */ void Channel::sendMidiLplay() { if (!midiOutL || midiOutLplaying == 0x0) return; switch (status) { case STATUS_OFF: sendMidiLmessage(midiOutLplaying, midimap::stopped); break; case STATUS_PLAY: sendMidiLmessage(midiOutLplaying, midimap::playing); break; case STATUS_WAIT: sendMidiLmessage(midiOutLplaying, midimap::waiting); break; case STATUS_ENDING: sendMidiLmessage(midiOutLplaying, midimap::stopping); } } /* -------------------------------------------------------------------------- */ void Channel::receiveMidi(const MidiEvent& midiEvent) { } /* -------------------------------------------------------------------------- */ void Channel::setMidiInFilter(int c) { midiInFilter = c; } int Channel::getMidiInFilter() const { return midiInFilter; } bool Channel::isMidiInAllowed(int c) const { return midiInFilter == -1 || midiInFilter == c; } /* -------------------------------------------------------------------------- */ void Channel::setPan(float v) { if (v > 1.0f) pan = 1.0f; else if (v < 0.0f) pan = 0.0f; else pan = v; } float Channel::getPan() const { return pan; } /* -------------------------------------------------------------------------- */ float Channel::calcPanning(int ch) { if (pan == 0.5f) // center: nothing to do return 1.0; if (ch == 0) return 1.0 - pan; else // channel 1 return pan; } /* -------------------------------------------------------------------------- */ void Channel::setPreviewMode(int m) { previewMode = m; } bool Channel::isPreview() const { return previewMode != G_PREVIEW_NONE; } /* -------------------------------------------------------------------------- */ void Channel::setArmed(bool b) { armed = b; } bool Channel::isArmed() const { return armed; } /* -------------------------------------------------------------------------- */ std::string Channel::getName() const { return name; } void Channel::setName(const std::string& s) { name = s; } /* -------------------------------------------------------------------------- */ #ifdef WITH_VST juce::MidiBuffer &Channel::getPluginMidiEvents() { return midiBuffer; } /* -------------------------------------------------------------------------- */ void Channel::clearMidiBuffer() { midiBuffer.clear(); } #endif giada-0.14.5/src/core/channel.h000066400000000000000000000200711322662744500161610ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef G_CHANNEL_H #define G_CHANNEL_H #include #include #include #include "midiMapConf.h" #include "midiEvent.h" #include "recorder.h" #ifdef WITH_VST #include "../deps/juce-config.h" #endif class Plugin; class MidiMapConf; class geChannel; class Channel { protected: /* sendMidiLMessage Composes a MIDI message by merging bytes from MidiMap conf class, and sends it to KernelMidi. */ void sendMidiLmessage(uint32_t learn, const giada::m::midimap::message_t& msg); /* calcPanning Given an audio channel (stereo: 0 or 1) computes the current panning value. */ float calcPanning(int ch); #ifdef WITH_VST /* MidiBuffer contains MIDI events. When ready, events are sent to each plugin in the channel. This is available for any kind of channel, but it makes sense only for MIDI channels. */ juce::MidiBuffer midiBuffer; #endif /* bufferSize Size of every buffer in this channel (vChan, pChan) */ int bufferSize; /* midiInFilter Which MIDI channel should be filtered out when receiving MIDI messages. -1 means 'all'. */ int midiInFilter; /* previewMode Whether the channel is in audio preview mode or not. */ int previewMode; float pan; bool armed; std::string name; public: Channel(int type, int status, int bufferSize); virtual ~Channel(); /* copy Makes a shallow copy (no vChan/pChan allocation) of another channel. */ virtual void copy(const Channel* src, pthread_mutex_t* pluginMutex) = 0; /* readPatch Fills channel with data from patch. */ virtual int readPatch(const std::string& basePath, int i, pthread_mutex_t* pluginMutex, int samplerate, int rsmpQuality); /* process Merges vChannels into buffer, plus plugin processing (if any). Warning: inBuffer might be nullptr if no input devices are available for recording. */ virtual void process(float* outBuffer, float* inBuffer) = 0; /* Preview Makes itself audibile for audio preview, such as Sample Editor or other tools. */ virtual void preview(float* outBuffer) = 0; /* start Action to do when channel starts. doQuantize = false (don't quantize) when Mixer is reading actions from Recorder. If isUserGenerated means that the channel has been started by a human key press and not a pre-recorded action. */ virtual void start(int frame, bool doQuantize, int quantize, bool mixerIsRunning, bool forceStart, bool isUserGenerated) = 0; /* stop What to do when channel is stopped normally (via key or MIDI). */ virtual void stop() = 0; /* kill What to do when channel stops abruptly. */ virtual void kill(int frame) = 0; /* mute What to do when channel is muted. If internal == true, set internal mute without altering main mute. */ virtual void setMute (bool internal) = 0; virtual void unsetMute(bool internal) = 0; /* empty Frees any associated resources (e.g. waveform for SAMPLE). */ virtual void empty() = 0; /* stopBySeq What to do when channel is stopped by sequencer. */ virtual void stopBySeq(bool chansStopOnSeqHalt) = 0; /* quantize Starts channel according to quantizer. Index = array index of mixer::channels, used by recorder. LocalFrame = frame within the current buffer. */ virtual void quantize(int index, int localFrame) = 0; /* onZero What to do when frame goes to zero, i.e. sequencer restart. */ virtual void onZero(int frame, bool recsStopOnChanHalt) = 0; /* onBar What to do when a bar has passed. */ virtual void onBar(int frame) = 0; /* parseAction Does something on a recorded action. Parameters: - action *a - action to parse - localFrame - frame number of the processed buffer - globalFrame - actual frame in Mixer */ // TODO - quantize is useless! virtual void parseAction(giada::m::recorder::action* a, int localFrame, int globalFrame, int quantize, bool mixerIsRunning) = 0; /* rewind Rewinds channel when rewind button is pressed. */ virtual void rewind() = 0; /* clear Clears all memory buffers. This is actually useful to sample channels only. */ virtual void clear() = 0; /* canInputRec Tells whether a channel can accept and handle input audio. Always false for Midi channels, true for Sample channels only if they don't contain a sample yet.*/ virtual bool canInputRec() = 0; /* writePatch Fills a patch with channel values. Returns the index of the last Patch::channel_t added. */ virtual int writePatch(int i, bool isProject); /* receiveMidi Receives and processes midi messages from external devices. */ virtual void receiveMidi(const giada::m::MidiEvent& midiEvent); /* allocBuffers Mandatory method to allocate memory for internal buffers. Call it after the object has been constructed. */ virtual bool allocBuffers(); bool isPlaying() const; float getPan() const; bool isArmed() const; std::string getName() const; bool isPreview() const; int getMidiInFilter() const; /* isMidiAllowed Given a MIDI channel 'c' tells whether this channel should be allowed to receive and process MIDI events on MIDI channel 'c'. */ bool isMidiInAllowed(int c) const; /* sendMidiL* * send MIDI lightning events to a physical device. */ void sendMidiLmute(); void sendMidiLsolo(); void sendMidiLplay(); void setPan(float v); void setArmed(bool b); void setName(const std::string& s); void setPreviewMode(int m); void setMidiInFilter(int c); #ifdef WITH_VST /* getPluginMidiEvents * Return a reference to midiBuffer stack. This is available for any kind of * channel, but it makes sense only for MIDI channels. */ juce::MidiBuffer& getPluginMidiEvents(); void clearMidiBuffer(); #endif int index; // unique id int type; // midi or sample int status; // status: see const.h int key; // keyboard button float volume; // global volume float volume_i; // internal volume float volume_d; // delta volume (for envelope) bool mute_i; // internal mute bool mute_s; // previous mute status after being solo'd bool mute; // global mute bool solo; bool hasActions; // has something recorded bool readActions; // read what's recorded int recStatus; // status of recordings (waiting, ending, ...) float* vChan; // virtual channel geChannel* guiChannel; // pointer to a gChannel object, part of the GUI // TODO - midi structs, please bool midiIn; // enable midi input uint32_t midiInKeyPress; uint32_t midiInKeyRel; uint32_t midiInKill; uint32_t midiInArm; uint32_t midiInVolume; uint32_t midiInMute; uint32_t midiInSolo; /* midiOutL* * Enable MIDI lightning output, plus a set of midi lighting event to be sent * to a device. Those events basically contains the MIDI channel, everything * else gets stripped out. */ bool midiOutL; uint32_t midiOutLplaying; uint32_t midiOutLmute; uint32_t midiOutLsolo; #ifdef WITH_VST std::vector plugins; #endif }; #endif giada-0.14.5/src/core/clock.cpp000066400000000000000000000230261322662744500162020ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include #include "../glue/transport.h" #include "../glue/main.h" #include "conf.h" #include "const.h" #include "kernelAudio.h" #include "kernelMidi.h" #include "clock.h" namespace giada { namespace m { namespace clock { namespace { bool running = false; float bpm = G_DEFAULT_BPM; int bars = G_DEFAULT_BARS; int beats = G_DEFAULT_BEATS; int quantize = G_DEFAULT_QUANTIZE; int quanto = 1; // quantizer step int framesPerBar = 0; // frames in one bar int framesPerBeat = 0; // frames in one beat int framesInSequencer = 0; // frames in the whole sequencer int totalFrames = 0; // frames in the selected range (e.g. 4/4) int currentFrame = 0; int currentBeat = 0; int midiTCrate = 0; // send MTC data every midiTCrate frames int midiTCframes = 0; int midiTCseconds = 0; int midiTCminutes = 0; int midiTChours = 0; #ifdef G_OS_LINUX kernelAudio::JackState jackStatePrev; #endif void updateQuanto() { if (quantize != 0) quanto = framesPerBeat / quantize; if (quanto % 2 != 0) quanto++; } }; // {anonymous} /* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */ void init(int sampleRate, float midiTCfps) { midiTCrate = (sampleRate / midiTCfps) * 2; // stereo values running = false; bpm = G_DEFAULT_BPM; bars = G_DEFAULT_BARS; beats = G_DEFAULT_BEATS; quantize = G_DEFAULT_QUANTIZE; updateFrameBars(); } /* -------------------------------------------------------------------------- */ bool isRunning() { return running; } bool quantoHasPassed() { return currentFrame % (quanto) == 0; } bool isOnBar() { /* A bar cannot occur at frame 0. That's the first beat. */ return currentFrame % framesPerBar == 0 && currentFrame != 0; } bool isOnBeat() { /* Skip frame 0: it is intended as 'first beat'. */ /* TODO - this is wrong! */ return currentFrame % framesPerBeat == 0 && currentFrame > 0; } bool isOnFirstBeat() { return currentFrame == 0; } void start() { running = true; if (conf::midiSync == MIDI_SYNC_CLOCK_M) { kernelMidi::send(MIDI_START, -1, -1); kernelMidi::send(MIDI_POSITION_PTR, 0, 0); } } void stop() { running = false; if (conf::midiSync == MIDI_SYNC_CLOCK_M) kernelMidi::send(MIDI_STOP, -1, -1); } void setBpm(float b) { if (b < G_MIN_BPM) b = G_MIN_BPM; bpm = b; updateFrameBars(); } void setBars(int newBars) { /* Bars cannot be greater than beats and must be a sub multiple of beats. If not, approximate to the nearest (and greater) value available. */ if (newBars > beats) bars = beats; else if (newBars <= 0) bars = 1; else if (beats % newBars != 0) { bars = newBars + (beats % newBars); if (beats % bars != 0) // it could be an odd value, let's check it (and avoid it) bars = bars - (beats % bars); } else bars = newBars; } void setBeats(int b) { if (b > G_MAX_BEATS) beats = G_MAX_BEATS; else if (b < 1) beats = 1; else beats = b; } void setQuantize(int q) { quantize = q; updateQuanto(); } /* -------------------------------------------------------------------------- */ void incrCurrentFrame() { currentFrame += 2; if (currentFrame > totalFrames) { currentFrame = 0; currentBeat = 0; } else if (isOnBeat()) currentBeat++; } void rewind() { currentFrame = 0; currentBeat = 0; sendMIDIrewind(); } /* -------------------------------------------------------------------------- */ void updateFrameBars() { /* seconds ....... total time of play (in seconds) of the whole * sequencer. 60 / bpm == how many seconds lasts one bpm * totalFrames ... loop length in frames, x2 because it's stereo * framesPerBar .. n. of frames within a bar * framesPerBeat . n. of frames within a beat * framesInSeq ... number of frames in the whole sequencer */ float seconds = (60.0f / bpm) * beats; totalFrames = conf::samplerate * seconds * 2; framesPerBar = totalFrames / bars; framesPerBeat = totalFrames / beats; framesInSequencer = framesPerBeat * G_MAX_BEATS; /* big troubles if frames are odd. */ if (totalFrames % 2 != 0) totalFrames--; if (framesPerBar % 2 != 0) framesPerBar--; if (framesPerBeat % 2 != 0) framesPerBeat--; updateQuanto(); } /* -------------------------------------------------------------------------- */ void sendMIDIsync() { /* TODO - only Master (_M) is implemented so far. */ if (conf::midiSync == MIDI_SYNC_CLOCK_M) { if (currentFrame % (framesPerBeat/24) == 0) kernelMidi::send(MIDI_CLOCK, -1, -1); return; } if (conf::midiSync == MIDI_SYNC_MTC_M) { /* check if a new timecode frame has passed. If so, send MIDI TC * quarter frames. 8 quarter frames, divided in two branches: * 1-4 and 5-8. We check timecode frame's parity: if even, send * range 1-4, if odd send 5-8. */ if (currentFrame % midiTCrate != 0) // no timecode frame passed return; /* frame low nibble * frame high nibble * seconds low nibble * seconds high nibble */ if (midiTCframes % 2 == 0) { kernelMidi::send(MIDI_MTC_QUARTER, (midiTCframes & 0x0F) | 0x00, -1); kernelMidi::send(MIDI_MTC_QUARTER, (midiTCframes >> 4) | 0x10, -1); kernelMidi::send(MIDI_MTC_QUARTER, (midiTCseconds & 0x0F) | 0x20, -1); kernelMidi::send(MIDI_MTC_QUARTER, (midiTCseconds >> 4) | 0x30, -1); } /* minutes low nibble * minutes high nibble * hours low nibble * hours high nibble SMPTE frame rate */ else { kernelMidi::send(MIDI_MTC_QUARTER, (midiTCminutes & 0x0F) | 0x40, -1); kernelMidi::send(MIDI_MTC_QUARTER, (midiTCminutes >> 4) | 0x50, -1); kernelMidi::send(MIDI_MTC_QUARTER, (midiTChours & 0x0F) | 0x60, -1); kernelMidi::send(MIDI_MTC_QUARTER, (midiTChours >> 4) | 0x70, -1); } midiTCframes++; /* check if total timecode frames are greater than timecode fps: * if so, a second has passed */ if (midiTCframes > conf::midiTCfps) { midiTCframes = 0; midiTCseconds++; if (midiTCseconds >= 60) { midiTCminutes++; midiTCseconds = 0; if (midiTCminutes >= 60) { midiTChours++; midiTCminutes = 0; } } //gu_log("%d:%d:%d:%d\n", midiTChours, midiTCminutes, midiTCseconds, midiTCframes); } } } /* -------------------------------------------------------------------------- */ void sendMIDIrewind() { midiTCframes = 0; midiTCseconds = 0; midiTCminutes = 0; midiTChours = 0; /* For cueing the slave to a particular start point, Quarter Frame * messages are not used. Instead, an MTC Full Frame message should * be sent. The Full Frame is a SysEx message that encodes the entire * SMPTE time in one message */ if (conf::midiSync == MIDI_SYNC_MTC_M) { kernelMidi::send(MIDI_SYSEX, 0x7F, 0x00); // send msg on channel 0 kernelMidi::send(0x01, 0x01, 0x00); // hours 0 kernelMidi::send(0x00, 0x00, 0x00); // mins, secs, frames 0 kernelMidi::send(MIDI_EOX, -1, -1); // end of sysex } } /* -------------------------------------------------------------------------- */ #ifdef G_OS_LINUX void recvJackSync() { kernelAudio::JackState jackState = kernelAudio::jackTransportQuery(); if (jackState.running != jackStatePrev.running) { if (jackState.running) { if (!isRunning()) glue_startSeq(false); // not from UI } else { if (isRunning()) glue_stopSeq(false); // not from UI } } if (jackState.bpm != jackStatePrev.bpm) if (jackState.bpm > 1.0f) // 0 bpm if Jack does not send that info glue_setBpm(jackState.bpm); if (jackState.frame == 0 && jackState.frame != jackStatePrev.frame) glue_rewindSeq(false, false); // not from UI, don't notify jack (avoid loop) jackStatePrev = jackState; } #endif /* -------------------------------------------------------------------------- */ int getCurrentFrame() { return currentFrame; } int getTotalFrames() { return totalFrames; } int getCurrentBeat() { return currentBeat; } int getQuantize() { return quantize; } float getBpm() { return bpm; } int getBeats() { return beats; } int getBars() { return bars; } int getQuanto() { return quanto; } int getFramesPerBar() { return framesPerBar; } int getFramesPerBeat() { return framesPerBeat; } int getFramesInSequencer() { return framesInSequencer; } }}}; // giada::m::clock:: giada-0.14.5/src/core/clock.h000066400000000000000000000043731322662744500156530ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef G_CLOCK_H #define G_CLOCK_H class Conf; class KernelMidi; class KernelAudio; namespace giada { namespace m { namespace clock { void init(int sampleRate, float midiTCfps); /* sendMIDIsync Generates MIDI sync output data. */ void sendMIDIsync(); /* sendMIDIrewind Rewinds timecode to beat 0 and also send a MTC full frame to cue the slave. */ void sendMIDIrewind(); #ifdef __linux__ void recvJackSync(); #endif float getBpm(); int getBeats(); int getBars(); int getCurrentFrame(); int getCurrentBeat(); int getFramesPerBar(); int getFramesPerBeat(); int getTotalFrames(); int getFramesInSequencer(); int getQuantize(); int getQuanto(); /* incrCurrentFrame Increases current frame of a stereo step (+2). */ void incrCurrentFrame(); /* quantoHasPassed Tells whether a quanto unit has passed yet. */ bool quantoHasPassed(); /* updateFrameBars Updates bpm, frames, beats and so on. */ void updateFrameBars(); void setBpm(float b); void setBars(int b); void setBeats(int b); void setQuantize(int q); bool isRunning(); bool isOnBeat(); bool isOnBar(); bool isOnFirstBeat(); void rewind(); void start(); void stop(); }}}; // giada::m::clock:: #endif giada-0.14.5/src/core/conf.cpp000066400000000000000000000561311322662744500160370ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include #include "../utils/fs.h" #include "../utils/log.h" #include "storager.h" #include "const.h" #include "conf.h" using std::string; namespace giada { namespace m { namespace conf { namespace { string confFilePath = ""; string confDirPath = ""; /* -------------------------------------------------------------------------- */ /* sanitize Avoids funky values from config file. */ void sanitize() { if (!(soundSystem & G_SYS_API_ANY)) soundSystem = G_DEFAULT_SOUNDSYS; if (soundDeviceOut < 0) soundDeviceOut = G_DEFAULT_SOUNDDEV_OUT; if (soundDeviceIn < -1) soundDeviceIn = G_DEFAULT_SOUNDDEV_IN; if (channelsOut < 0) channelsOut = 0; if (channelsIn < 0) channelsIn = 0; if (buffersize < G_MIN_BUF_SIZE || buffersize > G_MAX_BUF_SIZE) buffersize = G_DEFAULT_BUFSIZE; if (delayComp < 0) delayComp = G_DEFAULT_DELAYCOMP; if (midiPortOut < -1) midiPortOut = G_DEFAULT_MIDI_SYSTEM; if (midiPortOut < -1) midiPortOut = G_DEFAULT_MIDI_PORT_OUT; if (midiPortIn < -1) midiPortIn = G_DEFAULT_MIDI_PORT_IN; if (browserX < 0) browserX = 0; if (browserY < 0) browserY = 0; if (browserW < 396) browserW = 396; if (browserH < 302) browserH = 302; if (actionEditorX < 0) actionEditorX = 0; if (actionEditorY < 0) actionEditorY = 0; if (actionEditorW < 640) actionEditorW = 640; if (actionEditorH < 176) actionEditorH = 176; if (actionEditorZoom < 100) actionEditorZoom = 100; if (actionEditorGridVal < 0 || actionEditorGridVal > G_MAX_GRID_VAL) actionEditorGridVal = 0; if (actionEditorGridOn < 0) actionEditorGridOn = 0; if (pianoRollH <= 0) pianoRollH = 422; if (sampleEditorX < 0) sampleEditorX = 0; if (sampleEditorY < 0) sampleEditorY = 0; if (sampleEditorW < 500) sampleEditorW = 500; if (sampleEditorH < 292) sampleEditorH = 292; if (sampleEditorGridVal < 0 || sampleEditorGridVal > G_MAX_GRID_VAL) sampleEditorGridVal = 0; if (sampleEditorGridOn < 0) sampleEditorGridOn = 0; if (midiInputX < 0) midiInputX = 0; if (midiInputY < 0) midiInputY = 0; if (midiInputW < G_DEFAULT_MIDI_INPUT_UI_W) midiInputW = G_DEFAULT_MIDI_INPUT_UI_W; if (midiInputH < G_DEFAULT_MIDI_INPUT_UI_H) midiInputH = G_DEFAULT_MIDI_INPUT_UI_H; if (configX < 0) configX = 0; if (configY < 0) configY = 0; if (pluginListX < 0) pluginListX = 0; if (pluginListY < 0) pluginListY = 0; #ifdef WITH_VST if (pluginChooserW < 640) pluginChooserW = 640; if (pluginChooserH < 480) pluginChooserW = 480; #endif if (bpmX < 0) bpmX = 0; if (bpmY < 0) bpmY = 0; if (beatsX < 0) beatsX = 0; if (beatsY < 0) beatsY = 0; if (aboutX < 0) aboutX = 0; if (aboutY < 0) aboutY = 0; if (samplerate < 8000) samplerate = G_DEFAULT_SAMPLERATE; if (rsmpQuality < 0 || rsmpQuality > 4) rsmpQuality = 0; } /* -------------------------------------------------------------------------- */ /* createConfigFolder Creates local folder where to put the configuration file. Path differs from OS to OS. */ int createConfigFolder() { #if defined(__linux__) || defined(__APPLE__) if (gu_dirExists(confDirPath)) return 1; gu_log("[conf::createConfigFolder] .giada folder not present. Updating...\n"); if (gu_mkdir(confDirPath)) { gu_log("[conf::createConfigFolder] status: ok\n"); return 1; } else { gu_log("[conf::createConfigFolder] status: error!\n"); return 0; } #else // windows return 1; #endif } }; // {anonymous} /* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */ string header = "GIADACFG"; int logMode = LOG_MODE_MUTE; int soundSystem = G_DEFAULT_SOUNDSYS; int soundDeviceOut = G_DEFAULT_SOUNDDEV_OUT; int soundDeviceIn = G_DEFAULT_SOUNDDEV_IN; int channelsOut = 0; int channelsIn = 0; int samplerate = G_DEFAULT_SAMPLERATE; int buffersize = G_DEFAULT_BUFSIZE; int delayComp = G_DEFAULT_DELAYCOMP; bool limitOutput = false; int rsmpQuality = 0; int midiSystem = 0; int midiPortOut = G_DEFAULT_MIDI_PORT_OUT; int midiPortIn = G_DEFAULT_MIDI_PORT_IN; bool noNoteOff = false; string midiMapPath = ""; string lastFileMap = ""; int midiSync = MIDI_SYNC_NONE; float midiTCfps = 25.0f; bool midiIn = false; int midiInFilter = -1; uint32_t midiInRewind = 0x0; uint32_t midiInStartStop = 0x0; uint32_t midiInActionRec = 0x0; uint32_t midiInInputRec = 0x0; uint32_t midiInVolumeIn = 0x0; uint32_t midiInVolumeOut = 0x0; uint32_t midiInBeatDouble = 0x0; uint32_t midiInBeatHalf = 0x0; uint32_t midiInMetronome = 0x0; bool recsStopOnChanHalt = false; bool chansStopOnSeqHalt = false; bool treatRecsAsLoops = false; bool resizeRecordings = true; bool inputMonitorDefaultOn = false; string pluginPath = ""; string patchPath = ""; string samplePath = ""; int mainWindowX = 0; int mainWindowY = 0; int mainWindowW = G_GUI_WIDTH; int mainWindowH = G_GUI_HEIGHT; int browserX = 0; int browserY = 0; int browserW = 640; int browserH = 480; int browserPosition = 0; int browserLastValue = 0; string browserLastPath = ""; int actionEditorX = 0; int actionEditorY = 0; int actionEditorW = 640; int actionEditorH = 480; int actionEditorZoom = 100; int actionEditorGridVal = 1; int actionEditorGridOn = false; int sampleEditorX = 0; int sampleEditorY = 0; int sampleEditorW = 640; int sampleEditorH = 480; int sampleEditorGridVal = 0; int sampleEditorGridOn = false; int midiInputX = 0; int midiInputY = 0; int midiInputW = G_DEFAULT_MIDI_INPUT_UI_W; int midiInputH = G_DEFAULT_MIDI_INPUT_UI_H; int pianoRollY = -1; int pianoRollH = 422; int pluginListX = 0; int pluginListY = 0; int configX = 0; int configY = 0; int bpmX = 0; int bpmY = 0; int beatsX = 0; int beatsY = 0; int aboutX = 0; int aboutY = 0; int nameX = 0; int nameY = 0; #ifdef WITH_VST int pluginChooserX = 0; int pluginChooserY = 0; int pluginChooserW = 640; int pluginChooserH = 480; int pluginSortMethod = 0; #endif /* -------------------------------------------------------------------------- */ void init() { /* Initialize confFilePath, i.e. the configuration file. In windows it is in * the same dir of the .exe, while in Linux and OS X in ~/.giada */ #if defined(__linux__) || defined(__APPLE__) confFilePath = gu_getHomePath() + G_SLASH + CONF_FILENAME; confDirPath = gu_getHomePath() + G_SLASH; #elif defined(_WIN32) confFilePath = CONF_FILENAME; confDirPath = ""; #endif } /* -------------------------------------------------------------------------- */ bool isMidiInAllowed(int c) { return midiInFilter == -1 || midiInFilter == c; } /* -------------------------------------------------------------------------- */ int read() { init(); json_error_t jError; json_t *jRoot = json_load_file(confFilePath.c_str(), 0, &jError); if (!jRoot) { gu_log("[conf::read] unable to read configuration file! Error on line %d: %s\n", jError.line, jError.text); return 0; } if (!storager::checkObject(jRoot, "root element")) { json_decref(jRoot); return 0; } if (!storager::setString(jRoot, CONF_KEY_HEADER, header)) return 0; if (!storager::setInt(jRoot, CONF_KEY_LOG_MODE, logMode)) return 0; if (!storager::setInt(jRoot, CONF_KEY_SOUND_SYSTEM, soundSystem)) return 0; if (!storager::setInt(jRoot, CONF_KEY_SOUND_DEVICE_OUT, soundDeviceOut)) return 0; if (!storager::setInt(jRoot, CONF_KEY_SOUND_DEVICE_IN, soundDeviceIn)) return 0; if (!storager::setInt(jRoot, CONF_KEY_CHANNELS_OUT, channelsOut)) return 0; if (!storager::setInt(jRoot, CONF_KEY_CHANNELS_IN, channelsIn)) return 0; if (!storager::setInt(jRoot, CONF_KEY_SAMPLERATE, samplerate)) return 0; if (!storager::setInt(jRoot, CONF_KEY_BUFFER_SIZE, buffersize)) return 0; if (!storager::setInt(jRoot, CONF_KEY_DELAY_COMPENSATION, delayComp)) return 0; if (!storager::setBool(jRoot, CONF_KEY_LIMIT_OUTPUT, limitOutput)) return 0; if (!storager::setInt(jRoot, CONF_KEY_RESAMPLE_QUALITY, rsmpQuality)) return 0; if (!storager::setInt(jRoot, CONF_KEY_MIDI_SYSTEM, midiSystem)) return 0; if (!storager::setInt(jRoot, CONF_KEY_MIDI_PORT_OUT, midiPortOut)) return 0; if (!storager::setInt(jRoot, CONF_KEY_MIDI_PORT_IN, midiPortIn)) return 0; if (!storager::setBool(jRoot, CONF_KEY_NO_NOTE_OFF, noNoteOff)) return 0; if (!storager::setString(jRoot, CONF_KEY_MIDIMAP_PATH, midiMapPath)) return 0; if (!storager::setString(jRoot, CONF_KEY_LAST_MIDIMAP, lastFileMap)) return 0; if (!storager::setInt(jRoot, CONF_KEY_MIDI_SYNC, midiSync)) return 0; if (!storager::setFloat(jRoot, CONF_KEY_MIDI_TC_FPS, midiTCfps)) return 0; if (!storager::setBool(jRoot, CONF_KEY_MIDI_IN, midiIn)) return 0; if (!storager::setInt(jRoot, CONF_KEY_MIDI_IN_FILTER, midiInFilter)) return 0; if (!storager::setUint32(jRoot, CONF_KEY_MIDI_IN_REWIND, midiInRewind)) return 0; if (!storager::setUint32(jRoot, CONF_KEY_MIDI_IN_START_STOP, midiInStartStop)) return 0; if (!storager::setUint32(jRoot, CONF_KEY_MIDI_IN_ACTION_REC, midiInActionRec)) return 0; if (!storager::setUint32(jRoot, CONF_KEY_MIDI_IN_INPUT_REC, midiInInputRec)) return 0; if (!storager::setUint32(jRoot, CONF_KEY_MIDI_IN_METRONOME, midiInMetronome)) return 0; if (!storager::setUint32(jRoot, CONF_KEY_MIDI_IN_VOLUME_IN, midiInVolumeIn)) return 0; if (!storager::setUint32(jRoot, CONF_KEY_MIDI_IN_VOLUME_OUT, midiInVolumeOut)) return 0; if (!storager::setUint32(jRoot, CONF_KEY_MIDI_IN_BEAT_DOUBLE, midiInBeatDouble)) return 0; if (!storager::setUint32(jRoot, CONF_KEY_MIDI_IN_BEAT_HALF, midiInBeatHalf)) return 0; if (!storager::setBool(jRoot, CONF_KEY_RECS_STOP_ON_CHAN_HALT, recsStopOnChanHalt)) return 0; if (!storager::setBool(jRoot, CONF_KEY_CHANS_STOP_ON_SEQ_HALT, chansStopOnSeqHalt)) return 0; if (!storager::setBool(jRoot, CONF_KEY_TREAT_RECS_AS_LOOPS, treatRecsAsLoops)) return 0; if (!storager::setBool(jRoot, CONF_KEY_RESIZE_RECORDINGS, resizeRecordings)) return 0; if (!storager::setBool(jRoot, CONF_KEY_INPUT_MONITOR_DEFAULT_ON, inputMonitorDefaultOn)) return 0; if (!storager::setString(jRoot, CONF_KEY_PLUGINS_PATH, pluginPath)) return 0; if (!storager::setString(jRoot, CONF_KEY_PATCHES_PATH, patchPath)) return 0; if (!storager::setString(jRoot, CONF_KEY_SAMPLES_PATH, samplePath)) return 0; if (!storager::setInt(jRoot, CONF_KEY_MAIN_WINDOW_X, mainWindowX)) return 0; if (!storager::setInt(jRoot, CONF_KEY_MAIN_WINDOW_Y, mainWindowY)) return 0; if (!storager::setInt(jRoot, CONF_KEY_MAIN_WINDOW_W, mainWindowW)) return 0; if (!storager::setInt(jRoot, CONF_KEY_MAIN_WINDOW_H, mainWindowH)) return 0; if (!storager::setInt(jRoot, CONF_KEY_BROWSER_X, browserX)) return 0; if (!storager::setInt(jRoot, CONF_KEY_BROWSER_Y, browserY)) return 0; if (!storager::setInt(jRoot, CONF_KEY_BROWSER_W, browserW)) return 0; if (!storager::setInt(jRoot, CONF_KEY_BROWSER_H, browserH)) return 0; if (!storager::setInt(jRoot, CONF_KEY_BROWSER_POSITION, browserPosition)) return 0; if (!storager::setString(jRoot, CONF_KEY_BROWSER_LAST_PATH, browserLastPath)) return 0; if (!storager::setInt(jRoot, CONF_KEY_BROWSER_LAST_VALUE, browserLastValue)) return 0; if (!storager::setInt(jRoot, CONF_KEY_ACTION_EDITOR_X, actionEditorX)) return 0; if (!storager::setInt(jRoot, CONF_KEY_ACTION_EDITOR_Y, actionEditorY)) return 0; if (!storager::setInt(jRoot, CONF_KEY_ACTION_EDITOR_W, actionEditorW)) return 0; if (!storager::setInt(jRoot, CONF_KEY_ACTION_EDITOR_H, actionEditorH)) return 0; if (!storager::setInt(jRoot, CONF_KEY_ACTION_EDITOR_ZOOM, actionEditorZoom)) return 0; if (!storager::setInt(jRoot, CONF_KEY_ACTION_EDITOR_GRID_VAL, actionEditorGridVal)) return 0; if (!storager::setInt(jRoot, CONF_KEY_ACTION_EDITOR_GRID_ON, actionEditorGridOn)) return 0; if (!storager::setInt(jRoot, CONF_KEY_SAMPLE_EDITOR_X, sampleEditorX)) return 0; if (!storager::setInt(jRoot, CONF_KEY_SAMPLE_EDITOR_Y, sampleEditorY)) return 0; if (!storager::setInt(jRoot, CONF_KEY_SAMPLE_EDITOR_W, sampleEditorW)) return 0; if (!storager::setInt(jRoot, CONF_KEY_SAMPLE_EDITOR_H, sampleEditorH)) return 0; if (!storager::setInt(jRoot, CONF_KEY_SAMPLE_EDITOR_GRID_VAL, sampleEditorGridVal)) return 0; if (!storager::setInt(jRoot, CONF_KEY_SAMPLE_EDITOR_GRID_ON, sampleEditorGridOn)) return 0; if (!storager::setInt(jRoot, CONF_KEY_PIANO_ROLL_Y, pianoRollY)) return 0; if (!storager::setInt(jRoot, CONF_KEY_PIANO_ROLL_H, pianoRollH)) return 0; if (!storager::setInt(jRoot, CONF_KEY_PLUGIN_LIST_X, pluginListX)) return 0; if (!storager::setInt(jRoot, CONF_KEY_PLUGIN_LIST_Y, pluginListY)) return 0; if (!storager::setInt(jRoot, CONF_KEY_CONFIG_X, configX)) return 0; if (!storager::setInt(jRoot, CONF_KEY_CONFIG_Y, configY)) return 0; if (!storager::setInt(jRoot, CONF_KEY_BPM_X, bpmX)) return 0; if (!storager::setInt(jRoot, CONF_KEY_BPM_Y, bpmY)) return 0; if (!storager::setInt(jRoot, CONF_KEY_BEATS_X, beatsX)) return 0; if (!storager::setInt(jRoot, CONF_KEY_BEATS_Y, beatsY)) return 0; if (!storager::setInt(jRoot, CONF_KEY_ABOUT_X, aboutX)) return 0; if (!storager::setInt(jRoot, CONF_KEY_ABOUT_Y, aboutY)) return 0; if (!storager::setInt(jRoot, CONF_KEY_NAME_X, nameX)) return 0; if (!storager::setInt(jRoot, CONF_KEY_NAME_Y, nameY)) return 0; if (!storager::setInt(jRoot, CONF_KEY_MIDI_INPUT_X, midiInputX)) return 0; if (!storager::setInt(jRoot, CONF_KEY_MIDI_INPUT_Y, midiInputY)) return 0; if (!storager::setInt(jRoot, CONF_KEY_MIDI_INPUT_W, midiInputW)) return 0; if (!storager::setInt(jRoot, CONF_KEY_MIDI_INPUT_H, midiInputH)) return 0; #ifdef WITH_VST if (!storager::setInt(jRoot, CONF_KEY_PLUGIN_CHOOSER_X, pluginChooserX)) return 0; if (!storager::setInt(jRoot, CONF_KEY_PLUGIN_CHOOSER_Y, pluginChooserY)) return 0; if (!storager::setInt(jRoot, CONF_KEY_PLUGIN_CHOOSER_W, pluginChooserW)) return 0; if (!storager::setInt(jRoot, CONF_KEY_PLUGIN_CHOOSER_H, pluginChooserH)) return 0; if (!storager::setInt(jRoot, CONF_KEY_PLUGIN_SORT_METHOD, pluginSortMethod)) return 0; #endif json_decref(jRoot); sanitize(); return 1; } /* -------------------------------------------------------------------------- */ int write() { if (!createConfigFolder()) return 0; json_t *jRoot = json_object(); json_object_set_new(jRoot, CONF_KEY_HEADER, json_string(header.c_str())); json_object_set_new(jRoot, CONF_KEY_LOG_MODE, json_integer(logMode)); json_object_set_new(jRoot, CONF_KEY_SOUND_SYSTEM, json_integer(soundSystem)); json_object_set_new(jRoot, CONF_KEY_SOUND_DEVICE_OUT, json_integer(soundDeviceOut)); json_object_set_new(jRoot, CONF_KEY_SOUND_DEVICE_IN, json_integer(soundDeviceIn)); json_object_set_new(jRoot, CONF_KEY_CHANNELS_OUT, json_integer(channelsOut)); json_object_set_new(jRoot, CONF_KEY_CHANNELS_IN, json_integer(channelsIn)); json_object_set_new(jRoot, CONF_KEY_SAMPLERATE, json_integer(samplerate)); json_object_set_new(jRoot, CONF_KEY_BUFFER_SIZE, json_integer(buffersize)); json_object_set_new(jRoot, CONF_KEY_DELAY_COMPENSATION, json_integer(delayComp)); json_object_set_new(jRoot, CONF_KEY_LIMIT_OUTPUT, json_boolean(limitOutput)); json_object_set_new(jRoot, CONF_KEY_RESAMPLE_QUALITY, json_integer(rsmpQuality)); json_object_set_new(jRoot, CONF_KEY_MIDI_SYSTEM, json_integer(midiSystem)); json_object_set_new(jRoot, CONF_KEY_MIDI_PORT_OUT, json_integer(midiPortOut)); json_object_set_new(jRoot, CONF_KEY_MIDI_PORT_IN, json_integer(midiPortIn)); json_object_set_new(jRoot, CONF_KEY_NO_NOTE_OFF, json_boolean(noNoteOff)); json_object_set_new(jRoot, CONF_KEY_MIDIMAP_PATH, json_string(midiMapPath.c_str())); json_object_set_new(jRoot, CONF_KEY_LAST_MIDIMAP, json_string(lastFileMap.c_str())); json_object_set_new(jRoot, CONF_KEY_MIDI_SYNC, json_integer(midiSync)); json_object_set_new(jRoot, CONF_KEY_MIDI_TC_FPS, json_real(midiTCfps)); json_object_set_new(jRoot, CONF_KEY_MIDI_IN, json_boolean(midiIn)); json_object_set_new(jRoot, CONF_KEY_MIDI_IN_FILTER, json_integer(midiInFilter)); json_object_set_new(jRoot, CONF_KEY_MIDI_IN_REWIND, json_integer(midiInRewind)); json_object_set_new(jRoot, CONF_KEY_MIDI_IN_START_STOP, json_integer(midiInStartStop)); json_object_set_new(jRoot, CONF_KEY_MIDI_IN_ACTION_REC, json_integer(midiInActionRec)); json_object_set_new(jRoot, CONF_KEY_MIDI_IN_INPUT_REC, json_integer(midiInInputRec)); json_object_set_new(jRoot, CONF_KEY_MIDI_IN_METRONOME, json_integer(midiInMetronome)); json_object_set_new(jRoot, CONF_KEY_MIDI_IN_VOLUME_IN, json_integer(midiInVolumeIn)); json_object_set_new(jRoot, CONF_KEY_MIDI_IN_VOLUME_OUT, json_integer(midiInVolumeOut)); json_object_set_new(jRoot, CONF_KEY_MIDI_IN_BEAT_DOUBLE, json_integer(midiInBeatDouble)); json_object_set_new(jRoot, CONF_KEY_MIDI_IN_BEAT_HALF, json_integer(midiInBeatHalf)); json_object_set_new(jRoot, CONF_KEY_RECS_STOP_ON_CHAN_HALT, json_boolean(recsStopOnChanHalt)); json_object_set_new(jRoot, CONF_KEY_CHANS_STOP_ON_SEQ_HALT, json_boolean(chansStopOnSeqHalt)); json_object_set_new(jRoot, CONF_KEY_TREAT_RECS_AS_LOOPS, json_boolean(treatRecsAsLoops)); json_object_set_new(jRoot, CONF_KEY_RESIZE_RECORDINGS, json_boolean(resizeRecordings)); json_object_set_new(jRoot, CONF_KEY_INPUT_MONITOR_DEFAULT_ON, json_boolean(inputMonitorDefaultOn)); json_object_set_new(jRoot, CONF_KEY_PLUGINS_PATH, json_string(pluginPath.c_str())); json_object_set_new(jRoot, CONF_KEY_PATCHES_PATH, json_string(patchPath.c_str())); json_object_set_new(jRoot, CONF_KEY_SAMPLES_PATH, json_string(samplePath.c_str())); json_object_set_new(jRoot, CONF_KEY_MAIN_WINDOW_X, json_integer(mainWindowX)); json_object_set_new(jRoot, CONF_KEY_MAIN_WINDOW_Y, json_integer(mainWindowY)); json_object_set_new(jRoot, CONF_KEY_MAIN_WINDOW_W, json_integer(mainWindowW)); json_object_set_new(jRoot, CONF_KEY_MAIN_WINDOW_H, json_integer(mainWindowH)); json_object_set_new(jRoot, CONF_KEY_BROWSER_X, json_integer(browserX)); json_object_set_new(jRoot, CONF_KEY_BROWSER_Y, json_integer(browserY)); json_object_set_new(jRoot, CONF_KEY_BROWSER_W, json_integer(browserW)); json_object_set_new(jRoot, CONF_KEY_BROWSER_H, json_integer(browserH)); json_object_set_new(jRoot, CONF_KEY_BROWSER_POSITION, json_integer(browserPosition)); json_object_set_new(jRoot, CONF_KEY_BROWSER_LAST_PATH, json_string(browserLastPath.c_str())); json_object_set_new(jRoot, CONF_KEY_BROWSER_LAST_VALUE, json_integer(browserLastValue)); json_object_set_new(jRoot, CONF_KEY_ACTION_EDITOR_X, json_integer(actionEditorX)); json_object_set_new(jRoot, CONF_KEY_ACTION_EDITOR_Y, json_integer(actionEditorY)); json_object_set_new(jRoot, CONF_KEY_ACTION_EDITOR_W, json_integer(actionEditorW)); json_object_set_new(jRoot, CONF_KEY_ACTION_EDITOR_H, json_integer(actionEditorH)); json_object_set_new(jRoot, CONF_KEY_ACTION_EDITOR_ZOOM, json_integer(actionEditorZoom)); json_object_set_new(jRoot, CONF_KEY_ACTION_EDITOR_GRID_VAL, json_integer(actionEditorGridVal)); json_object_set_new(jRoot, CONF_KEY_ACTION_EDITOR_GRID_ON, json_integer(actionEditorGridOn)); json_object_set_new(jRoot, CONF_KEY_SAMPLE_EDITOR_X, json_integer(sampleEditorX)); json_object_set_new(jRoot, CONF_KEY_SAMPLE_EDITOR_Y, json_integer(sampleEditorY)); json_object_set_new(jRoot, CONF_KEY_SAMPLE_EDITOR_W, json_integer(sampleEditorW)); json_object_set_new(jRoot, CONF_KEY_SAMPLE_EDITOR_H, json_integer(sampleEditorH)); json_object_set_new(jRoot, CONF_KEY_SAMPLE_EDITOR_GRID_VAL, json_integer(sampleEditorGridVal)); json_object_set_new(jRoot, CONF_KEY_SAMPLE_EDITOR_GRID_ON, json_integer(sampleEditorGridOn)); json_object_set_new(jRoot, CONF_KEY_PIANO_ROLL_Y, json_integer(pianoRollY)); json_object_set_new(jRoot, CONF_KEY_PIANO_ROLL_H, json_integer(pianoRollH)); json_object_set_new(jRoot, CONF_KEY_PLUGIN_LIST_X, json_integer(pluginListX)); json_object_set_new(jRoot, CONF_KEY_PLUGIN_LIST_Y, json_integer(pluginListY)); json_object_set_new(jRoot, CONF_KEY_CONFIG_X, json_integer(configX)); json_object_set_new(jRoot, CONF_KEY_CONFIG_Y, json_integer(configY)); json_object_set_new(jRoot, CONF_KEY_BPM_X, json_integer(bpmX)); json_object_set_new(jRoot, CONF_KEY_BPM_Y, json_integer(bpmY)); json_object_set_new(jRoot, CONF_KEY_BEATS_X, json_integer(beatsX)); json_object_set_new(jRoot, CONF_KEY_BEATS_Y, json_integer(beatsY)); json_object_set_new(jRoot, CONF_KEY_ABOUT_X, json_integer(aboutX)); json_object_set_new(jRoot, CONF_KEY_ABOUT_Y, json_integer(aboutY)); json_object_set_new(jRoot, CONF_KEY_NAME_X, json_integer(nameX)); json_object_set_new(jRoot, CONF_KEY_NAME_Y, json_integer(nameY)); json_object_set_new(jRoot, CONF_KEY_MIDI_INPUT_X, json_integer(midiInputX)); json_object_set_new(jRoot, CONF_KEY_MIDI_INPUT_Y, json_integer(midiInputY)); json_object_set_new(jRoot, CONF_KEY_MIDI_INPUT_W, json_integer(midiInputW)); json_object_set_new(jRoot, CONF_KEY_MIDI_INPUT_H, json_integer(midiInputH)); #ifdef WITH_VST json_object_set_new(jRoot, CONF_KEY_PLUGIN_CHOOSER_X, json_integer(pluginChooserX)); json_object_set_new(jRoot, CONF_KEY_PLUGIN_CHOOSER_Y, json_integer(pluginChooserY)); json_object_set_new(jRoot, CONF_KEY_PLUGIN_CHOOSER_W, json_integer(pluginChooserW)); json_object_set_new(jRoot, CONF_KEY_PLUGIN_CHOOSER_H, json_integer(pluginChooserH)); json_object_set_new(jRoot, CONF_KEY_PLUGIN_SORT_METHOD, json_integer(pluginSortMethod)); #endif if (json_dump_file(jRoot, confFilePath.c_str(), JSON_INDENT(2)) != 0) { gu_log("[conf::write] unable to write configuration file!\n"); return 0; } return 1; } }}}; // giada::m::conf:: giada-0.14.5/src/core/conf.h000066400000000000000000000066121322662744500155030ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef G_CONF_H #define G_CONF_H #include namespace giada { namespace m { namespace conf { void init(); int read(); int write(); /* isMidiAllowed Given a MIDI channel 'c' tells whether this channel should be allowed to receive and process MIDI events on MIDI channel 'c'. */ bool isMidiInAllowed(int c); extern std::string header; extern int logMode; extern int soundSystem; extern int soundDeviceOut; extern int soundDeviceIn; extern int channelsOut; extern int channelsIn; extern int samplerate; extern int buffersize; extern int delayComp; extern bool limitOutput; extern int rsmpQuality; extern int midiSystem; extern int midiPortOut; extern int midiPortIn; extern bool noNoteOff; extern std::string midiMapPath; extern std::string lastFileMap; extern int midiSync; // see const.h extern float midiTCfps; extern bool midiIn; extern int midiInFilter; extern uint32_t midiInRewind; extern uint32_t midiInStartStop; extern uint32_t midiInActionRec; extern uint32_t midiInInputRec; extern uint32_t midiInMetronome; extern uint32_t midiInVolumeIn; extern uint32_t midiInVolumeOut; extern uint32_t midiInBeatDouble; extern uint32_t midiInBeatHalf; extern bool recsStopOnChanHalt; extern bool chansStopOnSeqHalt; extern bool treatRecsAsLoops; extern bool resizeRecordings; extern bool inputMonitorDefaultOn; extern std::string pluginPath; extern std::string patchPath; extern std::string samplePath; extern int mainWindowX, mainWindowY, mainWindowW, mainWindowH; extern int browserX, browserY, browserW, browserH, browserPosition, browserLastValue; extern std::string browserLastPath; extern int actionEditorX, actionEditorY, actionEditorW, actionEditorH, actionEditorZoom; extern int actionEditorGridVal; extern int actionEditorGridOn; extern int sampleEditorX, sampleEditorY, sampleEditorW, sampleEditorH; extern int sampleEditorGridVal; extern int sampleEditorGridOn; extern int midiInputX, midiInputY, midiInputW, midiInputH; extern int pianoRollY, pianoRollH; extern int pluginListX, pluginListY; extern int configX, configY; extern int bpmX, bpmY; extern int beatsX, beatsY; extern int aboutX, aboutY; extern int nameX, nameY; #ifdef WITH_VST extern int pluginChooserX, pluginChooserY, pluginChooserW, pluginChooserH; extern int pluginSortMethod; #endif }}}; // giada::m::conf:: #endif giada-0.14.5/src/core/const.h000066400000000000000000000517161322662744500157110ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef G_CONST_H #define G_CONST_H /* -- environment ----------------------------------------------------------- */ #if defined(_WIN32) #define G_OS_WINDOWS #elif defined(__APPLE__) #define G_OS_MAC #elif defined(__linux__) #define G_OS_LINUX #endif #ifndef BUILD_DATE #define BUILD_DATE __DATE__ #endif /* -- version --------------------------------------------------------------- */ #define G_APP_NAME "Giada" #define G_VERSION_STR "0.14.5" #define G_VERSION_MAJOR 0 #define G_VERSION_MINOR 14 #define G_VERSION_PATCH 5 #define CONF_FILENAME "giada.conf" #ifdef G_OS_WINDOWS #define G_SLASH '\\' #define G_SLASH_STR "\\" #else #define G_SLASH '/' #define G_SLASH_STR "/" #endif /* -- GUI ------------------------------------------------------------------- */ #ifdef G_OS_WINDOWS #define G_GUI_SLEEP 1000/24 #else #define G_GUI_SLEEP 1000000/24 // == 1.000.000 / 24 == 1/24 sec == 24 Hz #endif #define G_GUI_WIDTH 816 #define G_GUI_HEIGHT 510 #define G_GUI_PLUGIN_RATE 0.05 // refresh rate for plugin GUIs #define G_GUI_FONT_SIZE_BASE 12 #define G_GUI_INNER_MARGIN 4 #define G_GUI_OUTER_MARGIN 8 #define G_GUI_UNIT 20 // base unit for elements #define G_GUI_CHANNEL_H_1 G_GUI_UNIT #define G_GUI_CHANNEL_H_2 G_GUI_UNIT * 2 #define G_GUI_CHANNEL_H_3 G_GUI_UNIT * 4 #define G_GUI_CHANNEL_H_4 G_GUI_UNIT * 6 #define G_COLOR_RED fl_rgb_color(28, 32, 80) #define G_COLOR_BLUE fl_rgb_color(113, 31, 31) #define G_COLOR_RED_ALERT fl_rgb_color(239, 75, 53) #define G_COLOR_LIGHT_2 fl_rgb_color(200, 200, 200) #define G_COLOR_LIGHT_1 fl_rgb_color(170, 170, 170) #define G_COLOR_GREY_4 fl_rgb_color(78, 78, 78) #define G_COLOR_GREY_3 fl_rgb_color(54, 54, 54) #define G_COLOR_GREY_2 fl_rgb_color(37, 37, 37) #define G_COLOR_GREY_1_5 fl_rgb_color(28, 28, 28) #define G_COLOR_GREY_1 fl_rgb_color(25, 25, 25) #define G_COLOR_BLACK fl_rgb_color(0, 0, 0) /* -- MIN/MAX values -------------------------------------------------------- */ #define G_MIN_BPM 20.0f #define G_MAX_BPM 999.0f #define G_MAX_BEATS 32 #define G_MAX_BARS 32 #define G_MAX_QUANTIZE 8 #define G_MIN_DB_SCALE 60.0f #define G_MIN_COLUMN_WIDTH 140 #define G_MAX_BOOST_DB 20.0f #define G_MAX_PITCH 4.0f #define G_MAX_GRID_VAL 64 #define G_MIN_BUF_SIZE 8 #define G_MAX_BUF_SIZE 4096 /* -- kernel audio ---------------------------------------------------------- */ #define G_SYS_API_NONE 0x00 // 0000 0000 #define G_SYS_API_JACK 0x01 // 0000 0001 #define G_SYS_API_ALSA 0x02 // 0000 0010 #define G_SYS_API_DS 0x04 // 0000 0100 #define G_SYS_API_ASIO 0x08 // 0000 1000 #define G_SYS_API_CORE 0x10 // 0001 0000 #define G_SYS_API_PULSE 0x20 // 0010 0000 #define G_SYS_API_WASAPI 0x40 // 0100 0000 #define G_SYS_API_ANY 0x7F // 0111 1111 /* -- kernel midi ----------------------------------------------------------- */ #define G_MIDI_API_JACK 0x01 // 0000 0001 #define G_MIDI_API_ALSA 0x02 // 0000 0010 /* -- default system -------------------------------------------------------- */ #if defined(G_OS_LINUX) #define G_DEFAULT_SOUNDSYS G_SYS_API_NONE #elif defined(G_OS_WINDOWS) #define G_DEFAULT_SOUNDSYS G_SYS_API_DS #elif defined(G_OS_MAC) #define G_DEFAULT_SOUNDSYS G_SYS_API_CORE #endif #define G_DEFAULT_SOUNDDEV_OUT 0 // FIXME - please override with rtAudio::getDefaultDevice (or similar) #define G_DEFAULT_SOUNDDEV_IN -1 // no recording by default: input disabled #define G_DEFAULT_MIDI_SYSTEM 0 #define G_DEFAULT_MIDI_PORT_IN -1 #define G_DEFAULT_MIDI_PORT_OUT -1 #define G_DEFAULT_SAMPLERATE 44100 #define G_DEFAULT_BUFSIZE 1024 #define G_DEFAULT_DELAYCOMP 0 #define G_DEFAULT_BIT_DEPTH 32 // float #define G_DEFAULT_AUDIO_CHANS 2 // stereo for internal processing #define G_DEFAULT_VOL 1.0f #define G_DEFAULT_PITCH 1.0f #define G_DEFAULT_BOOST 1.0f #define G_DEFAULT_OUT_VOL 1.0f #define G_DEFAULT_IN_VOL 1.0f #define G_DEFAULT_CHANMODE SINGLE_BASIC #define G_DEFAULT_BPM 120.0f #define G_DEFAULT_BEATS 4 #define G_DEFAULT_BARS 1 #define G_DEFAULT_QUANTIZE 0 // quantizer off #define G_DEFAULT_FADEOUT_STEP 0.01f // micro-fadeout speed #define G_DEFAULT_COLUMN_WIDTH 380 #define G_DEFAULT_PATCH_NAME "(default patch)" #define G_DEFAULT_MIDI_INPUT_UI_W 300 #define G_DEFAULT_MIDI_INPUT_UI_H 350 #define G_DEFAULT_MIDI_ACTION_SIZE 8192 // frames /* -- mixer statuses and modes ---------------------------------------------- */ #define LOOP_BASIC 0x01 // 0000 0001 chanMode #define LOOP_ONCE 0x02 // 0000 0010 chanMode #define SINGLE_BASIC 0x04 // 0000 0100 chanMode #define SINGLE_PRESS 0x08 // 0000 1000 chanMode #define SINGLE_RETRIG 0x10 // 0001 0000 chanMode #define LOOP_REPEAT 0x20 // 0010 0000 chanMode #define SINGLE_ENDLESS 0x40 // 0100 0000 chanMode #define LOOP_ONCE_BAR 0x80 // 1000 0000 chanMode #define LOOP_ANY 0xA3 // 1010 0011 chanMode - any loop mode #define SINGLE_ANY 0x5C // 0101 1100 chanMode - any single mode #define STATUS_ENDING 0x01 // 0000 0001 chanStatus - ending (loop mode only) #define STATUS_WAIT 0x02 // 0000 0010 chanStatus - waiting for start (loop mode only) #define STATUS_PLAY 0x04 // 0000 0100 chanStatus - playing #define STATUS_OFF 0x08 // 0000 1000 chanStatus - off #define STATUS_EMPTY 0x10 // 0001 0000 chanStatus - not loaded (empty chan) #define STATUS_MISSING 0x20 // 0010 0000 chanStatus - not found #define STATUS_WRONG 0x40 // 0100 0000 chanStatus - something wrong (freq, bitrate, ...) #define REC_WAITING 0x01 // 0000 0001 #define REC_ENDING 0x02 // 0000 0010 #define REC_READING 0x04 // 0000 0100 #define REC_STOPPED 0x08 // 0000 1000 /* -- preview modes --------------------------------------------------------- */ #define G_PREVIEW_NONE 0x00 #define G_PREVIEW_NORMAL 0x01 #define G_PREVIEW_LOOP 0x02 /* -- actions --------------------------------------------------------------- */ #define G_ACTION_KEYPRESS 0x01 // 0000 0001 #define G_ACTION_KEYREL 0x02 // 0000 0010 #define G_ACTION_KILL 0x04 // 0000 0100 #define G_ACTION_MUTEON 0x08 // 0000 1000 #define G_ACTION_MUTEOFF 0x10 // 0001 0000 #define G_ACTION_VOLUME 0x20 // 0010 0000 #define G_ACTION_MIDI 0x40 // 0100 0000 #define G_ACTION_KEYS 0x03 // 0000 0011 any key #define G_ACTION_MUTES 0x24 // 0001 1000 any mute #define G_RANGE_CHAR 0x01 // range for MIDI (0-127) #define G_RANGE_FLOAT 0x02 // range for volumes and VST params (0.0-1.0) /* -- responses and return codes -------------------------------------------- */ #define G_RES_ERR_PROCESSING -6 #define G_RES_ERR_WRONG_DATA -5 #define G_RES_ERR_NO_DATA -4 #define G_RES_ERR_PATH_TOO_LONG -3 #define G_RES_ERR_IO -2 #define G_RES_ERR_MEMORY -1 #define G_RES_ERR 0 #define G_RES_OK 1 /* -- log modes ------------------------------------------------------------- */ #define LOG_MODE_STDOUT 0x01 #define LOG_MODE_FILE 0x02 #define LOG_MODE_MUTE 0x04 /* -- channel types --------------------------------------------------------- */ #define CHANNEL_SAMPLE 0x01 #define CHANNEL_MIDI 0x02 /* -- unique IDs of mainWin's subwindows ------------------------------------ */ /* -- wid > 0 are reserved by gg_keyboard ----------------------------------- */ #define WID_BEATS -1 #define WID_BPM -2 #define WID_ABOUT -3 #define WID_FILE_BROWSER -4 #define WID_CONFIG -5 #define WID_FX_LIST -6 #define WID_ACTION_EDITOR -7 #define WID_SAMPLE_EDITOR -8 #define WID_FX -9 #define WID_KEY_GRABBER -10 #define WID_SAMPLE_NAME -11 /* -- patch signals --------------------------------------------------------- */ #define PATCH_UNREADABLE 0x01 #define PATCH_INVALID 0x02 #define PATCH_READ_OK 0x04 #define PATCH_WRONG_PLUGINS 0x08 // currently unused #define PATCH_WRONG_SAMPLES 0x10 // currently unused /* -- midimap signals ------------------------------------------------------- */ #define MIDIMAP_NOT_SPECIFIED 0x00 #define MIDIMAP_UNREADABLE 0x01 #define MIDIMAP_INVALID 0x02 #define MIDIMAP_READ_OK 0x04 /* -- MIDI signals ------------------------------------------------------------- All signals are set to channel 0 (where channels are considered). It's up to the caller to bitmask them with the proper channel number. Channel voices messages - controller (0xB0) is a special subset of this family: it drives knobs, volume, faders and such. */ #define MIDI_CONTROLLER 0xB0 << 24 #define MIDI_NOTE_ON 0x90 << 24 #define MIDI_NOTE_OFF 0x80 << 24 #define MIDI_VELOCITY 0x3F << 8 #define MIDI_ALL_NOTES_OFF (MIDI_CONTROLLER) | (0x7B << 16) #define MIDI_VOLUME (MIDI_CONTROLLER) | (0x07 << 16) /* system common / real-time messages. Single bytes */ #define MIDI_SYSEX 0xF0 #define MIDI_MTC_QUARTER 0xF1 #define MIDI_POSITION_PTR 0xF2 #define MIDI_CLOCK 0xF8 #define MIDI_START 0xFA #define MIDI_CONTINUE 0xFB #define MIDI_STOP 0xFC #define MIDI_EOX 0xF7 // end of sysex /* channels */ #define MIDI_CHAN_0 0x00 << 24 #define MIDI_CHAN_1 0x01 << 24 #define MIDI_CHAN_2 0x02 << 24 #define MIDI_CHAN_3 0x03 << 24 #define MIDI_CHAN_4 0x04 << 24 #define MIDI_CHAN_5 0x05 << 24 #define MIDI_CHAN_6 0x06 << 24 #define MIDI_CHAN_7 0x07 << 24 #define MIDI_CHAN_8 0x08 << 24 #define MIDI_CHAN_9 0x09 << 24 #define MIDI_CHAN_10 0x0A << 24 #define MIDI_CHAN_11 0x0B << 24 #define MIDI_CHAN_12 0x0C << 24 #define MIDI_CHAN_13 0x0D << 24 #define MIDI_CHAN_14 0x0E << 24 #define MIDI_CHAN_15 0x0F << 24 const int MIDI_CHANS[16] = { MIDI_CHAN_0, MIDI_CHAN_1, MIDI_CHAN_2, MIDI_CHAN_3, MIDI_CHAN_4, MIDI_CHAN_5, MIDI_CHAN_6, MIDI_CHAN_7, MIDI_CHAN_8, MIDI_CHAN_9, MIDI_CHAN_10, MIDI_CHAN_11, MIDI_CHAN_12, MIDI_CHAN_13, MIDI_CHAN_14, MIDI_CHAN_15 }; /* midi sync constants */ #define MIDI_SYNC_NONE 0x00 #define MIDI_SYNC_CLOCK_M 0x01 // master #define MIDI_SYNC_CLOCK_S 0x02 // slave #define MIDI_SYNC_MTC_M 0x04 // master #define MIDI_SYNC_MTC_S 0x08 // slave /* JSON patch keys */ #define PATCH_KEY_HEADER "header" #define PATCH_KEY_VERSION "version" #define PATCH_KEY_VERSION_MAJOR "version_major" #define PATCH_KEY_VERSION_MINOR "version_minor" #define PATCH_KEY_VERSION_PATCH "version_patch" #define PATCH_KEY_NAME "name" #define PATCH_KEY_BPM "bpm" #define PATCH_KEY_BARS "bars" #define PATCH_KEY_BEATS "beats" #define PATCH_KEY_QUANTIZE "quantize" #define PATCH_KEY_MASTER_VOL_IN "master_vol_in" #define PATCH_KEY_MASTER_VOL_OUT "master_vol_out" #define PATCH_KEY_METRONOME "metronome" #define PATCH_KEY_LAST_TAKE_ID "last_take_id" #define PATCH_KEY_SAMPLERATE "samplerate" #define PATCH_KEY_COLUMNS "columns" #define PATCH_KEY_MASTER_OUT_PLUGINS "master_out_plugins" #define PATCH_KEY_MASTER_IN_PLUGINS "master_in_plugins" #define PATCH_KEY_CHANNELS "channels" #define PATCH_KEY_CHANNEL_TYPE "type" #define PATCH_KEY_CHANNEL_INDEX "index" #define PATCH_KEY_CHANNEL_SIZE "size" #define PATCH_KEY_CHANNEL_NAME "name" #define PATCH_KEY_CHANNEL_COLUMN "column" #define PATCH_KEY_CHANNEL_MUTE "mute" #define PATCH_KEY_CHANNEL_MUTE_S "mute_s" #define PATCH_KEY_CHANNEL_SOLO "solo" #define PATCH_KEY_CHANNEL_VOLUME "volume" #define PATCH_KEY_CHANNEL_PAN "pan" #define PATCH_KEY_CHANNEL_MIDI_IN "midi_in" #define PATCH_KEY_CHANNEL_MIDI_IN_KEYPRESS "midi_in_keypress" #define PATCH_KEY_CHANNEL_MIDI_IN_KEYREL "midi_in_keyrel" #define PATCH_KEY_CHANNEL_MIDI_IN_KILL "midi_in_kill" #define PATCH_KEY_CHANNEL_MIDI_IN_ARM "midi_in_arm" #define PATCH_KEY_CHANNEL_MIDI_IN_VOLUME "midi_in_volume" #define PATCH_KEY_CHANNEL_MIDI_IN_MUTE "midi_in_mute" #define PATCH_KEY_CHANNEL_MIDI_IN_FILTER "midi_in_filter" #define PATCH_KEY_CHANNEL_MIDI_IN_SOLO "midi_in_solo" #define PATCH_KEY_CHANNEL_MIDI_OUT_L "midi_out_l" #define PATCH_KEY_CHANNEL_MIDI_OUT_L_PLAYING "midi_out_l_playing" #define PATCH_KEY_CHANNEL_MIDI_OUT_L_MUTE "midi_out_l_mute" #define PATCH_KEY_CHANNEL_MIDI_OUT_L_SOLO "midi_out_l_solo" #define PATCH_KEY_CHANNEL_SAMPLE_PATH "sample_path" #define PATCH_KEY_CHANNEL_KEY "key" #define PATCH_KEY_CHANNEL_MODE "mode" #define PATCH_KEY_CHANNEL_BEGIN "begin" #define PATCH_KEY_CHANNEL_END "end" #define PATCH_KEY_CHANNEL_BOOST "boost" #define PATCH_KEY_CHANNEL_REC_ACTIVE "rec_active" #define PATCH_KEY_CHANNEL_PITCH "pitch" #define PATCH_KEY_CHANNEL_INPUT_MONITOR "input_monitor" #define PATCH_KEY_CHANNEL_MIDI_IN_READ_ACTIONS "midi_in_read_actions" #define PATCH_KEY_CHANNEL_MIDI_IN_PITCH "midi_in_pitch" #define PATCH_KEY_CHANNEL_MIDI_OUT "midi_out" #define PATCH_KEY_CHANNEL_MIDI_OUT_CHAN "midi_out_chan" #define PATCH_KEY_CHANNEL_PLUGINS "plugins" #define PATCH_KEY_CHANNEL_ACTIONS "actions" #define PATCH_KEY_CHANNEL_ARMED "armed" #define PATCH_KEY_ACTION_TYPE "type" #define PATCH_KEY_ACTION_FRAME "frame" #define PATCH_KEY_ACTION_F_VALUE "f_value" #define PATCH_KEY_ACTION_I_VALUE "i_value" #define PATCH_KEY_PLUGIN_PATH "path" #define PATCH_KEY_PLUGIN_BYPASS "bypass" #define PATCH_KEY_PLUGIN_PARAMS "params" #define PATCH_KEY_PLUGIN_MIDI_IN_PARAMS "midi_in_params" #define PATCH_KEY_COLUMN_INDEX "index" #define PATCH_KEY_COLUMN_WIDTH "width" #define PATCH_KEY_COLUMN_CHANNELS "channels" /* JSON config keys */ #define CONF_KEY_HEADER "header" #define CONF_KEY_LOG_MODE "log_mode" #define CONF_KEY_SOUND_SYSTEM "sound_system" #define CONF_KEY_SOUND_DEVICE_IN "sound_device_in" #define CONF_KEY_SOUND_DEVICE_OUT "sound_device_out" #define CONF_KEY_CHANNELS_IN "channels_in" #define CONF_KEY_CHANNELS_OUT "channels_out" #define CONF_KEY_SAMPLERATE "samplerate" #define CONF_KEY_BUFFER_SIZE "buffer_size" #define CONF_KEY_DELAY_COMPENSATION "delay_compensation" #define CONF_KEY_LIMIT_OUTPUT "limit_output" #define CONF_KEY_RESAMPLE_QUALITY "resample_quality" #define CONF_KEY_MIDI_SYSTEM "midi_system" #define CONF_KEY_MIDI_PORT_OUT "midi_port_out" #define CONF_KEY_MIDI_PORT_IN "midi_port_in" #define CONF_KEY_NO_NOTE_OFF "no_note_off" #define CONF_KEY_MIDIMAP_PATH "midimap_path" #define CONF_KEY_LAST_MIDIMAP "last_midimap" #define CONF_KEY_MIDI_SYNC "midi_sync" #define CONF_KEY_MIDI_TC_FPS "midi_tc_fps" #define CONF_KEY_MIDI_IN "midi_in" #define CONF_KEY_MIDI_IN_FILTER "midi_in_filter" #define CONF_KEY_MIDI_IN_REWIND "midi_in_rewind" #define CONF_KEY_MIDI_IN_START_STOP "midi_in_start_stop" #define CONF_KEY_MIDI_IN_ACTION_REC "midi_in_action_rec" #define CONF_KEY_MIDI_IN_INPUT_REC "midi_in_input_rec" #define CONF_KEY_MIDI_IN_METRONOME "midi_in_metronome" #define CONF_KEY_MIDI_IN_VOLUME_IN "midi_in_volume_in" #define CONF_KEY_MIDI_IN_VOLUME_OUT "midi_in_volume_out" #define CONF_KEY_MIDI_IN_BEAT_DOUBLE "midi_in_beat_doble" #define CONF_KEY_MIDI_IN_BEAT_HALF "midi_in_beat_half" #define CONF_KEY_RECS_STOP_ON_CHAN_HALT "recs_stop_on_chan_halt" #define CONF_KEY_CHANS_STOP_ON_SEQ_HALT "chans_stop_on_seq_halt" #define CONF_KEY_TREAT_RECS_AS_LOOPS "treat_recs_as_loops" #define CONF_KEY_RESIZE_RECORDINGS "resize_recordings" #define CONF_KEY_INPUT_MONITOR_DEFAULT_ON "input_monitor_default_on" #define CONF_KEY_PLUGINS_PATH "plugins_path" #define CONF_KEY_PATCHES_PATH "patches_path" #define CONF_KEY_SAMPLES_PATH "samples_path" #define CONF_KEY_MAIN_WINDOW_X "main_window_x" #define CONF_KEY_MAIN_WINDOW_Y "main_window_y" #define CONF_KEY_MAIN_WINDOW_W "main_window_w" #define CONF_KEY_MAIN_WINDOW_H "main_window_h" #define CONF_KEY_BROWSER_X "browser_x" #define CONF_KEY_BROWSER_Y "browser_y" #define CONF_KEY_BROWSER_W "browser_w" #define CONF_KEY_BROWSER_H "browser_h" #define CONF_KEY_BROWSER_POSITION "browser_position" #define CONF_KEY_BROWSER_LAST_PATH "browser_last_path" #define CONF_KEY_BROWSER_LAST_VALUE "browser_last_value" #define CONF_KEY_ACTION_EDITOR_X "action_editor_x" #define CONF_KEY_ACTION_EDITOR_Y "action_editor_y" #define CONF_KEY_ACTION_EDITOR_W "action_editor_w" #define CONF_KEY_ACTION_EDITOR_H "action_editor_h" #define CONF_KEY_ACTION_EDITOR_ZOOM "action_editor_zoom" #define CONF_KEY_ACTION_EDITOR_GRID_VAL "action_editor_grid_val" #define CONF_KEY_ACTION_EDITOR_GRID_ON "action_editor_grid_on" #define CONF_KEY_SAMPLE_EDITOR_X "sample_editor_x" #define CONF_KEY_SAMPLE_EDITOR_Y "sample_editor_y" #define CONF_KEY_SAMPLE_EDITOR_W "sample_editor_w" #define CONF_KEY_SAMPLE_EDITOR_H "sample_editor_h" #define CONF_KEY_SAMPLE_EDITOR_GRID_VAL "sample_editor_grid_val" #define CONF_KEY_SAMPLE_EDITOR_GRID_ON "sample_editor_grid_on" #define CONF_KEY_PIANO_ROLL_Y "piano_roll_y" #define CONF_KEY_PIANO_ROLL_H "piano_roll_h" #define CONF_KEY_PLUGIN_LIST_X "plugin_list_x" #define CONF_KEY_PLUGIN_LIST_Y "plugin_list_y" #define CONF_KEY_CONFIG_X "config_x" #define CONF_KEY_CONFIG_Y "config_y" #define CONF_KEY_BPM_X "bpm_x" #define CONF_KEY_BPM_Y "bpm_y" #define CONF_KEY_BEATS_X "beats_x" #define CONF_KEY_BEATS_Y "beats_y" #define CONF_KEY_ABOUT_X "about_x" #define CONF_KEY_ABOUT_Y "about_y" #define CONF_KEY_NAME_X "name_x" #define CONF_KEY_NAME_Y "name_y" #define CONF_KEY_PLUGIN_CHOOSER_X "plugin_chooser_x" #define CONF_KEY_PLUGIN_CHOOSER_Y "plugin_chooser_y" #define CONF_KEY_PLUGIN_CHOOSER_W "plugin_chooser_w" #define CONF_KEY_PLUGIN_CHOOSER_H "plugin_chooser_h" #define CONF_KEY_MIDI_INPUT_X "midi_input_x" #define CONF_KEY_MIDI_INPUT_Y "midi_input_y" #define CONF_KEY_MIDI_INPUT_W "midi_input_w" #define CONF_KEY_MIDI_INPUT_H "midi_input_h" #define CONF_KEY_PLUGIN_SORT_METHOD "plugin_sort_method" /* JSON midimaps keys */ #define MIDIMAP_KEY_BRAND "brand" #define MIDIMAP_KEY_DEVICE "device" #define MIDIMAP_KEY_INIT_COMMANDS "init_commands" #define MIDIMAP_KEY_MUTE_ON "mute_on" #define MIDIMAP_KEY_MUTE_OFF "mute_off" #define MIDIMAP_KEY_SOLO_ON "solo_on" #define MIDIMAP_KEY_SOLO_OFF "solo_off" #define MIDIMAP_KEY_WAITING "waiting" #define MIDIMAP_KEY_PLAYING "playing" #define MIDIMAP_KEY_STOPPING "stopping" #define MIDIMAP_KEY_STOPPED "stopped" #define MIDIMAP_KEY_CHANNEL "channel" #define MIDIMAP_KEY_MESSAGE "message" #endif giada-0.14.5/src/core/graphics.cpp000066400000000000000000001736251322662744500167220ustar00rootroot00000000000000/* --------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * graphics * * --------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * ------------------------------------------------------------------ */ #include "graphics.h" const char *giada_logo_xpm[] = { "300 82 8 1", " c #181917", ". c #333432", "+ c #484A47", "@ c #5F615E", "# c #767875", "$ c #8D8F8C", "% c #A5A7A4", "& c #C5C7C4", " .#%%&$ ", " ..#%&&&&&# ", " +@#$%&&&&&&&&&@ ", " .. +&&&&&&&&&&&&&&. ", " +$%%#+ +%&&&&&&&&&&&&&. ", " #&&&&&%+ .+@@$&&&&&&&&&%. ", " #&&&&&&&$ +&&&&&&&&# ", " .&&&&&&&&%. #&&&&&&&@ ", " @&&&&&&&&$ +&&&&&&&+ ", " $&&&&&&&&# .&&&&&&&. ", " #&&&&&&&&. +&&&&&&%. ", " .&&&&&&&@ @&&&&&&$. ", " @&&&&&@ $&&&&&&# ", " .##@. %&&&&&&@ ", " &&&&&&&+ ", " .&&&&&&%. ", " @&&&&&&$. ", " .+@@###@+. ...... ... ++@@###@@. ....... .+@@###@@+. #&&&&&&# .+@@###@@+ ....... ", " .@$%%&&&&&&&%$+ +%%%%%%. .+@#$%%$. .@$%&&&&&&&&%$@. $%%%%%%+ .@#%%&&&&&&&&%$@ %&&&&&&@ .@$%%&&&&&&&%$#. @%%%%%%@ ", " #%&&&&&&&&&&&&&&$. #&&&&&& +@#$$%%%&&&&&%. .$%&&&&&&&&&&&&&&%@ .&&&&&&&+ #%&&&&&&&&&&&&&&&$+ &&&&&&&@ #%&&&&&&&&&&&&&&%#. %&&&&&&@ ", " +%&&&&&&&&&&&&&&&&&%@ .&&&&&&% .%&&&&&&&&&&&&$ #%&&&&&&&&&&&&&&&&&&$ +&&&&&&%. @%&&&&&&&&&&&&&&&&&&&# .&&&&&&%+ @%&&&&&&&&&&&&&&&&&&%. &&&&&&&+ ", " #&&&&&&&&&#+....@$&&&&@@&&&&&&$ .&&&&&&&&&&&&&# +%&&&&&&&&%#+....+#%&&&$#&&&&&&$. .$&&&&&&&&&$+.....@%&&&&$@&&&&&&%. .$&&&&&&&&&#@.....#%&&&%+&&&&&&%. ", " $&&&&&&&&@ .%&&&&&&&&&&@ .@$&&&&&&&&&&@ +%&&&&&&&%@ .#&&&&&&&&&&$ .%&&&&&&&&@ .$&&&&&&&&&&$ .%&&&&&&&&#. @&&&&&&&&&&%. ", " $&&&&&&&%. @&&&&&&&&&+ .%&&&&&&&%+ @&&&&&&&&# +%&&&&&&&&# +%&&&&&&&$. @&&&&&&&&&# .%&&&&&&&$. .%&&&&&&&&$ ", " %&&&&&&&# @&&&&&&&&. +%&&&&&&%. @&&&&&&&&# .%&&&&&&&@ +%&&&&&&&# @&&&&&&&&@ +%&&&&&&&# %&&&&&&&@ ", " $&&&&&&&$ #&&&&&&%. %&&&&&&% +&&&&&&&&@ +&&&&&&%+ .%&&&&&&&# @&&&&&&&+ .%&&&&&&&# &&&&&&&+ ", " @&&&&&&&$. #&&&&&&$ %&&&&&&$ .%&&&&&&&@ @&&&&&&%. .$&&&&&&&$ +&&&&&&%+ $&&&&&&&$ .&&&&&&&+ ", " +&&&&&&&%+ %&&&&&&# %&&&&&&# $&&&&&&&$ $&&&&&&% @&&&&&&&% #&&&&&&%. #&&&&&&&%. @&&&&&&%. ", " %&&&&&&&# &&&&&&&+ .%&&&&&&@ @&&&&&&&$ %&&&&&&$ +&&&&&&&&+ $&&&&&&$ +&&&&&&&%. $&&&&&&$. ", " @&&&&&&&%. .&&&&&&&. +%&&&&&%. .&&&&&&&&. .%&&&&&&# $&&&&&&&# %&&&&&&# .$&&&&&&&@ %&&&&&&# ", " .%&&&&&&&@ @&&&&&&&. @&&&&&&% @&&&&&&&# @&&&&&&&+ +&&&&&&&&. +&&&&&&&@ +&&&&&&&% .&&&&&&&@ ", " @&&&&&&&% $&&&&&&%. #&&&&&&% &&&&&&&&. #&&&&&&%. %&&&&&&&# @&&&&&&%+ .$&&&&&&&@ +&&&&&&&+ ", " .$&&&&&&&# %&&&&&&# $&&&&&&# #&&&&&&&# $&&&&&&% +&&&&&&&&. $&&&&&&%. +&&&&&&&% #&&&&&&%+ ", " +%&&&&&&&+ .&&&&&&&@ .%&&&&&&@ %&&&&&&&+ .%&&&&&&$ $&&&&&&&$ %&&&&&&% #&&&&&&&# %&&&&&&%. ", " @&&&&&&&% +&&&&&&&+ +%&&&&&&+ .&&&&&&&%. +&&&&&&&# &&&&&&&&+ +%&&&&&&$ .%&&&&&&&. &&&&&&&$ ", " $&&&&&&&@ #&&&&&&&. @&&&&&&&. #&&&&&&&# #&&&&&&&@ +&&&&&&&%. @&&&&&&&# .&&&&&&&% +&&&&&&&# ", " .%&&&&&&&. %&&&&&&%. #&&&&&&% %&&&&&&&+ $&&&&&&&+ #&&&&&&&$. $&&&&&&&+ @&&&&&&&@ #&&&&&&&@ ", " +&&&&&&&& &&&&&&&$. #&&&&&&$ &&&&&&&%. .%&&&&&&& %&&&&&&&# .%&&&&&&%. $&&&&&&&+ $&&&&&&%+ ", " +&&&&&&&$ +&&&&&&&# .$&&&&&&# .&&&&&&&$. +&&&&&&&% %&&&&&&&+ +%&&&&&&% %&&&&&&&. .%&&&&&&%. ", " @&&&&&&&@ $&&&&&&&@ .%&&&&&&+ +&&&&&&&# #&&&&&&&$ &&&&&&&&+ #&&&&&&&% &&&&&&&% @&&&&&&&$ ", " @&&&&&&&+ &&&&&&&&+ +%&&&&&&. @&&&&&&&@ .%&&&&&&&@ .&&&&&&&%. .%&&&&&&&# &&&&&&&# $&&&&&&&$ ", " #&&&&&&&. @&&&&&&&%. @&&&&&&& @&&&&&&&@ @&&&&&&&&+ .&&&&&&&%. @&&&&&&&&@ .&&&&&&&# +%&&&&&&&# ", " #&&&&&&&. %&&&&&&&$. #&&&&&&% #&&&&&&&+ .$&&&&&&&&. .&&&&&&&$. .$&&&&&&&&. .&&&&&&&@ $&&&&&&&&@ ", " #&&&&&&& #&&&&&&&&$ $&&&&&&# @&&&&&&&+ @&&&&&&&&& .&&&&&&&$. @&&&&&&&&& .&&&&&&&+ +%&&&&&&&%+ ", " @&&&&&&& .%&&&&&&&&# .%&&&&&&@ @&&&&&&%+ .%&&&&&&&&% &&&&&&&$. .%&&&&&&&&% &&&&&&&+ $&&&&&&&&%. ", " @&&&&&&&. $&&&&&&&&&@ +%&&&&&&. +&&&&&&&@ @&&&&&&&&&$ &&&&&&&%. #&&&&&&&&&$ &&&&&&&@ +&&&&&&&&&% ", " +&&&&&&&+ @&&&&&&&&&%+ +&&&&&&& &&&&&&&@ +&&&&&&&&&&# $&&&&&&%+ @&&&&&&&&&&# $&&&&&&# .%&&&&&&&&&% ", " .%&&&&&&@ +%&&$&&&&&&%. +&&&&&&& %&&&&&&# .&&&&&&&&&&&@ @&&&&&&&+ +&&&$%&&&&&&@ @&&&&&&$ .$&&&&&&&&&&$ ", " #&&&&&&$ .$&&##&&&&&&$ @&&&&&&& @&&&&&&%. .%&&%@%&&&&&&@ .&&&&&&&# .&&&$+%&&&&&&. .&&&&&&&. .#&&%@%&&&&&&$ ", " +&&&&&&&. .%&&% $&&&&&&# +&&&&&&&. +.&&&&&&&@ .%&&%+.%&&&&&&$ @+$&&&&&&&+ +&&&% +&&&&&&&+ .+ $&&&&&&$ .$&&&@ %&&&&&&% .# ", " $&&&&&&$ +%&&&+ %&&&&&&@ +&&&&&&&@ #&$#&&&&&&%+ @&&&&@ .$&&&&&&&+ #&&@&&&&&&&$. .#&&&%. +%&&&&&&# .$&$ +&&&&&&&+ +%&&&# $&&&&&&&# +&&$ ", " +%&&&&&&#. +#&&&%@ .%&&&&&&+ .%&&&&&&&+ .$&&%.%&&&&&&%+ +#&&&&@ #&&&&&&&%@..+@$&&&+@&&&&&&&%+ .@%&&&%+ .%&&&&&&&+ +%&&$. #&&&&&&%@ +#&&&&# @&&&&&&&&@...+#&&&# ", " @&&&&&&&$@+..++#%&&&%+ +&&&&&&%. $&&&&&&&%#@@#%&&%. .%&&&&&&%@+...+@$&&&&%+ +%&&&&&&&&%$%&&&&+ $&&&&&&&&$#@+@@#%&&&&$. #&&&&&&&&#@+@$&&&$. .$&&&&&&&#+...++#%&&&&@ .%&&&&&&&&%$%&&&&# ", " @&&&&&&&&%%%%&&&&&$. @&&&&&&% +%&&&&&&&&&&&&&$. +%&&&&&&&&%$%%&&&&&$. #&&&&&&&&&&&&&%+ .$&&&&&&&&&&&&&&&&&&# .%&&&&&&&&&&&&&&# .$&&&&&&&&%$%%&&&&&%+ @&&&&&&&&&&&&&%@ ", " @%&&&&&&&&&&&&&%@. #&&&&&&$ #&&&&&&&&&&&%@. .$&&&&&&&&&&&&&&%@. .%&&&&&&&&&&&#. @&&&&&&&&&&&&&&&$+ +&&&&&&&&&&&&%+ .#&&&&&&&&&&&&&&&#. $&&&&&&&&&&&$+ ", " .#%&&&&&&&&&%+. %&&&&&&# +%&&&&&&&%#. +$&&&&&&&&&&%@. .$&&&&&&&&#+ .#&&&&&&&&&&%#. .$&&&&&&&&%@. +$&&&&&&&&&&%@. .@&&&&&&&&$+. ", " .+#$%%$#+.. .%&&&&&&+ .@#$%$#+. .@#$%%$#@+ +@$%$#+. .+#$%%$#@+. +@$%$$@. .+#$%%$#@+. .@$%$#@. ", " +&&&&&&%. ", " #&&&&&&% ", " .%&&&&&&@ ", " @&&&&&&%. ", " $&&&&&&$ ", " @&&&&&&&+ ", " @#$#+ .$&&&&&&$ ", " $&&&&&# #&&&&&&% ", "#&&&&&&&@ @&&&&&&&+ ", "%&&&&&&&%. @&&&&&&%+ ", "%&&&&&&&&# #&&&&&&%. ", "@&&&&&&&&&@ .$&&&&&&#. ", " $&&&&&&&&&$+ +$&&&&&&%+ ", " +&&&&&&&&&&%#@@#$%&&&&&&&#. +. .+ +@. ++ .+ +++++ +. .+ ++ +++++. .++++. +@. .@+ .++++. .+++++++ +. +@. .+@. .++++. ++. ++. .+. .+@. .+ +. .+ .+. .+ .+++++++ ", " .$&&&&&&&&&&&&&&&&&&&%@ $%. %@ +&&%&%. .$$ +& .&&&&&&# .%@ +&. %&+ $&&&&&%. @&&&&&%. +&&%&%. .%&%&&+ #&&&&&&@ +&&&&&&%. &# +&&%&%. @&&%&%. +&&&&&&@ $&% +%&+ .$&@ @&&%&$. $% .%$ $% +&%. +%. +&&&&&&$. ", " +$%%&&&&&&&&&&%$@. +&# #% +&# %% $$ +& .&+ @&@ .%@ +&. +$&$ $$ .%% @% .%&. .&$ .%$. %%. #&+ #& .$&+ +&. &# +&# $%. @&# .%%. +&. $&+ $%&+ #%&+ .&%$ @&# +%$ $% .%# $% +&%$ +%. +&. ", " .++@###@@+. #&. .&+ $%. .&@ $$ +& .&+ %$ .%@ +&. ##$% $$ @&. @% %$ $%. @&. @&+ %$. #& .%@ +&. &# %% .&# %% .&@ +&. &# $#%$ $#&+ @%@%. %% @%+ $% .%# $% +&+%+ +%. +&. ", " .%$ $$ .&# %% $$ +& .&+ %$ .%@ +&. .%@+&+ $$ @&. @% #& &# +. %% #&. #& .%@ +&. &# .&@ $$ +&+ .$$ +&. %# $#@& +$@&+ $#.%@ +&@ .+. $% .%# $% +& $$ +%. +&. ", " @&#&. .&@ $& $$ +& .&#+++#&+ .%%####$&. +%. %$ $%.++@&$ @% +&..&@ &$ @&+ #&++++$%. +&$###@ &# +&+ #%.@&. #% +&+ ..#&+ $#.&@ ##+&+ .&..$$ @&. $&$###$&# $% +& +%@ +%. +&####+ ", " %&@ +&+ #& $$ +& .&%$$%%@ .%%####$&. #$ #& $&$$%&#. @% +&++&@ &# +&+ #&$$%&%+ +&$$$$# &# @&. #%.@&. #%.+%%%%%%@ $# $% .$+.&+ @% @%. @&. $&$###$&# $% +& #%.+%. +&$$$$@ ", " #&. .&@ $% $$ +& .&#+.$%. .%@ +&. .$%##$&+ $%.+@&@ @% +&..&@ &$ @&. #&+++$$ +&+ &# +&. #% @&. $$ +&#@@++ $# +&.+$.+&+ $%@#$&+ @&. $% .%# $% +& .%@+%. +&. ", " @& .%# &$ $$ +% .&+ .%@ .%@ +&. +%$###&$ $$ $% @% $& &$ .$+ $% #%. #& +&+ +&. &# .&@ .%$ +&@ .%# +&. $# .%### +&+ .&####&# .&@ .$+ $% .%# $% +& @%@%. +&. ", " @& #%. @&. #%. $% .&+ $% .%@ +&. @$. #& $$ +&+ @% +&@ #&. #&. +&+ .%@ #& .%# +&. &# $% +&+ %% @&+ +&. $# #&%+ +&+ @% #%. %% #%. $% .%# $% +& .$%%. +&. ", " @& .%%+.@&# .%%++#&@ .&+ +&+ .%@ +&. $# .&+ $$ %$ @&+++#&%. $%+.#&# #&@.+%%. #& @%. +&@+++++. &$++++. .%%+.@%# .%%..@&# +&. $# +&$ +&+ %# +&+ .%%+.#&# $% .%# $% +& +&&. +&@++++. %&", " @& .$&&%@ +%&&&@ .&+ %$ .%@ +&..%+ %$ $$ @&. @&&&&%@ .$%&&# @%&&$. #& .%@ +&&&&&&%+ &&&&&&$. .$&&%# .$&&%@ +&. $# .%# +&+.&. .%# .$&&&@ $% .%# $% +& $&. +&&&&&&%. &&"}; const char *loopRepeat_xpm[] = { "18 18 8 1", " c #181917", ". c #242523", "+ c #323331", "@ c #4D4F4C", "# c #646663", "$ c #787A77", "% c #919390", "& c #BFC1BD", "..................", "..................", "..................", "...&%#......#%&...", "...&&&%+..+%&&&...", "...$%&&%..%&&%$...", ".....$&&##&&$.....", "......%&%%&%......", "......$&&&&$......", "......$&&&&$......", "......%&%%&%......", ".....$&&##&&$.....", "...$%&&%..%&&%$...", "...&&&%+..+%&&&...", "...&%#......#%&...", "..................", "..................", ".................."}; const char *loopBasic_xpm[] = { "18 18 8 1", " c #181917", ". c #242523", "+ c #313230", "@ c #4D4F4C", "# c #666765", "$ c #787A77", "% c #919390", "& c #BEC0BD", "..................", "..................", "..................", "......#%&&%#......", "....+%&&&&&&%+....", "....%&&&%%&&&%....", "...#&&%+..+%&&#...", "...%&&+....+&&%...", "...&&%......%&&...", "...&&%......%&&...", "...%&&+....+&&%...", "...#&&%+..+%&&#...", "....%&&&%%&&&%....", "....+%&&&&&&%+....", "......#%&&%#......", "..................", "..................", ".................."}; const char *loopOnce_xpm[] = { "18 18 8 1", " c #181917", ". c #242523", "+ c #323331", "@ c #4D4F4C", "# c #646663", "$ c #787A77", "% c #919390", "& c #BFC1BD", "..................", "..................", "..................", "......$%&&%#......", "....+&&&&&&&&+....", "...+&&&&$$&&&&+...", "...$&&$....$&&$...", "...%&&......%&%...", "..................", "..................", "...%&&+.....&&&...", "...#&&$....$&&#...", "....%&&&%$&&&%....", "....+%&&&&&&%+....", "......#%&&%#......", "..................", "..................", ".................."}; const char *loopOnceBar_xpm[] = { "18 18 8 1", " c #242523", ". c #393A38", "+ c #545553", "@ c #747673", "# c #A3A5A2", "$ c #ADAFAC", "% c #B5B7B4", "& c #C7C9C6", " ", " ", " ", " @$&%#@ ", " .$&&&&&&$. ", " %&&#@@#&&$ ", " @&&@ @&&@ ", " %&# +%$+ #&$ ", " %&&% ", " %&&% ", " $&# +%%+ #&$ ", " @&&@ @&&@ ", " $&&#@@#&&$ ", " .$&&&&&&$. ", " @#&&#@ ", " ", " ", " "}; const char *oneshotBasic_xpm[] = { "18 18 8 1", " c #181917", ". c #242523", "+ c #313230", "@ c #4D4F4C", "# c #666765", "$ c #787A77", "% c #919390", "& c #BEC0BD", "..................", "..................", "..................", "..................", "..................", "..................", "..................", "..................", "..................", "..................", "..................", "..................", "...$$$$$$$$$$$$...", "...&&&&&&&&&&&&...", "...&&&&&&&&&&&&...", "..................", "..................", ".................."}; const char *oneshotRetrig_xpm[] = { "18 18 8 1", " c #181917", ". c #242523", "+ c #313230", "@ c #4D4F4C", "# c #666765", "$ c #787A77", "% c #919390", "& c #BEC0BD", "..................", "..................", "..................", "..................", "..................", "...$$$$$$$#@......", "...&&&&&&&&&&@....", "...&&&&&&&&&&&+...", "..........+$&&%...", "............%&&...", "............%&&...", "...........+&&%...", "...$$$$$$$%&&&#...", "...&&&&&&&&&&%....", "...&&&&&&&&%#.....", "..................", "..................", ".................."}; const char *oneshotPress_xpm[] = { "18 18 8 1", " c #181917", ". c #242523", "+ c #313230", "@ c #4D4F4C", "# c #666765", "$ c #787A77", "% c #919390", "& c #BEC0BD", "..................", "..................", "..................", "..................", "..................", "..................", "...+%&%+..........", "...%&&&%..........", "...&&&&&..........", "...$&&&$..........", "...+$&$+..........", "..................", "...$$$$$$$$$$$$...", "...&&&&&&&&&&&&...", "...&&&&&&&&&&&&...", "..................", "..................", ".................."}; const char *oneshotEndless_xpm[] = { "18 18 6 1", " c #242523", ". c #464745", "+ c #6D6F6C", "@ c #888A87", "# c #ADAFAC", "$ c #C6C8C5", " ", " ", " ", " ", " ", " .++. ", " @$$$$#. ", " @$$$$$$$. ", " .$$#. +$$@ ", " +$$. @$# ", " +$$ @$$ ", " .$$+ #$# ", " @@@$$$@@#$$+ ", " $$$$$$$$$$@ ", " $$$$$$$$#+ ", " ", " ", " "}; const char *updirOff_xpm[] = { "18 18 8 1", " c #242523", ". c #332F2E", "+ c #54494A", "@ c #6B5A5C", "# c #866C6B", "$ c #967B7A", "% c #987D7C", "& c #B18E8F", " ", " ", " ", " ", " @@ ", " #&&# ", " .#&&&&#. ", " .$&&&&&&$. ", " +@%&&&&%@+ ", " #&&&&# ", " #&&&&# ", " #&&&&# ", " #&&&&# ", " #&&&&# ", " .... ", " ", " ", " "}; const char *updirOn_xpm[] = { "18 18 8 1", " c #4D4F4C", ". c #555150", "+ c #706465", "@ c #7D6B6E", "# c #877373", "$ c #957978", "% c #9F8382", "& c #B18E8F", " ", " ", " ", " ", " ## ", " #&&# ", " .$&&&&$. ", " .%&&&&&&%. ", " +@%&&&&%@+ ", " $&&&&$ ", " $&&&&$ ", " $&&&&$ ", " $&&&&$ ", " $&&&&$ ", " .... ", " ", " ", " "}; const char *pause_xpm[] = { "23 23 8 1", " c #4D4F4C", ". c #514E53", "+ c #5C4F61", "@ c #6F507E", "# c #855098", "$ c #9551AE", "% c #A652C5", "& c #AE52D1", " ", " ", " ", " ", " ", " #+ ", " &%#. ", " &&&%@ ", " &&&&&$+ ", " &&&&&&&#. ", " &&&&&&&&%@ ", " &&&&&&&&&&# ", " &&&&&&&&%@. ", " &&&&&&&#. ", " &&&&&$+ ", " &&&%@ ", " &&#. ", " $+ ", " ", " ", " ", " ", " "}; const char *play_xpm[] = { "23 23 8 1", " c #242523", ". c #393534", "+ c #574B4C", "@ c #6E5B5A", "# c #7C6663", "$ c #8C7170", "% c #A48384", "& c #B18E8F", " ", " ", " ", " ", " ", " $. ", " &&@ ", " &&&%+ ", " &&&&&$. ", " &&&&&&&@ ", " &&&&&&&&%+ ", " &&&&&&&&&&# ", " &&&&&&&&%+ ", " &&&&&&&#. ", " &&&&&$. ", " &&&%+ ", " &&@ ", " $. ", " ", " ", " ", " ", " "}; const char *rewindOff_xpm[] = { "23 23 8 1", " c #242523", ". c #393534", "+ c #574B4C", "@ c #6E5B5A", "# c #7C6663", "$ c #8C7170", "% c #A48384", "& c #B18E8F", " ", " ", " ", " ", " ", " .$ ", " @&& ", " +%&&& ", " .$&&&&& ", " @&&&&&&& ", " +%&&&&&&&& ", " #&&&&&&&&&& ", " +%&&&&&&&& ", " .#&&&&&&& ", " .$&&&&& ", " +%&&& ", " @&& ", " .$ ", " ", " ", " ", " ", " "}; const char *rewindOn_xpm[] = { "23 23 8 1", " c #4D4F4C", ". c #514E53", "+ c #5C4F61", "@ c #6F507E", "# c #855098", "$ c #9551AE", "% c #A652C5", "& c #AE52D1", " ", " ", " ", " ", " ", " +# ", " .#%& ", " @%&&& ", " +$&&&&& ", " .#&&&&&&& ", " @%&&&&&&&& ", " #&&&&&&&&&& ", " .@%&&&&&&&& ", " .#&&&&&&& ", " +$&&&&& ", " @%&&& ", " .#&& ", " +$ ", " ", " ", " ", " ", " "}; // 18x18 /* const unsigned char giada_icon[] = { 0x00, 0x00, 0x00, 0xfc, 0xff, 0x00, 0xfe, 0xff, 0x01, 0xfe, 0xff, 0x01, 0x3e, 0xf0, 0x01, 0x1e, 0xe0, 0x01, 0x0e, 0xc3, 0x01, 0x8e, 0xff, 0x01, 0x8e, 0xc1, 0x01, 0x8e, 0xc1, 0x01, 0x8e, 0xc7, 0x01, 0x0e, 0xc7, 0x01, 0x1e, 0xc0, 0x01, 0x3e, 0xf0, 0x01, 0xfe, 0xff, 0x01, 0xfe, 0xff, 0x01, 0xfc, 0xff, 0x00, 0x00, 0x00, 0x00 }; */ const char *giada_icon[] = { "65 65 11 1", " c None", ". c #000000", "+ c #000100", "@ c #FFFFFF", "# c #FDFFFC", "$ c #CBCDC9", "% c #292B28", "& c #626461", "* c #484A47", "= c #888A87", "- c #A7A9A6", "....+++++++++++++++++++++++++++++++++++++++++++++++++++++++++....", ".@@@#####################$%+++++++++++++%&&*%+++++++&##%+++++....", ".@@#####################&++++++++++++=$#######-*+++++&$+++++++...", ".@@####################&++++++++++%-############$*+++++++++++++..", "+#####################*++++++++++&################-++++++++++++%+", "+####################*++++++++++=##################$+++++++++++*+", "+###################*++++++++++$####################$++++++++++=+", "+##################=++++++++++-######################-+++++++++-+", "+#################$++++++++++=#######################=+++++++++$+", "+#################%+++++++++*########################*+++++++++#+", "+################*++++++++++#########################%++++++++*#+", "+###############-++++++++++=########################$+++++++++&#+", "+###############%+++++++++%#########################-+++++++++-#+", "+##############-++++++++++-#########################&+++++++++$#+", "+##############%+++++++++%##########################*+++++++++##+", "+#############-++++++++++-##########################+++++++++%##+", "+#############%++++++++++##########################$+++++++++*##+", "+############$++++++++++&##########################=+++++++++=##+", "+############=++++++++++$##########################&+++++++++-##+", "+############*+++++++++%###########################%+++++++++$##+", "+############++++++++++=###########################++++++++++###+", "+###########$++++++++++$##########################-+++++++++*###+", "+###########=++++++++++###########################=+++++++++&###+", "+###########*+++++++++%###########################*+++++++++-###+", "+###########%+++++++++&###########################++++++++++$###+", "+###########%+++++++++-##########################=++++++++++####+", "+###########++++++++++$##########################%+++++++++%####+", "+###########++++++++++##########################$++++++++++&####+", "+##########$++++++++++##########################&++++++++++=####+", "+##########$+++++++++%#########################$+++++++++++-####+", "+##########$+++++++++%#########################&+++++++++++$####+", "+###########+++++++++*########################$++++++++++++#####+", "+###########+++++++++*########################*+++++++++++*#####+", "+###########%++++++++%#######################=++++++++++++&#####+", "+###########&+++++++++######################$+++++++++++++-#####+", "+###########=+++++++++$#####################%+++%+++++++++$#####+", "+###########$+++++++++$####################&+++%=+++++++++######+", "+############%++++++++&###################=++++$&++++++++%######+", "+############-+++++++++$#################&++++=#*++++++++&######+", "+#############+++++++++&################*++++*##+++++++++=######+", "+#############=+++++++++-#############$%++++%###+++++++++-######+", "+##############*+++++++++=##########$*+++++%###$+++++++++#######+", "+###############++++++++++&$######$&++++++&####=+++++++++#######+", "+###############$++++++++++++%&*%++++++++=#####&++++++++*#######+", "+################$%+++++++++++++++++++++-######%++++++++=#######+", "+##################&++++++++++++++++++=########+++++++++-#######+", "+###################$%+++++++++++++%=#########$+++++++++########+", "+#####################$=%++++++%*=-###########=++++++++%########+", "+##########################$$$################*++++++++&########+", "+#############################################+++++++++=########+", "+############################################$+++++++++$########+", "+############################################&++++++++%#########+", "+############################################+++++++++=#########+", "+###########################################=+++++++++##########+", "+###########################################%++++++++*##########+", "+##########################################=+++++++++-##########+", "+#########-==$############################$+++++++++&###########+", "+#######=++++++-##########################*+++++++++############+", "+######$++++++++$########################=+++++++++-############+", "+######=+++++++++$######################-+++++++++&#############+", "+######=+++++++++*#####################$+++++++++&##############.", ".@#####=++++++++++-###################-+++++++++=##############@.", ".@@####=++++++++++%##################=+++++++++=#############@@@.", ".@@@###=+++++++++++*###############$*++++++++%$##############@@@.", "....++++++++++++++++++++++++++++++++++++++++++++++++++++++++....."}; const char *recOff_xpm[] = { "23 23 8 1", " c #242523", ". c #342F2E", "+ c #3F3B3A", "@ c #594F4F", "# c #7A6663", "$ c #8C7170", "% c #A68384", "& c #B18E8F", " ", " ", " ", " ", " ", " @$%%$@ ", " .$&&&&&&$. ", " $&&&&&&&&$ ", " @&&&#++#&&&@ ", " $&&# #&&$ ", " %&&+ +&&% ", " %&&+ +&&% ", " $&&# #&&$ ", " @&&&#++#&&&@ ", " $&&&&&&&&$ ", " .$&&&&&&$. ", " @$%%$@ ", " ", " ", " ", " ", " ", " "}; const char *recOn_xpm[] = { "23 23 8 1", " c #4D4F4C", ". c #5F4E50", "+ c #6E4F50", "@ c #8C5050", "# c #AE5454", "$ c #BB5253", "% c #C55352", "& c #E85557", " ", " ", " ", " ", " ", " @$&&$@ ", " .%&&&&&&%. ", " %&&&&&&&&% ", " @&&&#++#&&&@ ", " $&&# #&&$ ", " &&&+ +&&& ", " &&&+ +&&& ", " $&&# #&&$ ", " @&&&#++#&&&@ ", " %&&&&&&&&% ", " .%&&&&&&%. ", " @$&&$@ ", " ", " ", " ", " ", " ", " "}; const char *inputRecOn_xpm[] = { "23 23 8 1", " c #524D4C", ". c #4D4F4C", "+ c #5D4F50", "@ c #8C5050", "# c #BB5253", "$ c #C45251", "% c #DD5256", "& c #EA5657", ".......................", ".......................", ".......................", ".......................", ".......................", "........ @#%%#@ .......", ".......+$&&&&&&$+......", "...... $&&&&&&&&$ .....", "......@&&&&&&&&&&@.....", "......#&&&&&&&&&&#.....", "......%&&&&&&&&&&%.....", "......%&&&&&&&&&&%.....", "......#&&&&&&&&&&#.....", "......@&&&&&&&&&&@.....", ".......$&&&&&&&&$......", ".......+$&&&&&&$+......", "........ @#%%#@ .......", ".......................", ".......................", ".......................", ".......................", ".......................", "......................."}; const char *inputRecOff_xpm[] = { "23 23 8 1", " c #242523", ". c #252724", "+ c #332F2E", "@ c #594E4F", "# c #896E6D", "$ c #8D7271", "% c #A68384", "& c #B18E8F", " ", " ", " ", " ", " ", " .@#%%#@. ", " +$&&&&&&$+ ", " .$&&&&&&&&$. ", " @&&&&&&&&&&@ ", " #&&&&&&&&&&# ", " %&&&&&&&&&&% ", " %&&&&&&&&&&% ", " #&&&&&&&&&&# ", " @&&&&&&&&&&@ ", " $&&&&&&&&$ ", " +$&&&&&&$+ ", " .@#%%#@. ", " ", " ", " ", " ", " ", " "}; const char *inputToOutputOn_xpm[] = { "10 10 8 1", " c #4D4F4C", ". c #585A57", "+ c #666765", "@ c #6F716E", "# c #939592", "$ c #999B98", "% c #AEB0AD", "& c #BCBEBB", " ", " ", " .#@ ", " #&&#+ ", " @$&%. ", " @$&%. ", " #&&#+ ", " .#@ ", " ", " "}; const char *inputToOutputOff_xpm[] = { "10 10 8 1", " c #242523", ". c #2E302D", "+ c #3A3B39", "@ c #4F514E", "# c #828481", "$ c #8B8D8A", "% c #A7A9A6", "& c #B9BBB7", " ", " ", " +$@ ", " .#&&#+ ", " @$&%. ", " @$&%. ", " .#&&#+ ", " +$@ ", " ", " "}; const char *muteOff_xpm[] = { "18 18 8 1", " c #242523", ". c #2E2F2D", "+ c #3B3C3A", "@ c #525451", "# c #6F716E", "$ c #878986", "% c #ADAFAC", "& c #C6C8C5", " ", " ", " ", " ", " ++. .++ ", " +&&$ $&&+ ", " +&&% %&&+ ", " +&%&++&%&+ ", " +&$&##&$&+ ", " +&#%$$%#&+ ", " +&#$%%$#&+ ", " +&#@&&@#&+ ", " +&#+&&+#&+ ", " .#@ ## @#. ", " ", " ", " ", " "}; const char *muteOn_xpm[] = { "18 18 8 1", " c #4D4F4C", ". c #585A57", "+ c #616260", "@ c #7A7C79", "# c #888A87", "$ c #989A97", "% c #B2B4B1", "& c #C6C8C5", " ", " ", " ", " ", " .. .. ", " +&&$ $&&+ ", " +&&% %&&+ ", " +&%&++&%&+ ", " +&$&@@&$&+ ", " +&#%$$%#&+ ", " +&#$&&$#&+ ", " +&#@&&@#&+ ", " +&#.&&.#&+ ", " .#+ ## +#. ", " ", " ", " ", " "}; const char *readActionOff_xpm[] = { "18 18 8 1", " c #242523", ". c #393B38", "+ c #555754", "@ c #6B6D6A", "# c #7F807E", "$ c #9C9E9B", "% c #B1B3B0", "& c #C3C5C2", " ", " ", " ", " ", " .... ", " %&&&&%+ ", " %&@@@&& ", " %% $&. ", " %&@@#&$ ", " %&&&&@ ", " %% +&$ ", " %% #&# ", " %% %&+ ", " @@ .#+ ", " ", " ", " ", " "}; const char *readActionOn_xpm[] = { "18 18 8 1", " c #4D4F4C", ". c #696B68", "+ c #7A7C79", "@ c #888A87", "# c #939592", "$ c #A7A9A6", "% c #B7B9B6", "& c #C4C6C3", " ", " ", " ", " ", " ", " %&&&&%. ", " %&++@&& ", " %% $& ", " %&@@#&$ ", " %&&&&@ ", " %% +&$ ", " %% #&# ", " %% %&. ", " +@ .@+ ", " ", " ", " ", " "}; const char *metronomeOff_xpm[] = { "13 13 8 1", " c #242523", ". c #2D2928", "+ c #34302F", "@ c #443D3C", "# c #4F4445", "$ c #685659", "% c #826A68", "& c #A18282", " ", " ", " . . ", " #% %# ", " .&+ +&. ", " %$ $% ", " @& &@ ", " &@ @& ", " $% %$ ", " +&. .&+ ", " %# #% ", " . . ", " "}; const char *metronomeOn_xpm[] = { "13 13 8 1", " c #4D4F4C", ". c #565150", "+ c #645C5C", "@ c #716465", "# c #837070", "$ c #8F7775", "% c #977C7B", "& c #A68787", " ", " ", " . . ", " @% %@ ", " .&. .&. ", " $# #$ ", " +& &+ ", " &+ +& ", " #$ $# ", " .&. .&. ", " %@ @% ", " . . ", " "}; const char *zoomInOff_xpm[] = { "18 18 8 1", " c None", ". c #252525", "+ c #262626", "@ c #535353", "# c #ACACAC", "$ c #AEAEAE", "% c #B1B1B1", "& c #C4C4C4", "++++++++++++++++++", "+................+", "+................+", "+................+", "+................+", "+.......@@.......+", "+.......#$.......+", "+.......#$.......+", "+....@%%&&%%@....+", "+....@%%&&%%@....+", "+.......#$.......+", "+.......#$.......+", "+.......@@.......+", "+................+", "+................+", "+................+", "+................+", "++++++++++++++++++"}; const char *zoomInOn_xpm[] = { "18 18 8 1", " c None", ". c #4E4E4E", "+ c #707070", "@ c #717171", "# c #B3B3B3", "$ c #B5B5B5", "% c #B7B7B7", "& c #C5C5C5", "..................", "..................", "..................", "..................", "..................", "........++........", "........#$........", "........#$........", ".....@%%&&%%@.....", ".....@%%&&%%@.....", "........#$........", "........#$........", "........++........", "..................", "..................", "..................", "..................", ".................."}; const char *zoomOutOff_xpm[] = { "18 18 5 1", " c None", ". c #252525", "+ c #262626", "@ c #9C9C9C", "# c #BBBBBB", "++++++++++++++++++", "+................+", "+................+", "+................+", "+................+", "+................+", "+................+", "+................+", "+......@##@......+", "+......@##@......+", "+................+", "+................+", "+................+", "+................+", "+................+", "+................+", "+................+", "++++++++++++++++++"}; const char *zoomOutOn_xpm[] = { "18 18 4 1", " c None", ". c #4E4E4E", "+ c #A7A7A7", "@ c #BEBEBE", "..................", "..................", "..................", "..................", "..................", "..................", "..................", "..................", ".......+@@+.......", ".......+@@+.......", "..................", "..................", "..................", "..................", "..................", "..................", "..................", ".................."}; const char *scrollRightOff_xpm[] = { "12 12 8 1", " c #181917", ". c #242523", "+ c #2E2F2D", "@ c #4D4F4C", "# c #5D5F5C", "$ c #828481", "% c #9B9D9A", "& c #BCBEBB", "............", "............", "...+........", "...&$@......", "...$&&%@....", "....+#%&%...", "....+#%&%...", "...$&&%#....", "...&$@......", "...+........", "............", "............"}; const char *scrollLeftOff_xpm[] = { "12 12 8 1", " c #181917", ". c #242523", "+ c #2E2F2D", "@ c #4D4F4C", "# c #5D5F5C", "$ c #828481", "% c #9B9D9A", "& c #BCBEBB", "............", "............", "........+...", "......@$&...", "....@%&&$...", "...%&%#+....", "...%&%#+....", "....#%&&$...", "......@$&...", "........+...", "............", "............"}; const char *scrollLeftOn_xpm[] = { "12 12 8 1", " c #4D4F4C", ". c #6B6D6A", "+ c #7B7D7A", "@ c #969895", "# c #A6A8A5", "$ c #B4B6B3", "% c #C0C2BF", "& c #FEFFFC", " ", " ", " ", " .@$ ", " +#%%@ ", " $%#+ ", " %%#+ ", " +$%%@ ", " .#$ ", " ", " ", " "}; const char *scrollRightOn_xpm[] = { "12 12 8 1", " c #4D4F4C", ". c #6B6D6A", "+ c #7B7D7A", "@ c #969895", "# c #A6A8A5", "$ c #B4B6B3", "% c #C0C2BF", "& c #FEFFFC", " ", " ", " ", " %@. ", " @%%#. ", " +#%# ", " +#%# ", " @%%#+ ", " %@. ", " ", " ", " "}; const char *soloOn_xpm[] = { "18 18 8 1", " c #4D4F4C", ". c #616360", "+ c #737572", "@ c #838582", "# c #929491", "$ c #A5A7A4", "% c #B1B3B0", "& c #C6C8C5", " ", " ", " ", " ", " .@+. ", " #&&&&# ", " .&$ %&. ", " &%+ .. ", " #&&&$. ", " .@$&&. ", " .#. @&@ ", " .&$. #&+ ", " #&&&&$ ", " .+@+ ", " ", " ", " ", " "}; const char *soloOff_xpm[] = { "18 18 8 1", " c #242523", ". c #3D3F3D", "+ c #525451", "@ c #666865", "# c #80827F", "$ c #979996", "% c #A7A9A6", "& c #C6C8C5", " ", " ", " ", " ", " .@@. ", " #&&&&# ", " .&$ %&. ", " &%+ .. ", " #&&&$+ ", " .@%&&. ", " +#. @&@ ", " .&$..#&+ ", " #&&&&$ ", " .@@+ ", " ", " ", " ", " "}; #ifdef WITH_VST const char *fxOff_xpm[] = { "18 18 8 1", " c #242523", ". c #40423F", "+ c #4D4E4C", "@ c #686A67", "# c #7B7D7A", "$ c #919390", "% c #AEB0AD", "& c #C1C3C0", " ", " ", " ", " ", " ", " ..... . . ", " $&&&$ $% @&. ", " $$ .&#&@ ", " $%##. @&$ ", " $%##. #&% ", " $$ .&@&# ", " $$ %$ @&. ", " .. + +. ", " ", " ", " ", " ", " "}; const char *fxOn_xpm[] = { "18 18 8 1", " c #4D4F4C", ". c #565855", "+ c #636562", "@ c #80827F", "# c #8E908D", "$ c #9FA19E", "% c #B1B3B0", "& c #C1C3C0", " ", " ", " ", " ", " ", " .++++ +. +. ", " $&&&$ $% @&. ", " $$ .&#&@ ", " $%##+ @&$ ", " $%##+ #&% ", " $$ +&@&# ", " $$ %$ @&+ ", " ++ .+. ++ ", " ", " ", " ", " ", " "}; const char *fxShiftUpOff_xpm[] = { "18 18 7 1", " c #242523", ". c #4D4F4C", "+ c #A3A5A2", "@ c #868885", "# c #C1C3C0", "$ c #313330", "% c #626361", " ", " ", " ", " ", " ", " ", " .+@ ", " @+#. ", " $#%+@ ", " %# %#$ ", " +@ $#% ", " $#. @+ ", " $. $. ", " ", " ", " ", " ", " "}; const char *fxShiftUpOn_xpm[] = { "18 18 5 1", " c #4D4F4C", ". c #70726F", "+ c #A5A7A4", "@ c #C1C3BF", "# c #8E908D", " ", " ", " ", " ", " ", " ", " .++ ", " +@@. ", " @.+# ", " .@ .@ ", " +# @. ", " .@. #+ ", " . . ", " ", " ", " ", " ", " "}; const char *fxShiftDownOff_xpm[] = { "18 18 7 1", " c #242523", ". c #4D4F4C", "+ c #A3A5A2", "@ c #313330", "# c #626361", "$ c #868885", "% c #C1C3C0", " ", " ", " ", " ", " ", " ", " .+@ #$ ", " @%# +$ ", " $+ .%@ ", " .%@$+ ", " +$%# ", " #%%@ ", " @.. ", " ", " ", " ", " ", " "}; const char *fxShiftDownOn_xpm[] = { "18 18 5 1", " c #4D4F4C", ". c #70726F", "+ c #A5A7A4", "@ c #C1C3BF", "# c #8E908D", " ", " ", " ", " ", " ", " ", " .+ .+ ", " @. +# ", " #+ .@. ", " .@.#+ ", " +#@. ", " #@@ ", " .. ", " ", " ", " ", " ", " "}; const char *vstLogo_xpm[] = { "65 38 8 1", " c #161715", ". c #2B2D2A", "+ c #474846", "@ c #6A6C69", "# c #8C8E8B", "$ c #A8AAA7", "% c #C7C9C6", "& c #EEF0ED", " @#############################################################+ ", "@#.............................................................$+", "#. .#", "#. .#", "#. ...... .. .#", "#. .@$$$####$%$#@.+&$ .#", "#. .#$$#+. +#$%%%%$ .#", "#. .$$#$ .#$$%$ .#", "#. ............. ....$$$$$ ++$$%$+@@@@@@@@@@@@@@@ .#", "#. ##$$$$$$%%%%@ %%&&&&%%$@ %&@#$$@@$%%&&&%%%&&&&& .#", "#. +$$$$$%@ .&%####%$@ $&$.$# #$%%%& @&%& .#", "#. +$$$$$% +&$###$%%&&$@. $&. #$%%%&. .%& .#", "#. @$$$$%$ %##$##$%&&&&&&%#.%# #$$%%&. @& .#", "#. $$$$$%+ #& #$$%%&&&&&&&%%$$@ #$$%%&. + .#", "#. .$$$$$% +&+ .#%&&&&&&&&%$$#$$# #$$%%&. .#", "#. @$$$$%$ %$ @%&&&&&&%$$###$$ #$$%%&. .#", "#. #$$$%%@ #& . +$&&&%$####$%$ #$$%%&. .#", "#. $$$%%% .&@ +%# .@$$$###$$% #$$$%&. .#", "#. +%$%%%$$% +$$+ #$#$$$% @$$$%&. .#", "#. #%%%%%&. +%$$ ##$$%$ @$$$%%. .#", "#. $$%%%@ +%$$$. #$$$%. @$$$$%. .#", "#. +%%%$ +%$$#$@ +$$%$ @#$$$%+ .#", "#. @%%. +%%%$$$$#@++.++@#$$$@ @@##$$$%%%$$@ .#", "#. #@ +&# .@@###$$$###@. @+++@@@@###$@ .#", "#. .#", "#. .#", "#. .#", "#. .#", "#. .#", "#. .@$$$$$$$$ .$%%%%%%# .#", "#. ....... .@@@@@@@@@. .#", "#. ........ @@@+@@@@@@@@@@+ .#", "@# ......... .####@@@@@@@@@@@+ #@", " @$$$$$$$$$$$$$$$.......... .@@@@@@@@@@@@@@@@@$$$$$$$$$$$$$$$$@ ", " ......... .@@@@@@@@@@@@@@@. ", " ........ @@@@@@@@@@. ", " ........... .@@@@@@@@@ ", " .......... .@@@@@@@@ "}; const char *fxRemoveOff_xpm[] = { "18 18 9 1", " c None", ". c #242623", "+ c #2F312E", "@ c #393A38", "# c #484A47", "$ c #5D5F5C", "% c #8E908D", "& c #9B9D9A", "* c #BDBFBC", "..................", "..................", "..................", "..................", "..................", ".....+#@..@#+.....", "......&*++*&......", "......@*%%*@......", ".......$**$.......", ".......#**#.......", "......+*&&*+......", "......%*@@*%......", "......@@..@@......", "..................", "..................", "..................", "..................", ".................."}; const char *fxRemoveOn_xpm[] = { "18 18 9 1", " c None", ". c #4D4F4C", "+ c #575956", "@ c #5C5D5B", "# c #666865", "$ c #787977", "% c #9C9E9B", "& c #A6A8A5", "* c #BFC1BE", "..................", "..................", "..................", "..................", "..................", "......#@..@#......", "......&*++*&......", "......@*%%*@......", ".......$**$.......", ".......#**#.......", "......+*&&*+......", "......%*@+*%......", "......@+..+@......", "..................", "..................", "..................", "..................", ".................."}; #endif // #ifdef WITH_VST const char *divideOn_xpm[] = { "18 18 7 1", " c #5A5A5A", ". c #696969", "+ c #757575", "@ c #8B8B8B", "# c #AAAAAA", "$ c #BBBBBB", "% c #BDBDBD", " ", " ", " ", " ", " ", " @@ ", " %$ ", " ++ ", " .########. ", " .########. ", " ++ ", " %$ ", " @@ ", " ", " ", " ", " ", " "}; const char *divideOff_xpm[] = { "18 18 8 1", " c #252525", ". c #3B3B3B", "+ c #4D4D4D", "@ c #6D6D6D", "# c #6E6E6E", "$ c #9C9C9C", "% c #B5B5B5", "& c #B7B7B7", " ", " ", " ", " ", " ", " @# ", " &% ", " ++ ", " .$$$$$$$$. ", " .$$$$$$$$. ", " ++ ", " &% ", " @# ", " ", " ", " ", " ", " "}; const char *multiplyOn_xpm[] = { "18 18 8 1", " c #595B58", ". c #737572", "+ c #747673", "@ c #8B8D8A", "# c #8D8F8C", "$ c #8E908D", "% c #8F918E", "& c #C7C9C6", " ", " ", " ", " ", " ", " + . ", " +&$ #&. ", " #&$#&# ", " @&&# ", " @&&% ", " @&#@&% ", " +&# #&+ ", " + . ", " ", " ", " ", " ", " "}; const char *multiplyOff_xpm[] = { "18 18 8 1", " c #242523", ". c #4A4C49", "+ c #4D4E4C", "@ c #6D6F6C", "# c #717370", "$ c #737572", "% c #757774", "& c #C7C9C6", " ", " ", " ", " ", " ", " + . ", " +&$ #&. ", " #&$#&# ", " @&&# ", " @&&% ", " @&$@&% ", " +&# #&+ ", " + . ", " ", " ", " ", " ", " "}; const char *channelStop_xpm[] = { "18 18 8 1", " c #242523", ". c #312D2C", "+ c #413A3A", "@ c #615253", "# c #73605F", "$ c #7A6663", "% c #9C7E7D", "& c #B08D8E", " ", " ", " ", " ", " ##. ", " $&%@ ", " $&&&%+ ", " $&&&&&$. ", " $&&&&&&&@ ", " $&&&&&&&@. ", " $&&&&&$. ", " $&&&%+ ", " $&&@ ", " $#. ", " . ", " ", " ", " "}; const char *channelPlay_xpm[] = { "18 18 8 1", " c #4D4F4C", ". c #554E56", "+ c #5A4D59", "@ c #605068", "# c #775086", "$ c #8A509C", "% c #9E50B5", "& c #AD52D0", " ", " ", " ", " . ", " $$. ", " $&%# ", " $&&&%@ ", " $&&&&&$. ", " $&&&&&&&#. ", " $&&&&&&&#. ", " $&&&&&$+ ", " $&&&%@ ", " $&&# ", " $$. ", " . ", " ", " ", " "}; const char *armOff_xpm[] = { "18 18 8 1", " c #242523", ". c #4F4445", "+ c #514647", "@ c #6D5C5E", "# c #8E7372", "$ c #AA8889", "% c #AC898A", "& c #B18E8F", " ", " ", " ", " ", " +#%%#. ", " @&&&&&&@ ", " +&&&&&&&&. ", " #&&&&&&&&# ", " %&&&&&&&&% ", " %&&&&&&&&% ", " #&&&&&&&&# ", " .&&&&&&&&. ", " @&&&&&&@ ", " .#%%#. ", " ", " ", " ", " "}; const char *armOn_xpm[] = { "18 18 8 1", " c #4D4F4C", ". c #6B5077", "+ c #805191", "@ c #9950AD", "# c #9751B3", "$ c #9553AD", "% c #AA52C9", "& c #AE52D1", " ", " ", " ", " ", " .#%%#. ", " +&&&&&&+ ", " .&&&&&&&&. ", " #&&&&&&&&@ ", " %&&&&&&&&% ", " %&&&&&&&&% ", " #&&&&&&&&$ ", " .&&&&&&&&. ", " +&&&&&&+ ", " .@%%$. ", " ", " ", " ", " "}; giada-0.14.5/src/core/graphics.h000066400000000000000000000062341322662744500163560ustar00rootroot00000000000000/* --------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * graphics * * --------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * ------------------------------------------------------------------ */ #ifndef G_GRAPHICS_H #define G_GRAPHICS_H extern const char *giada_logo_xpm[]; extern const char *loopRepeat_xpm[]; extern const char *loopBasic_xpm[]; extern const char *loopOnce_xpm[]; extern const char *loopOnceBar_xpm[]; extern const char *oneshotBasic_xpm[]; extern const char *oneshotRetrig_xpm[]; extern const char *oneshotPress_xpm[]; extern const char *oneshotEndless_xpm[]; extern const char *updirOff_xpm[]; extern const char *updirOn_xpm[]; extern const char *pause_xpm[]; extern const char *play_xpm[]; extern const char *zoomInOff_xpm[]; extern const char *zoomInOn_xpm[]; extern const char *zoomOutOff_xpm[]; extern const char *zoomOutOn_xpm[]; extern const char *scrollLeftOff_xpm[]; extern const char *scrollLeftOn_xpm[]; extern const char *scrollRightOff_xpm[]; extern const char *scrollRightOn_xpm[]; extern const char *rewindOff_xpm[]; extern const char *rewindOn_xpm[]; extern const char *recOff_xpm[]; extern const char *recOn_xpm[]; extern const char *metronomeOff_xpm[]; extern const char *metronomeOn_xpm[]; extern const char *inputRecOn_xpm[]; extern const char *inputRecOff_xpm[]; extern const char *inputToOutputOn_xpm[]; extern const char *inputToOutputOff_xpm[]; extern const char *divideOn_xpm[]; extern const char *divideOff_xpm[]; extern const char *multiplyOn_xpm[]; extern const char *multiplyOff_xpm[]; extern const char *muteOff_xpm[]; extern const char *muteOn_xpm[]; extern const char *soloOff_xpm[]; extern const char *soloOn_xpm[]; extern const char *armOff_xpm[]; extern const char *armOn_xpm[]; extern const char *readActionOn_xpm[]; extern const char *readActionOff_xpm[]; extern const char *channelStop_xpm[]; extern const char *channelPlay_xpm[]; #ifdef WITH_VST extern const char *fxOff_xpm[]; extern const char *fxOn_xpm[]; extern const char *fxShiftUpOn_xpm[]; extern const char *fxShiftUpOff_xpm[]; extern const char *fxShiftDownOn_xpm[]; extern const char *fxShiftDownOff_xpm[]; extern const char *fxRemoveOff_xpm[]; extern const char *fxRemoveOn_xpm[]; extern const char *vstLogo_xpm[]; #endif extern const char *giada_icon[]; #endif giada-0.14.5/src/core/init.cpp000066400000000000000000000130271322662744500160520ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include #ifdef __APPLE__ #include #endif #include "../utils/log.h" #include "../utils/fs.h" #include "../utils/gui.h" #include "../gui/dialogs/gd_mainWindow.h" #include "../gui/dialogs/gd_warnings.h" #include "../glue/main.h" #include "init.h" #include "mixer.h" #include "wave.h" #include "const.h" #include "clock.h" #include "channel.h" #include "mixerHandler.h" #include "patch.h" #include "conf.h" #include "pluginHost.h" #include "recorder.h" #include "midiMapConf.h" #include "kernelMidi.h" #include "kernelAudio.h" extern bool G_quit; extern gdMainWindow *G_MainWin; using namespace giada::m; void init_prepareParser() { time_t t; time (&t); gu_log("[init] Giada " G_VERSION_STR " - %s", ctime(&t)); conf::init(); conf::read(); patch::init(); if (!gu_logInit(conf::logMode)) gu_log("[init] log init failed! Using default stdout\n"); gu_log("[init] configuration file ready\n"); } /* -------------------------------------------------------------------------- */ void init_prepareKernelAudio() { kernelAudio::openDevice(); clock::init(conf::samplerate, conf::midiTCfps); mixer::init(clock::getTotalFrames(), kernelAudio::getRealBufSize()); recorder::init(); #ifdef WITH_VST /* If with Jack don't use buffer size stored in Conf. Use real buffersize from the soundcard (kernelAudio::realBufsize). */ if (conf::soundSystem == G_SYS_API_JACK) pluginHost::init(kernelAudio::getRealBufSize(), conf::samplerate); else pluginHost::init(conf::buffersize, conf::samplerate); pluginHost::sortPlugins(conf::pluginSortMethod); #endif } /* -------------------------------------------------------------------------- */ void init_prepareKernelMIDI() { kernelMidi::setApi(conf::midiSystem); kernelMidi::openOutDevice(conf::midiPortOut); kernelMidi::openInDevice(conf::midiPortIn); } /* -------------------------------------------------------------------------- */ void init_prepareMidiMap() { midimap::init(); midimap::setDefault(); if (midimap::read(conf::midiMapPath) != MIDIMAP_READ_OK) gu_log("[init_prepareMidiMap] MIDI map read failed!\n"); } /* -------------------------------------------------------------------------- */ void init_startGUI(int argc, char** argv) { G_MainWin = new gdMainWindow(G_GUI_WIDTH, G_GUI_HEIGHT, "", argc, argv); G_MainWin->resize(conf::mainWindowX, conf::mainWindowY, conf::mainWindowW, conf::mainWindowH); gu_updateMainWinLabel(patch::name == "" ? G_DEFAULT_PATCH_NAME : patch::name); /* never update the GUI elements if kernelAudio::getStatus() is bad, segfaults * are around the corner */ if (kernelAudio::getStatus()) gu_updateControls(); else gdAlert("Your soundcard isn't configured correctly.\n" "Check the configuration and restart Giada."); } /* -------------------------------------------------------------------------- */ void init_startKernelAudio() { if (kernelAudio::getStatus()) kernelAudio::startStream(); } /* -------------------------------------------------------------------------- */ void init_shutdown() { G_quit = true; /* store position and size of the main window for the next startup */ conf::mainWindowX = G_MainWin->x(); conf::mainWindowY = G_MainWin->y(); conf::mainWindowW = G_MainWin->w(); conf::mainWindowH = G_MainWin->h(); /* close any open subwindow, especially before cleaning PluginHost to * avoid mess */ gu_closeAllSubwindows(); gu_log("[init] all subwindows closed\n"); /* write configuration file */ if (!conf::write()) gu_log("[init] error while saving configuration file!\n"); else gu_log("[init] configuration saved\n"); /* if kernelAudio::getStatus() we close the kernelAudio FIRST, THEN the mixer. * The opposite could cause random segfaults (even now with RtAudio?). */ if (kernelAudio::getStatus()) { kernelAudio::closeDevice(); gu_log("[init] KernelAudio closed\n"); mixer::close(); gu_log("[init] Mixer closed\n"); } recorder::clearAll(); for (unsigned i=0; ihasActions = false; mixer::channels.at(i)->readActions = false; //if (mixer::channels.at(i)->type == CHANNEL_SAMPLE) // ((SampleChannel*)mixer::channels.at(i))->readActions = false; } gu_log("[init] Recorder cleaned up\n"); #ifdef WITH_VST pluginHost::freeAllStacks(&mixer::channels, &mixer::mutex_plugins); pluginHost::close(); gu_log("[init] PluginHost cleaned up\n"); #endif gu_log("[init] Giada " G_VERSION_STR " closed\n\n"); gu_logClose(); } giada-0.14.5/src/core/init.h000066400000000000000000000025531322662744500155210ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef G_INIT_H #define G_INIT_H void init_prepareParser(); void init_startGUI(int argc, char **argv); void init_prepareKernelAudio(); void init_prepareKernelMIDI(); void init_prepareMidiMap(); void init_startKernelAudio(); void init_shutdown(); #endif giada-0.14.5/src/core/kernelAudio.cpp000066400000000000000000000264001322662744500173500ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * KernelAudio * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include "../deps/rtaudio-mod/RtAudio.h" #include "../utils/log.h" #include "../glue/main.h" #include "conf.h" #include "mixer.h" #include "const.h" #include "kernelAudio.h" using std::string; using std::vector; namespace giada { namespace m { namespace kernelAudio { namespace { RtAudio *rtSystem = nullptr; bool status = false; unsigned numDevs = 0; bool inputEnabled = false; unsigned realBufsize = 0; // reale bufsize from the soundcard int api = 0; #ifdef __linux__ JackState jackState; jack_client_t *jackGetHandle() { return static_cast(rtSystem->rtapi_->__HACK__getJackClient()); } #endif }; // {anonymous} /* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */ bool getStatus() { return status; } /* -------------------------------------------------------------------------- */ int openDevice() { api = conf::soundSystem; gu_log("[KA] using system 0x%x\n", api); #if defined(__linux__) if (api == G_SYS_API_JACK && hasAPI(RtAudio::UNIX_JACK)) rtSystem = new RtAudio(RtAudio::UNIX_JACK); else if (api == G_SYS_API_ALSA && hasAPI(RtAudio::LINUX_ALSA)) rtSystem = new RtAudio(RtAudio::LINUX_ALSA); else if (api == G_SYS_API_PULSE && hasAPI(RtAudio::LINUX_PULSE)) rtSystem = new RtAudio(RtAudio::LINUX_PULSE); #elif defined(_WIN32) if (api == G_SYS_API_DS && hasAPI(RtAudio::WINDOWS_DS)) rtSystem = new RtAudio(RtAudio::WINDOWS_DS); else if (api == G_SYS_API_ASIO && hasAPI(RtAudio::WINDOWS_ASIO)) rtSystem = new RtAudio(RtAudio::WINDOWS_ASIO); else if (api == G_SYS_API_WASAPI && hasAPI(RtAudio::WINDOWS_WASAPI)) rtSystem = new RtAudio(RtAudio::WINDOWS_WASAPI); #elif defined(__APPLE__) if (api == G_SYS_API_CORE && hasAPI(RtAudio::MACOSX_CORE)) rtSystem = new RtAudio(RtAudio::MACOSX_CORE); #endif else { gu_log("[KA] No API available, nothing to do!\n"); return 0; } gu_log("[KA] Opening devices %d (out), %d (in), f=%d...\n", conf::soundDeviceOut, conf::soundDeviceIn, conf::samplerate); numDevs = rtSystem->getDeviceCount(); if (numDevs < 1) { gu_log("[KA] no devices found with this API\n"); closeDevice(); return 0; } else { gu_log("[KA] %d device(s) found\n", numDevs); for (unsigned i=0; iopenStream( &outParams, // output params conf::soundDeviceIn != -1 ? &inParams : nullptr, // input params if inDevice is selected RTAUDIO_FLOAT32, // audio format conf::samplerate, // sample rate &realBufsize, // buffer size in byte &mixer::masterPlay, // audio callback nullptr, // user data (unused) &options); status = true; return 1; } catch (RtAudioError &e) { gu_log("[KA] rtSystem init error: %s\n", e.getMessage().c_str()); closeDevice(); return 0; } } /* -------------------------------------------------------------------------- */ int startStream() { try { rtSystem->startStream(); gu_log("[KA] latency = %lu\n", rtSystem->getStreamLatency()); return 1; } catch (RtAudioError &e) { gu_log("[KA] Start stream error: %s\n", e.getMessage().c_str()); return 0; } } /* -------------------------------------------------------------------------- */ int stopStream() { try { rtSystem->stopStream(); return 1; } catch (RtAudioError &e) { gu_log("[KA] Stop stream error\n"); return 0; } } /* -------------------------------------------------------------------------- */ string getDeviceName(unsigned dev) { try { return static_cast(rtSystem->getDeviceInfo(dev)).name; } catch (RtAudioError &e) { gu_log("[KA] invalid device ID = %d\n", dev); return ""; } } /* -------------------------------------------------------------------------- */ int closeDevice() { if (rtSystem->isStreamOpen()) { #if defined(__linux__) || defined(__APPLE__) rtSystem->abortStream(); // stopStream seems to lock the thread #elif defined(_WIN32) rtSystem->stopStream(); // on Windows it's the opposite #endif rtSystem->closeStream(); delete rtSystem; rtSystem = nullptr; } return 1; } /* -------------------------------------------------------------------------- */ unsigned getMaxInChans(int dev) { if (dev == -1) return 0; try { return static_cast(rtSystem->getDeviceInfo(dev)).inputChannels; } catch (RtAudioError &e) { gu_log("[KA] Unable to get input channels\n"); return 0; } } /* -------------------------------------------------------------------------- */ unsigned getMaxOutChans(unsigned dev) { try { return static_cast(rtSystem->getDeviceInfo(dev)).outputChannels; } catch (RtAudioError &e) { gu_log("[KA] Unable to get output channels\n"); return 0; } } /* -------------------------------------------------------------------------- */ bool isProbed(unsigned dev) { try { return static_cast(rtSystem->getDeviceInfo(dev)).probed; } catch (RtAudioError &e) { return 0; } } /* -------------------------------------------------------------------------- */ unsigned getDuplexChans(unsigned dev) { try { return static_cast(rtSystem->getDeviceInfo(dev)).duplexChannels; } catch (RtAudioError &e) { return 0; } } /* -------------------------------------------------------------------------- */ bool isDefaultIn(unsigned dev) { try { return static_cast(rtSystem->getDeviceInfo(dev)).isDefaultInput; } catch (RtAudioError &e) { return 0; } } /* -------------------------------------------------------------------------- */ bool isDefaultOut(unsigned dev) { try { return static_cast(rtSystem->getDeviceInfo(dev)).isDefaultOutput; } catch (RtAudioError &e) { return 0; } } /* -------------------------------------------------------------------------- */ int getTotalFreqs(unsigned dev) { try { return static_cast(rtSystem->getDeviceInfo(dev)).sampleRates.size(); } catch (RtAudioError &e) { return 0; } } /* -------------------------------------------------------------------------- */ int getFreq(unsigned dev, int i) { try { return static_cast(rtSystem->getDeviceInfo(dev)).sampleRates.at(i); } catch (RtAudioError &e) { return 0; } } /* -------------------------------------------------------------------------- */ unsigned getRealBufSize() { return realBufsize; } /* -------------------------------------------------------------------------- */ bool isInputEnabled() { return inputEnabled; } /* -------------------------------------------------------------------------- */ unsigned countDevices() { return numDevs; } /* -------------------------------------------------------------------------- */ int getDefaultIn() { return rtSystem->getDefaultInputDevice(); } int getDefaultOut() { return rtSystem->getDefaultOutputDevice(); } /* -------------------------------------------------------------------------- */ int getDeviceByName(const char *name) { for (unsigned i=0; i APIs; RtAudio::getCompiledApi(APIs); for (unsigned i=0; i. * * -------------------------------------------------------------------------- */ #ifndef G_KERNELAUDIO_H #define G_KERNELAUDIO_H #include #ifdef __linux__ #include #include #include #endif class RtAudio; class Mixer; namespace giada { namespace m { namespace kernelAudio { #ifdef __linux__ struct JackState { bool running; double bpm; uint32_t frame; }; #endif int openDevice(); int closeDevice(); int startStream(); int stopStream(); bool getStatus(); bool isProbed(unsigned dev); bool isDefaultIn(unsigned dev); bool isDefaultOut(unsigned dev); bool isInputEnabled(); std::string getDeviceName(unsigned dev); unsigned getMaxInChans(int dev); unsigned getMaxOutChans(unsigned dev); unsigned getDuplexChans(unsigned dev); unsigned getRealBufSize(); unsigned countDevices(); int getTotalFreqs(unsigned dev); int getFreq(unsigned dev, int i); int getDeviceByName(const char *name); int getDefaultOut(); int getDefaultIn(); bool hasAPI(int API); #ifdef __linux__ void jackStart(); void jackStop(); void jackSetPosition(uint32_t frame); void jackSetBpm(double bpm); const JackState &jackTransportQuery(); #endif }}}; // giada::m::kernelAudio:: #endif giada-0.14.5/src/core/kernelMidi.cpp000066400000000000000000000156471322662744500172040ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include "const.h" #ifdef G_OS_MAC #include #else #include #endif #include "../utils/log.h" #include "midiDispatcher.h" #include "midiMapConf.h" #include "kernelMidi.h" using std::string; using std::vector; namespace giada { namespace m { namespace kernelMidi { namespace { bool status = false; int api = 0; RtMidiOut* midiOut = nullptr; RtMidiIn* midiIn = nullptr; unsigned numOutPorts = 0; unsigned numInPorts = 0; static void callback(double t, std::vector* msg, void* data) { if (msg->size() < 3) { //gu_log("[KM] MIDI received - unknown signal - size=%d, value=0x", (int) msg->size()); //for (unsigned i=0; isize(); i++) // gu_log("%X", (int) msg->at(i)); //gu_log("\n"); return; } midiDispatcher::dispatch(msg->at(0), msg->at(1), msg->at(2)); } /* -------------------------------------------------------------------------- */ void sendMidiLightningInitMsgs() { for(unsigned i=0; igetPortCount(); gu_log("[KM] %d output MIDI ports found\n", numOutPorts); for (unsigned i=0; i 0) { try { midiOut->openPort(port, getOutPortName(port)); gu_log("[KM] MIDI out port %d open\n", port); /* TODO - it shold send midiLightning message only if there is a map loaded and available in midimap:: */ sendMidiLightningInitMsgs(); return 1; } catch (RtMidiError &error) { gu_log("[KM] unable to open MIDI out port %d: %s\n", port, error.getMessage().c_str()); status = false; return 0; } } else return 2; } /* -------------------------------------------------------------------------- */ int openInDevice(int port) { try { midiIn = new RtMidiIn((RtMidi::Api) api, "Giada MIDI input"); status = true; } catch (RtMidiError &error) { gu_log("[KM] MIDI in device error: %s\n", error.getMessage().c_str()); status = false; return 0; } /* print input ports */ numInPorts = midiIn->getPortCount(); gu_log("[KM] %d input MIDI ports found\n", numInPorts); for (unsigned i=0; i 0) { try { midiIn->openPort(port, getInPortName(port)); midiIn->ignoreTypes(true, false, true); // ignore all system/time msgs, for now gu_log("[KM] MIDI in port %d open\n", port); midiIn->setCallback(&callback); return 1; } catch (RtMidiError &error) { gu_log("[KM] unable to open MIDI in port %d: %s\n", port, error.getMessage().c_str()); status = false; return 0; } } else return 2; } /* -------------------------------------------------------------------------- */ bool hasAPI(int API) { vector APIs; RtMidi::getCompiledApi(APIs); for (unsigned i=0; igetPortName(p); } catch (RtMidiError &error) { return ""; } } string getInPortName(unsigned p) { try { return midiIn->getPortName(p); } catch (RtMidiError &error) { return ""; } } /* -------------------------------------------------------------------------- */ void send(uint32_t data) { if (!status) return; vector msg(1, getB1(data)); msg.push_back(getB2(data)); msg.push_back(getB3(data)); midiOut->sendMessage(&msg); gu_log("[KM] send msg=0x%X (%X %X %X)\n", data, msg[0], msg[1], msg[2]); } /* -------------------------------------------------------------------------- */ void send(int b1, int b2, int b3) { if (!status) return; vector msg(1, b1); if (b2 != -1) msg.push_back(b2); if (b3 != -1) msg.push_back(b3); midiOut->sendMessage(&msg); //gu_log("[KM] send msg=(%X %X %X)\n", b1, b2, b3); } /* -------------------------------------------------------------------------- */ unsigned countInPorts() { return numInPorts; } unsigned countOutPorts() { return numOutPorts; } /* -------------------------------------------------------------------------- */ int getB1(uint32_t iValue) { return (iValue >> 24) & 0xFF; } int getB2(uint32_t iValue) { return (iValue >> 16) & 0xFF; } int getB3(uint32_t iValue) { return (iValue >> 8) & 0xFF; } uint32_t getIValue(int b1, int b2, int b3) { return (b1 << 24) | (b2 << 16) | (b3 << 8) | (0x00); } /* -------------------------------------------------------------------------- */ uint32_t setChannel(uint32_t iValue, int channel) { uint32_t chanMask = 0xF << 24; return (iValue & (~chanMask)) | (channel << 24); } /* -------------------------------------------------------------------------- */ bool getStatus() { return status; } }}}; // giada::m::kernelMidi:: giada-0.14.5/src/core/kernelMidi.h000066400000000000000000000045201322662744500166350ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef G_KERNELMIDI_H #define G_KERNELMIDI_H #ifdef __APPLE__ // our Clang still doesn't know about cstdint (c++11 stuff) #include #else #include #endif #include namespace giada { namespace m { namespace kernelMidi { int getB1(uint32_t iValue); int getB2(uint32_t iValue); int getB3(uint32_t iValue); uint32_t getIValue(int b1, int b2, int b3); /* setChannel Changes MIDI channel number inside iValue. Returns new message with updated channel. */ uint32_t setChannel(uint32_t iValue, int channel); /* send * send a MIDI message 's' (uint32_t). */ void send(uint32_t s); /* send (2) * send separate bytes of MIDI message. */ void send(int b1, int b2=-1, int b3=-1); /* setApi * set the Api in use for both in & out messages. */ void setApi(int api); /* getStatus Returns current engine status. */ bool getStatus(); /* open/close/in/outDevice */ int openOutDevice(int port); int openInDevice(int port); int closeInDevice(); int closeOutDevice(); /* getIn/OutPortName * return the name of the port 'p'. */ std::string getInPortName(unsigned p); std::string getOutPortName(unsigned p); unsigned countInPorts(); unsigned countOutPorts(); bool hasAPI(int API); }}}; // giada::m::kernelMidi:: #endif giada-0.14.5/src/core/midiChannel.cpp000066400000000000000000000201501322662744500173150ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include "../utils/log.h" #include "midiChannel.h" #include "channel.h" #include "patch.h" #include "const.h" #include "clock.h" #include "conf.h" #include "mixer.h" #ifdef WITH_VST #include "pluginHost.h" #endif #include "kernelMidi.h" using std::string; using namespace giada::m; MidiChannel::MidiChannel(int bufferSize) : Channel (CHANNEL_MIDI, STATUS_OFF, bufferSize), midiOut (false), midiOutChan(MIDI_CHANS[0]) { } /* -------------------------------------------------------------------------- */ MidiChannel::~MidiChannel() {} /* -------------------------------------------------------------------------- */ void MidiChannel::copy(const Channel *_src, pthread_mutex_t *pluginMutex) { Channel::copy(_src, pluginMutex); MidiChannel *src = (MidiChannel *) _src; midiOut = src->midiOut; midiOutChan = src->midiOutChan; } /* -------------------------------------------------------------------------- */ #ifdef WITH_VST void MidiChannel::addVstMidiEvent(uint32_t msg, int localFrame) { juce::MidiMessage message = juce::MidiMessage( kernelMidi::getB1(msg), kernelMidi::getB2(msg), kernelMidi::getB3(msg)); midiBuffer.addEvent(message, localFrame); } #endif /* -------------------------------------------------------------------------- */ void MidiChannel::onBar(int frame) {} /* -------------------------------------------------------------------------- */ void MidiChannel::stop() {} /* -------------------------------------------------------------------------- */ void MidiChannel::empty() {} /* -------------------------------------------------------------------------- */ void MidiChannel::quantize(int index, int localFrame) {} /* -------------------------------------------------------------------------- */ void MidiChannel::parseAction(recorder::action *a, int localFrame, int globalFrame, int quantize, bool mixerIsRunning) { if (a->type == G_ACTION_MIDI) sendMidi(a, localFrame/2); } /* -------------------------------------------------------------------------- */ void MidiChannel::onZero(int frame, bool recsStopOnChanHalt) { if (status == STATUS_ENDING) { status = STATUS_OFF; sendMidiLplay(); } else if (status == STATUS_WAIT) { status = STATUS_PLAY; sendMidiLplay(); } } /* -------------------------------------------------------------------------- */ void MidiChannel::setMute(bool internal) { mute = true; // internal mute does not exist for midi (for now) if (midiOut) kernelMidi::send(MIDI_ALL_NOTES_OFF); #ifdef WITH_VST addVstMidiEvent(MIDI_ALL_NOTES_OFF, 0); #endif sendMidiLmute(); } /* -------------------------------------------------------------------------- */ void MidiChannel::unsetMute(bool internal) { mute = false; // internal mute does not exist for midi (for now) sendMidiLmute(); } /* -------------------------------------------------------------------------- */ void MidiChannel::process(float *outBuffer, float *inBuffer) { #ifdef WITH_VST pluginHost::processStack(vChan, pluginHost::CHANNEL, this); #endif /* TODO - isn't this useful only if WITH_VST ? */ for (int j=0; jmidiOut; midiOutChan = pch->midiOutChan; return G_RES_OK; } /* -------------------------------------------------------------------------- */ void MidiChannel::sendMidi(recorder::action *a, int localFrame) { if (status & (STATUS_PLAY | STATUS_ENDING) && !mute) { if (midiOut) kernelMidi::send(a->iValue | MIDI_CHANS[midiOutChan]); #ifdef WITH_VST addVstMidiEvent(a->iValue, localFrame); #endif } } void MidiChannel::sendMidi(uint32_t data) { if (status & (STATUS_PLAY | STATUS_ENDING) && !mute) { if (midiOut) kernelMidi::send(data | MIDI_CHANS[midiOutChan]); #ifdef WITH_VST addVstMidiEvent(data, 0); #endif } } /* -------------------------------------------------------------------------- */ void MidiChannel::rewind() { if (midiOut) kernelMidi::send(MIDI_ALL_NOTES_OFF); #ifdef WITH_VST addVstMidiEvent(MIDI_ALL_NOTES_OFF, 0); #endif } /* -------------------------------------------------------------------------- */ int MidiChannel::writePatch(int i, bool isProject) { int pchIndex = Channel::writePatch(i, isProject); patch::channel_t *pch = &patch::channels.at(pchIndex); pch->midiOut = midiOut; pch->midiOutChan = midiOutChan; return 0; } /* -------------------------------------------------------------------------- */ void MidiChannel::receiveMidi(const MidiEvent& midiEvent) { if (!armed) return; /* Now all messages are turned into Channel-0 messages. Giada doesn't care about holding MIDI channel information. Moreover, having all internal messages on channel 0 is way easier. */ MidiEvent midiEventFlat(midiEvent); midiEventFlat.setChannel(0); #ifdef WITH_VST while (true) { if (pthread_mutex_trylock(&pluginHost::mutex_midi) != 0) continue; gu_log("[Channel::processMidi] msg=%X\n", midiEventFlat.getRaw()); addVstMidiEvent(midiEventFlat.getRaw(), 0); pthread_mutex_unlock(&pluginHost::mutex_midi); break; } #endif if (recorder::canRec(this, clock::isRunning(), mixer::recording)) { recorder::rec(index, G_ACTION_MIDI, clock::getCurrentFrame(), midiEventFlat.getRaw()); hasActions = true; } } /* -------------------------------------------------------------------------- */ bool MidiChannel::canInputRec() { return false; // midi channels don't handle input audio } /* -------------------------------------------------------------------------- */ void MidiChannel::clear() {} giada-0.14.5/src/core/midiChannel.h000066400000000000000000000057371322662744500170000ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef G_MIDI_CHANNEL_H #define G_MIDI_CHANNEL_H #ifdef WITH_VST #include "../deps/juce-config.h" #endif #include "channel.h" class MidiMapConf; class Patch; class MidiChannel : public Channel { public: MidiChannel(int bufferSize); ~MidiChannel(); bool midiOut; // enable midi output uint8_t midiOutChan; // midi output channel void copy(const Channel* src, pthread_mutex_t* pluginMutex) override; void clear() override; void process(float* outBuffer, float *inBuffer) override; void preview(float* outBuffer) override; void start(int frame, bool doQuantize, int quantize, bool mixerIsRunning, bool forceStart, bool isUserGenerated) override; void kill(int frame) override; void empty() override; void stopBySeq(bool chansStopOnSeqHalt) override; void stop() override; void rewind() override; void setMute(bool internal) override; void unsetMute(bool internal) override; int readPatch(const std::string& basePath, int i, pthread_mutex_t* pluginMutex, int samplerate, int rsmpQuality) override; int writePatch(int i, bool isProject) override; void quantize(int index, int localFrame) override; void onZero(int frame, bool recsStopOnChanHalt) override; void onBar(int frame) override; void parseAction(giada::m::recorder::action* a, int localFrame, int globalFrame, int quantize, bool mixerIsRunning) override; void receiveMidi(const giada::m::MidiEvent& midiEvent) override; bool canInputRec() override; /* sendMidi * send Midi event to the outside world. */ void sendMidi(giada::m::recorder::action* a, int localFrame); void sendMidi(uint32_t data); #ifdef WITH_VST /* addVstMidiEvent * Add a new Midi event to the midiEvent stack fom a composite uint32_t raw * Midi event. LocalFrame is the offset: it tells where to put the event * inside the buffer. */ void addVstMidiEvent(uint32_t msg, int localFrame); #endif }; #endif giada-0.14.5/src/core/midiDispatcher.cpp000066400000000000000000000204451322662744500200420ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include #include "../glue/plugin.h" #include "../glue/io.h" #include "../glue/channel.h" #include "../glue/transport.h" #include "../glue/main.h" #include "../utils/log.h" #include "channel.h" #include "sampleChannel.h" #include "midiChannel.h" #include "conf.h" #include "mixer.h" #include "pluginHost.h" #include "plugin.h" #include "midiDispatcher.h" using std::vector; namespace giada { namespace m { namespace midiDispatcher { namespace { /* cb_midiLearn, cb_data Callback prepared by the gdMidiGrabber window and called by midiDispatcher. It contains things to do once the midi message has been stored. */ cb_midiLearn* cb_learn = nullptr; void* cb_data = nullptr; /* -------------------------------------------------------------------------- */ #ifdef WITH_VST void processPlugins(Channel* ch, const MidiEvent& midiEvent) { /* Pure value: if 'noNoteOff' in global config, get the raw value with the 'velocy' byte. Otherwise strip it off. */ uint32_t pure = midiEvent.getRaw(conf::noNoteOff); /* Plugins' parameters layout reflects the structure of the matrix Channel::midiInPlugins. It is safe to assume then that i (i.e. Plugin*) and k indexes match both the structure of Channel::midiInPlugins and vector* plugins. */ vector* plugins = pluginHost::getStack(pluginHost::CHANNEL, ch); for (Plugin* plugin : *plugins) { for (unsigned k=0; kmidiInParams.size(); k++) { uint32_t midiInParam = plugin->midiInParams.at(k); if (pure != midiInParam) continue; float vf = midiEvent.getVelocity() / 127.0f; c::plugin::setParameter(plugin, k, vf, false); // false: not from GUI gu_log(" >>> [plugin %d parameter %d] ch=%d (pure=0x%X, value=%d, float=%f)\n", plugin->getId(), k, ch->index, pure, midiEvent.getVelocity(), vf); } } } #endif /* -------------------------------------------------------------------------- */ void processChannels(const MidiEvent& midiEvent) { /* Pure value: if 'noNoteOff' in global config, get the raw value with the 'velocy' byte. Otherwise strip it off. */ uint32_t pure = midiEvent.getRaw(conf::noNoteOff); for (Channel* ch : mixer::channels) { /* Do nothing on this channel if MIDI in is disabled or filtered out for the current MIDI channel. */ if (!ch->midiIn || !ch->isMidiInAllowed(midiEvent.getChannel())) continue; if (pure == ch->midiInKeyPress) { gu_log(" >>> keyPress, ch=%d (pure=0x%X)\n", ch->index, pure); glue_keyPress(ch, false, false); } else if (pure == ch->midiInKeyRel) { gu_log(" >>> keyRel ch=%d (pure=0x%X)\n", ch->index, pure); glue_keyRelease(ch, false, false); } else if (pure == ch->midiInMute) { gu_log(" >>> mute ch=%d (pure=0x%X)\n", ch->index, pure); glue_toggleMute(ch, false); } else if (pure == ch->midiInKill) { gu_log(" >>> kill ch=%d (pure=0x%X)\n", ch->index, pure); glue_kill(ch); } else if (pure == ch->midiInArm) { gu_log(" >>> arm ch=%d (pure=0x%X)\n", ch->index, pure); glue_toggleArm(ch, false); } else if (pure == ch->midiInSolo) { gu_log(" >>> solo ch=%d (pure=0x%X)\n", ch->index, pure); glue_toggleSolo(ch, false); } else if (pure == ch->midiInVolume) { float vf = midiEvent.getVelocity() / 127.0f; gu_log(" >>> volume ch=%d (pure=0x%X, value=%d, float=%f)\n", ch->index, pure, midiEvent.getVelocity(), vf); glue_setVolume(ch, vf, false); } else { SampleChannel* sch = static_cast(ch); if (pure == sch->midiInPitch) { float vf = midiEvent.getVelocity() / (127/4.0f); // [0-127] ~> [0.0-4.0] gu_log(" >>> pitch ch=%d (pure=0x%X, value=%d, float=%f)\n", sch->index, pure, midiEvent.getVelocity(), vf); glue_setPitch(sch, vf); } else if (pure == sch->midiInReadActions) { gu_log(" >>> toggle read actions ch=%d (pure=0x%X)\n", sch->index, pure); glue_toggleReadingRecs(sch, false); } } #ifdef WITH_VST processPlugins(ch, midiEvent); // Process plugins' parameters #endif /* Redirect full midi message (pure + velocity) to plugins. */ ch->receiveMidi(midiEvent.getRaw()); } } /* -------------------------------------------------------------------------- */ void processMaster(const MidiEvent& midiEvent) { /* Pure value: if 'noNoteOff' in global config, get the raw value with the 'velocy' byte. Otherwise strip it off. */ uint32_t pure = midiEvent.getRaw(conf::noNoteOff); if (pure == conf::midiInRewind) { gu_log(" >>> rewind (master) (pure=0x%X)\n", pure); glue_rewindSeq(false); } else if (pure == conf::midiInStartStop) { gu_log(" >>> startStop (master) (pure=0x%X)\n", pure); glue_startStopSeq(false); } else if (pure == conf::midiInActionRec) { gu_log(" >>> actionRec (master) (pure=0x%X)\n", pure); glue_startStopActionRec(false); } else if (pure == conf::midiInInputRec) { gu_log(" >>> inputRec (master) (pure=0x%X)\n", pure); glue_startStopInputRec(false); } else if (pure == conf::midiInMetronome) { gu_log(" >>> metronome (master) (pure=0x%X)\n", pure); glue_startStopMetronome(false); } else if (pure == conf::midiInVolumeIn) { float vf = midiEvent.getVelocity() / 127.0f; gu_log(" >>> input volume (master) (pure=0x%X, value=%d, float=%f)\n", pure, midiEvent.getVelocity(), vf); glue_setInVol(vf, false); } else if (pure == conf::midiInVolumeOut) { float vf = midiEvent.getVelocity() / 127.0f; gu_log(" >>> output volume (master) (pure=0x%X, value=%d, float=%f)\n", pure, midiEvent.getVelocity(), vf); glue_setOutVol(vf, false); } else if (pure == conf::midiInBeatDouble) { gu_log(" >>> sequencer x2 (master) (pure=0x%X)\n", pure); glue_beatsMultiply(); } else if (pure == conf::midiInBeatHalf) { gu_log(" >>> sequencer /2 (master) (pure=0x%X)\n", pure); glue_beatsDivide(); } } } // {anonymous} /* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */ void startMidiLearn(cb_midiLearn* cb, void* data) { cb_learn = cb; cb_data = data; } /* -------------------------------------------------------------------------- */ void stopMidiLearn() { cb_learn = nullptr; cb_data = nullptr; } /* -------------------------------------------------------------------------- */ void dispatch(int byte1, int byte2, int byte3) { /* Here we want to catch two things: a) note on/note off from a keyboard and b) knob/wheel/slider movements from a controller. */ MidiEvent midiEvent(byte1, byte2, byte3); gu_log("[midiDispatcher] MIDI received - 0x%X (chan %d)\n", midiEvent.getRaw(), midiEvent.getChannel()); /* Start dispatcher. If midi learn is on don't parse channels, just learn incoming MIDI signal. Learn callback wants 'pure' MIDI event: if 'noNoteOff' in global config, get the raw value with the 'velocy' byte. Otherwise strip it off. If midi learn is off process master events first, then each channel in the stack. This way incoming signals don't get processed by glue_* when MIDI learning is on. */ if (cb_learn) cb_learn(midiEvent.getRaw(conf::noNoteOff), cb_data); else { processMaster(midiEvent); processChannels(midiEvent); } } }}}; // giada::m::midiDispatcher:: giada-0.14.5/src/core/midiDispatcher.h000066400000000000000000000030731322662744500175050ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef G_MIDI_DISPATCHER_H #define G_MIDI_DISPATCHER_H #ifdef __APPLE__ // our Clang still doesn't know about cstdint (c++11 stuff) #include #else #include #endif namespace giada { namespace m { namespace midiDispatcher { typedef void (cb_midiLearn) (uint32_t, void*); void startMidiLearn(cb_midiLearn* cb, void* data); void stopMidiLearn(); void dispatch(int byte1, int byte2, int byte3); }}}; // giada::m::midiDispatcher:: #endif giada-0.14.5/src/core/midiEvent.cpp000066400000000000000000000052621322662744500170350ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include "midiEvent.h" namespace giada { namespace m { MidiEvent::MidiEvent() : m_raw (0x0), m_status (0), m_channel (0), m_note (0), m_velocity(0), m_delta (0) { } /* -------------------------------------------------------------------------- */ MidiEvent::MidiEvent(uint32_t raw) : m_raw (raw), m_status ((raw & 0xF0000000) >> 24), m_channel ((raw & 0x0F000000) >> 24), m_note ((raw & 0x00FF0000) >> 16), m_velocity((raw & 0x0000FF00) >> 8), m_delta (0) // not used { } /* -------------------------------------------------------------------------- */ MidiEvent::MidiEvent(int byte1, int byte2, int byte3) : MidiEvent((byte1 << 24) | (byte2 << 16) | (byte3 << 8) | (0x00)) { } /* -------------------------------------------------------------------------- */ void MidiEvent::resetDelta() { m_delta = 0; } /* -------------------------------------------------------------------------- */ void MidiEvent::setChannel(int c) { m_channel = c; } /* -------------------------------------------------------------------------- */ int MidiEvent::getStatus() const { return m_status; } int MidiEvent::getChannel() const { return m_channel; } int MidiEvent::getNote() const { return m_note; } int MidiEvent::getVelocity() const { return m_velocity; } bool MidiEvent::isNoteOnOff() const { return m_status == NOTE_ON || m_status == NOTE_OFF; } int MidiEvent::getDelta() const { return m_delta; } uint32_t MidiEvent::getRaw(bool velocity) const { if (!velocity) return m_raw & 0xFFFF0000; return m_raw; } }} // giada::m::giada-0.14.5/src/core/midiEvent.h000066400000000000000000000035351322662744500165030ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef G_MIDI_EVENT_H #define G_MIDI_EVENT_H #include namespace giada { namespace m { class MidiEvent { public: static const int NOTE_ON = 0x90; static const int NOTE_OFF = 0x80; MidiEvent(); MidiEvent(uint32_t raw); MidiEvent(int byte1, int byte2, int byte3); int getStatus() const; int getChannel() const; int getNote() const; int getVelocity() const; bool isNoteOnOff() const; int getDelta() const; /* getRaw Returns the raw message. If 'velocity' is false, velocity byte is stripped out. */ uint32_t getRaw(bool velocity=true) const; void resetDelta(); void setChannel(int c); private: uint32_t m_raw; int m_status; int m_channel; int m_note; int m_velocity; int m_delta; }; }} // giada::m:: #endifgiada-0.14.5/src/core/midiMapConf.cpp000066400000000000000000000164671322662744500173100ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include #include #include #include #include "../utils/string.h" #include "../utils/log.h" #include "../utils/fs.h" #include "const.h" #include "storager.h" #include "midiMapConf.h" using std::string; using std::vector; namespace giada { namespace m { namespace midimap { namespace { bool readInitCommands(json_t *jContainer) { json_t *jInitCommands = json_object_get(jContainer, MIDIMAP_KEY_INIT_COMMANDS); if (!storager::checkArray(jInitCommands, MIDIMAP_KEY_INIT_COMMANDS)) return 0; size_t commandIndex; json_t *jInitCommand; json_array_foreach(jInitCommands, commandIndex, jInitCommand) { string indexStr = "init command " + gu_iToString(commandIndex); if (!storager::checkObject(jInitCommand, indexStr.c_str())) return 0; message_t message; if (!storager::setInt(jInitCommand, MIDIMAP_KEY_CHANNEL, message.channel)) return 0; if (!storager::setString(jInitCommand, MIDIMAP_KEY_MESSAGE, message.valueStr)) return 0; message.value = strtoul(message.valueStr.c_str(), nullptr, 16); initCommands.push_back(message); } return 1; } /* -------------------------------------------------------------------------- */ bool readCommand(json_t *jContainer, message_t *msg, const string &key) { json_t *jCommand = json_object_get(jContainer, key.c_str()); if (!storager::checkObject(jCommand, key.c_str())) return 0; if (!storager::setInt(jCommand, MIDIMAP_KEY_CHANNEL, msg->channel)) return 0; if (!storager::setString(jCommand, MIDIMAP_KEY_MESSAGE, msg->valueStr)) return 0; return 1; } /* -------------------------------------------------------------------------- */ void parse(message_t *message) { /* Remove '0x' part from the original string. */ string input = message->valueStr.replace(0, 2, ""); /* Then transform string value into the actual uint32_t value, by parsing * each char (i.e. nibble) in the original string. Substitute 'n' with * zeros. */ string output; for (unsigned i=0, p=24; ioffset == -1) // do it once message->offset = p; } else output += input[i]; } /* from string to uint32_t */ message->value = strtoul(output.c_str(), nullptr, 16); gu_log("[parse] parsed chan=%d valueStr=%s value=%#x, offset=%d\n", message->channel, message->valueStr.c_str(), message->value, message->offset); } }; // {anonymous} /* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */ string brand; string device; vector initCommands; message_t muteOn; message_t muteOff; message_t soloOn; message_t soloOff; message_t waiting; message_t playing; message_t stopping; message_t stopped; string midimapsPath; vector maps; /* -------------------------------------------------------------------------- */ void init() { midimapsPath = gu_getHomePath() + G_SLASH + "midimaps" + G_SLASH; /* scan dir of midi maps and load the filenames into <>maps. */ gu_log("[init] scanning midimaps directory...\n"); DIR *dp; dirent *ep; dp = opendir(midimapsPath.c_str()); if (!dp) { gu_log("[init] unable to scan midimaps directory!\n"); return; } while ((ep = readdir(dp))) { if (!strcmp(ep->d_name, ".") || !strcmp(ep->d_name, "..")) continue; // TODO - check if is a valid midimap file (verify headers) gu_log("[init] found midimap '%s'\n", ep->d_name); maps.push_back(ep->d_name); } gu_log("[init] total midimaps found: %d\n", maps.size()); closedir(dp); } /* -------------------------------------------------------------------------- */ void setDefault() { brand = ""; device = ""; muteOn.channel = 0; muteOn.valueStr = ""; muteOn.offset = -1; muteOn.value = 0; muteOff.channel = 0; muteOff.valueStr = ""; muteOff.offset = -1; muteOff.value = 0; soloOn.channel = 0; soloOn.valueStr = ""; soloOn.offset = -1; soloOn.value = 0; soloOff.channel = 0; soloOff.valueStr = ""; soloOff.offset = -1; soloOff.value = 0; waiting.channel = 0; waiting.valueStr = ""; waiting.offset = -1; waiting.value = 0; playing.channel = 0; playing.valueStr = ""; playing.offset = -1; playing.value = 0; stopping.channel = 0; stopping.valueStr = ""; stopping.offset = -1; stopping.value = 0; stopped.channel = 0; stopped.valueStr = ""; stopped.offset = -1; stopped.value = 0; } /* -------------------------------------------------------------------------- */ int read(const string &file) { if (file.empty()) { gu_log("[read] midimap not specified, nothing to do\n"); return MIDIMAP_NOT_SPECIFIED; } gu_log("[read] reading midimap file '%s'\n", file.c_str()); json_error_t jError; string path = midimapsPath + file; json_t *jRoot = json_load_file(path.c_str(), 0, &jError); if (!jRoot) { gu_log("[read] unreadable midimap file. Error on line %d: %s\n", jError.line, jError.text); return MIDIMAP_UNREADABLE; } if (!storager::setString(jRoot, MIDIMAP_KEY_BRAND, brand)) return MIDIMAP_UNREADABLE; if (!storager::setString(jRoot, MIDIMAP_KEY_DEVICE, device)) return MIDIMAP_UNREADABLE; if (!readInitCommands(jRoot)) return MIDIMAP_UNREADABLE; if (!readCommand(jRoot, &muteOn, MIDIMAP_KEY_MUTE_ON)) return MIDIMAP_UNREADABLE; if (!readCommand(jRoot, &muteOff, MIDIMAP_KEY_MUTE_OFF)) return MIDIMAP_UNREADABLE; if (!readCommand(jRoot, &soloOn, MIDIMAP_KEY_SOLO_ON)) return MIDIMAP_UNREADABLE; if (!readCommand(jRoot, &soloOff, MIDIMAP_KEY_SOLO_OFF)) return MIDIMAP_UNREADABLE; if (!readCommand(jRoot, &waiting, MIDIMAP_KEY_WAITING)) return MIDIMAP_UNREADABLE; if (!readCommand(jRoot, &playing, MIDIMAP_KEY_PLAYING)) return MIDIMAP_UNREADABLE; if (!readCommand(jRoot, &stopping, MIDIMAP_KEY_STOPPING)) return MIDIMAP_UNREADABLE; if (!readCommand(jRoot, &stopped, MIDIMAP_KEY_STOPPED)) return MIDIMAP_UNREADABLE; /* parse messages */ parse(&muteOn); parse(&muteOff); parse(&soloOn); parse(&soloOff); parse(&waiting); parse(&playing); parse(&stopping); parse(&stopped); return MIDIMAP_READ_OK; } }}}; // giada::m::midimap:: giada-0.14.5/src/core/midiMapConf.h000066400000000000000000000043101322662744500167350ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef G_MIDIMAPCONF_H #define G_MIDIMAPCONF_H #include #include namespace giada { namespace m { namespace midimap { struct message_t { int channel; std::string valueStr; int offset; uint32_t value; }; extern std::string brand; extern std::string device; extern std::vector initCommands; extern message_t muteOn; extern message_t muteOff; extern message_t soloOn; extern message_t soloOff; extern message_t waiting; extern message_t playing; extern message_t stopping; extern message_t stopped; /* midimapsPath * path of midimap files, different between OSes. */ extern std::string midimapsPath; /* maps * Maps are the available .giadamap files. Each element of the std::vector * represents a .giadamap filename. */ extern std::vector maps; /* init Parse the midi maps folders and find the available maps. */ void init(); /* setDefault Set default values in case no maps are available/choosen. */ void setDefault(); /* read Read a midi map from file 'file'. */ int read(const std::string &file); }}}; // giada::m::midimap:: #endif giada-0.14.5/src/core/mixer.cpp000066400000000000000000000335051322662744500162360ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include #include #include "../deps/rtaudio-mod/RtAudio.h" #include "../utils/log.h" #include "wave.h" #include "kernelAudio.h" #include "recorder.h" #include "pluginHost.h" #include "conf.h" #include "mixerHandler.h" #include "clock.h" #include "const.h" #include "channel.h" #include "sampleChannel.h" #include "midiChannel.h" #include "mixer.h" namespace giada { namespace m { namespace mixer { namespace { #define TICKSIZE 38 float tock[TICKSIZE] = { 0.059033, 0.117240, 0.173807, 0.227943, 0.278890, 0.325936, 0.368423, 0.405755, 0.437413, 0.462951, 0.482013, 0.494333, 0.499738, 0.498153, 0.489598, 0.474195, 0.452159, 0.423798, 0.389509, 0.349771, 0.289883, 0.230617, 0.173194, 0.118739, 0.068260, 0.022631, -0.017423, -0.051339, -0.078721, -0.099345, -0.113163, -0.120295, -0.121028, -0.115804, -0.105209, -0.089954, -0.070862, -0.048844 }; float tick[TICKSIZE] = { 0.175860, 0.341914, 0.488904, 0.608633, 0.694426, 0.741500, 0.747229, 0.711293, 0.635697, 0.524656, 0.384362, 0.222636, 0.048496, -0.128348, -0.298035, -0.451105, -0.579021, -0.674653, -0.732667, -0.749830, -0.688924, -0.594091, -0.474481, -0.340160, -0.201360, -0.067752, 0.052194, 0.151746, 0.226280, 0.273493, 0.293425, 0.288307, 0.262252, 0.220811, 0.170435, 0.117887, 0.069639, 0.031320 }; /* -------------------------------------------------------------------------- */ /* lineInRec Records from line in. */ void lineInRec(float* inBuf, unsigned frame) { if (!mh::hasArmedSampleChannels() || !kernelAudio::isInputEnabled() || !recording) return; /* Delay comp: wait until waitRec reaches delayComp. WaitRec * returns to 0 in mixerHandler, as soon as the recording ends */ if (waitRec < conf::delayComp) { waitRec += 2; return; } vChanInput[inputTracker] += inBuf[frame] * inVol; vChanInput[inputTracker+1] += inBuf[frame+1] * inVol; inputTracker += 2; if (inputTracker >= clock::getTotalFrames()) inputTracker = 0; } /* -------------------------------------------------------------------------- */ /* ProcessLineIn Computes line in peaks, plus handles "hear what you're playin'" thing. */ void processLineIn(float* inBuf, unsigned frame) { if (!kernelAudio::isInputEnabled()) return; /* input peak calculation (left chan only so far). */ if (inBuf[frame] * inVol > peakIn) peakIn = inBuf[frame] * inVol; /* "hear what you're playing" - process, copy and paste the input buffer * onto the output buffer */ if (inToOut) { vChanInToOut[frame] = inBuf[frame] * inVol; vChanInToOut[frame+1] = inBuf[frame+1] * inVol; } } /* -------------------------------------------------------------------------- */ /* clearAllBuffers Cleans up every buffer, both in Mixer and in channels. */ void clearAllBuffers(float* outBuf, unsigned bufferSize) { memset(outBuf, 0, sizeof(float) * bufferSize); // out memset(vChanInToOut, 0, sizeof(float) * bufferSize); // inToOut vChan pthread_mutex_lock(&mutex_chans); for (Channel* channel : channels) channel->clear(); pthread_mutex_unlock(&mutex_chans); } /* -------------------------------------------------------------------------- */ /* readActions Reads all recorded actions. */ void readActions(unsigned frame) { pthread_mutex_lock(&mutex_recs); for (unsigned i=0; ichan; Channel *ch = mh::getChannelByIndex(index); ch->parseAction(recorder::global.at(i).at(j), frame, clock::getCurrentFrame(), clock::getQuantize(), clock::isRunning()); } break; } } pthread_mutex_unlock(&mutex_recs); } /* -------------------------------------------------------------------------- */ /* doQuantize Computes quantization on 'rewind' button and all channels. */ void doQuantize(unsigned frame) { /* Nothing to do if quantizer disabled or a quanto has not passed yet. */ if (clock::getQuantize() == 0 || !clock::quantoHasPassed()) return; if (rewindWait) { rewindWait = false; rewind(); } pthread_mutex_lock(&mutex_chans); for (unsigned i=0; iquantize(i, frame); pthread_mutex_unlock(&mutex_chans); } /* -------------------------------------------------------------------------- */ /* sumChannels Sums channels, i.e. lets them add sample frames to their virtual channels. This is required for CHANNEL_SAMPLE only */ void sumChannels(unsigned frame) { pthread_mutex_lock(&mutex_chans); for (unsigned k=0; ktype == CHANNEL_SAMPLE) static_cast(channels.at(k))->sum(frame, clock::isRunning()); } pthread_mutex_unlock(&mutex_chans); } /* -------------------------------------------------------------------------- */ /* renderMetronome Generates metronome when needed and pastes it to the output buffer. */ void renderMetronome(float* outBuf, unsigned frame) { if (tockPlay) { outBuf[frame] += tock[tockTracker]; outBuf[frame+1] += tock[tockTracker]; tockTracker++; if (tockTracker >= TICKSIZE-1) { tockPlay = false; tockTracker = 0; } } if (tickPlay) { outBuf[frame] += tick[tickTracker]; outBuf[frame+1] += tick[tickTracker]; tickTracker++; if (tickTracker >= TICKSIZE-1) { tickPlay = false; tickTracker = 0; } } } /* -------------------------------------------------------------------------- */ /* renderIO Final processing stage. Take each channel and process it (i.e. copy its content to the output buffer). Process plugins too, if any. */ void renderIO(float* outBuf, float* inBuf) { pthread_mutex_lock(&mutex_chans); for (Channel* channel : channels) { channel->process(outBuf, inBuf); channel->preview(outBuf); } pthread_mutex_unlock(&mutex_chans); #ifdef WITH_VST pthread_mutex_lock(&mutex_plugins); pluginHost::processStack(outBuf, pluginHost::MASTER_OUT); pluginHost::processStack(vChanInToOut, pluginHost::MASTER_IN); pthread_mutex_unlock(&mutex_plugins); #endif } /* -------------------------------------------------------------------------- */ /* limitOutput Applies a very dumb hard limiter. */ void limitOutput(float* outBuf, unsigned frame) { if (outBuf[frame] > 1.0f) outBuf[frame] = 1.0f; else if (outBuf[frame] < -1.0f) outBuf[frame] = -1.0f; if (outBuf[frame+1] > 1.0f) outBuf[frame+1] = 1.0f; else if (outBuf[frame+1] < -1.0f) outBuf[frame+1] = -1.0f; } /* -------------------------------------------------------------------------- */ /* computePeak */ void computePeak(float* outBuf, unsigned frame) { /* TODO it takes into account only left channel so far! */ if (outBuf[frame] > peakOut) peakOut = outBuf[frame]; } /* -------------------------------------------------------------------------- */ /* finalizeOutput Last touches after the output has been rendered: apply inToOut if any, apply output volume. */ void finalizeOutput(float* outBuf, unsigned frame) { /* merge vChanInToOut, if enabled */ if (inToOut) { outBuf[frame] += vChanInToOut[frame]; outBuf[frame+1] += vChanInToOut[frame+1]; } outBuf[frame] *= outVol; outBuf[frame+1] *= outVol; } /* -------------------------------------------------------------------------- */ /* test* Checks if the sequencer has reached a specific point (bar, first beat or last frame). */ void testBar(unsigned frame) { if (!clock::isOnBar()) return; if (metronome) tickPlay = true; pthread_mutex_lock(&mutex_chans); for (unsigned k=0; konBar(frame); pthread_mutex_unlock(&mutex_chans); } /* -------------------------------------------------------------------------- */ void testFirstBeat(unsigned frame) { if (!clock::isOnFirstBeat()) return; pthread_mutex_lock(&mutex_chans); for (unsigned k=0; konZero(frame, conf::recsStopOnChanHalt); pthread_mutex_unlock(&mutex_chans); } /* -------------------------------------------------------------------------- */ void testLastBeat() { if (clock::isOnBeat()) if (metronome && !tickPlay) tockPlay = true; } }; // {anonymous} /* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */ std::vector channels; bool recording = false; // is recording something? bool ready = true; float *vChanInput = nullptr; // virtual channel for recording float *vChanInToOut = nullptr; // virtual channel in->out bridge (hear what you're playin) float outVol = G_DEFAULT_OUT_VOL; float inVol = G_DEFAULT_IN_VOL; float peakOut = 0.0f; float peakIn = 0.0f; bool metronome = false; int waitRec = 0; // delayComp guard bool docross = false; // crossfade guard bool rewindWait = false; // rewind guard, if quantized int tickTracker, tockTracker = 0; bool tickPlay, tockPlay = false; // 1 = play, 0 = stop /* inputTracker * position of the sample in the input side (recording) */ int inputTracker = 0; /* inToOut * copy, process and paste the input into the output, in order to * obtain a "hear what you're playing" feature. */ bool inToOut = false; pthread_mutex_t mutex_recs; pthread_mutex_t mutex_chans; pthread_mutex_t mutex_plugins; /* -------------------------------------------------------------------------- */ void init(int framesInSeq, int audioBufferSize) { /* Allocate virtual input channels. vChanInput has variable size: it depends on how many frames there are in sequencer. */ allocVirtualInput(framesInSeq); if (vChanInToOut != nullptr) delete[] vChanInToOut; vChanInToOut = new (std::nothrow) float[audioBufferSize * 2]; if (!vChanInToOut) { gu_log("[Mixer::init] vChanInToOut alloc error!\n"); return; } pthread_mutex_init(&mutex_recs, nullptr); pthread_mutex_init(&mutex_chans, nullptr); pthread_mutex_init(&mutex_plugins, nullptr); rewind(); } /* -------------------------------------------------------------------------- */ void allocVirtualInput(int frames) { if (vChanInput != nullptr) delete[] vChanInput; vChanInput = new (std::nothrow) float[frames]; if (!vChanInput) gu_log("[Mixer::allocVirtualInput] vChanInput realloc error!\n"); gu_log("[Mixer::allocVirtualInput] vChanInput ready, %d frames\n", frames); } /* -------------------------------------------------------------------------- */ int masterPlay(void* _outBuf, void* _inBuf, unsigned bufferSize, double streamTime, RtAudioStreamStatus status, void* userData) { if (!ready) return 0; #ifdef __linux__ clock::recvJackSync(); #endif float* outBuf = (float*) _outBuf; float* inBuf = kernelAudio::isInputEnabled() ? (float*) _inBuf : nullptr; bufferSize *= 2; // stereo peakOut = 0.0f; // reset peak calculator peakIn = 0.0f; // reset peak calculator clearAllBuffers(outBuf, bufferSize); for (unsigned j=0; j 0) mh::deleteChannel(channels.at(0)); if (vChanInput != nullptr) { delete[] vChanInput; vChanInput = nullptr; } if (vChanInToOut != nullptr) { delete[] vChanInToOut; vChanInToOut = nullptr; } return 1; } /* -------------------------------------------------------------------------- */ bool isSilent() { for (unsigned i=0; istatus == STATUS_PLAY) return false; return true; } /* -------------------------------------------------------------------------- */ void rewind() { clock::rewind(); if (clock::isRunning()) for (Channel* ch : channels) ch->rewind(); } /* -------------------------------------------------------------------------- */ void mergeVirtualInput() { assert(vChanInput != nullptr); for (Channel* ch : channels) { if (ch->type == CHANNEL_MIDI) continue; SampleChannel* sch = static_cast(ch); if (sch->isArmed()) memcpy(sch->wave->getData(), vChanInput, clock::getTotalFrames() * sizeof(float)); } memset(vChanInput, 0, clock::getTotalFrames() * sizeof(float)); // clear vchan } }}}; // giada::m::mixer:: giada-0.14.5/src/core/mixer.h000066400000000000000000000062551322662744500157050ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef G_MIXER_H #define G_MIXER_H #include #include #include "../deps/rtaudio-mod/RtAudio.h" class Channel; namespace giada { namespace m { namespace mixer { void init(int framesInSeq, int audioBufferSize); /* allocVirtualInput Allocates new memory for the virtual input channel. Call this whenever you shrink or resize the sequencer. */ void allocVirtualInput(int frames); int close(); /* masterPlay Core method (callback) */ int masterPlay(void *outBuf, void *inBuf, unsigned bufferSize, double streamTime, RtAudioStreamStatus status, void *userData); /* isSilent Is mixer silent? */ bool isSilent(); /* rewind Rewinds sequencer to frame 0. */ void rewind(); /* mergeVirtualInput Copies the virtual channel input in the channels designed for input recording. Called by mixerHandler on stopInputRec(). */ void mergeVirtualInput(); enum { // const - what to do when a fadeout ends DO_STOP = 0x01, DO_MUTE = 0x02, DO_MUTE_I = 0x04 }; enum { // const - fade types FADEOUT = 0x01, XFADE = 0x02 }; extern std::vector channels; extern bool recording; // is recording something? extern bool ready; extern float *vChanInput; // virtual channel for recording extern float *vChanInToOut; // virtual channel in->out bridge (hear what you're playin) extern int frameSize; extern float outVol; extern float inVol; extern float peakOut; extern float peakIn; extern bool metronome; extern int waitRec; // delayComp guard extern bool docross; // crossfade guard extern bool rewindWait; // rewind guard, if quantized extern int tickTracker, tockTracker; extern bool tickPlay, tockPlay; // 1 = play, 0 = stop /* inputTracker * position of the sample in the input side (recording) */ extern int inputTracker; /* inToOut * copy, process and paste the input into the output, in order to * obtain a "hear what you're playing" feature. */ extern bool inToOut; extern pthread_mutex_t mutex_recs; extern pthread_mutex_t mutex_chans; extern pthread_mutex_t mutex_plugins; }}} // giada::m::mixer::; #endif giada-0.14.5/src/core/mixerHandler.cpp000066400000000000000000000217171322662744500175360ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include #include "../utils/fs.h" #include "../utils/string.h" #include "../utils/log.h" #include "../glue/main.h" #include "../glue/channel.h" #include "kernelMidi.h" #include "mixer.h" #include "const.h" #include "init.h" #include "pluginHost.h" #include "plugin.h" #include "waveFx.h" #include "conf.h" #include "patch.h" #include "recorder.h" #include "clock.h" #include "channel.h" #include "kernelAudio.h" #include "midiMapConf.h" #include "sampleChannel.h" #include "midiChannel.h" #include "wave.h" #include "waveManager.h" #include "mixerHandler.h" using std::vector; using std::string; namespace giada { namespace m { namespace mh { namespace { #ifdef WITH_VST int readPatchPlugins(vector* list, int type) { int ret = 1; for (unsigned i=0; isize(); i++) { patch::plugin_t *ppl = &list->at(i); // TODO use glue_addPlugin() Plugin *plugin = pluginHost::addPlugin(ppl->path.c_str(), type, &mixer::mutex_plugins, nullptr); if (plugin != nullptr) { plugin->setBypass(ppl->bypass); for (unsigned j=0; jparams.size(); j++) plugin->setParameter(j, ppl->params.at(j)); ret &= 1; } else ret &= 0; } return ret; } #endif /* ------------------------------------------------------------------------ */ int getNewChanIndex() { /* always skip last channel: it's the last one just added */ if (mixer::channels.size() == 1) return 0; int index = 0; for (unsigned i=0; iindex > index) index = mixer::channels.at(i)->index; } index += 1; return index; } }; // {anonymous} /* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */ bool uniqueSamplePath(const SampleChannel* skip, const string& path) { for (const Channel* ch : mixer::channels) { if (skip == ch || ch->type != CHANNEL_SAMPLE) // skip itself and MIDI channels continue; const SampleChannel* sch = static_cast(ch); if (sch->wave != nullptr && path == sch->wave->getPath()) return false; } return true; } /* -------------------------------------------------------------------------- */ Channel* addChannel(int type) { Channel* ch; int bufferSize = kernelAudio::getRealBufSize() * 2; if (type == CHANNEL_SAMPLE) ch = new SampleChannel(bufferSize, conf::inputMonitorDefaultOn); else ch = new MidiChannel(bufferSize); if (!ch->allocBuffers()) { delete ch; return nullptr; } while (true) { if (pthread_mutex_trylock(&mixer::mutex_chans) != 0) continue; mixer::channels.push_back(ch); pthread_mutex_unlock(&mixer::mutex_chans); break; } ch->index = getNewChanIndex(); gu_log("[addChannel] channel index=%d added, type=%d, total=%d\n", ch->index, ch->type, mixer::channels.size()); return ch; } /* -------------------------------------------------------------------------- */ int deleteChannel(Channel* ch) { int index = -1; for (unsigned i=0; iindex); return 0; } while (true) { if (pthread_mutex_trylock(&mixer::mutex_chans) != 0) continue; mixer::channels.erase(mixer::channels.begin() + index); delete ch; pthread_mutex_unlock(&mixer::mutex_chans); return 1; } } /* -------------------------------------------------------------------------- */ Channel* getChannelByIndex(int index) { for (unsigned i=0; iindex == index) return mixer::channels.at(i); gu_log("[getChannelByIndex] channel at index %d not found!\n", index); return nullptr; } /* -------------------------------------------------------------------------- */ bool hasLogicalSamples() { for (unsigned i=0; itype != CHANNEL_SAMPLE) continue; SampleChannel *ch = static_cast(mixer::channels.at(i)); if (ch->wave && ch->wave->isLogical()) return true; } return false; } /* -------------------------------------------------------------------------- */ bool hasEditedSamples() { for (unsigned i=0; itype != CHANNEL_SAMPLE) continue; SampleChannel *ch = static_cast(mixer::channels.at(i)); if (ch->wave && ch->wave->isEdited()) return true; } return false; } /* -------------------------------------------------------------------------- */ void stopSequencer() { clock::stop(); for (unsigned i=0; istopBySeq(conf::chansStopOnSeqHalt); } /* -------------------------------------------------------------------------- */ bool uniqueSolo(Channel* ch) { int solos = 0; for (unsigned i=0; isolo) solos++; if (solos > 1) return false; } return true; } /* -------------------------------------------------------------------------- */ void readPatch() { mixer::ready = false; mixer::outVol = patch::masterVolOut; mixer::inVol = patch::masterVolIn; clock::setBpm(patch::bpm); clock::setBars(patch::bars); clock::setBeats(patch::beats); clock::setQuantize(patch::quantize); clock::updateFrameBars(); mixer::metronome = patch::metronome; #ifdef WITH_VST readPatchPlugins(&patch::masterInPlugins, pluginHost::MASTER_IN); readPatchPlugins(&patch::masterOutPlugins, pluginHost::MASTER_OUT); #endif /* Rewind and update frames in Mixer. Also alloc new space in the virtual input buffer, in case the patch has a sequencer size != default one (which is very likely). */ mixer::rewind(); mixer::allocVirtualInput(clock::getTotalFrames()); mixer::ready = true; } /* -------------------------------------------------------------------------- */ void rewindSequencer() { if (clock::getQuantize() > 0 && clock::isRunning()) // quantize rewind mixer::rewindWait = true; else mixer::rewind(); } /* -------------------------------------------------------------------------- */ bool startInputRec() { int channelsReady = 0; for (Channel* channel : mixer::channels) { if (!channel->canInputRec()) continue; SampleChannel* ch = static_cast(channel); /* Allocate empty sample for the current channel. */ Wave* wave = nullptr; int result = waveManager::createEmpty(clock::getTotalFrames(), conf::samplerate, string("TAKE-" + gu_iToString(patch::lastTakeId)), &wave); if (result != G_RES_OK) { gu_log("[startInputRec] unable to allocate new Wave in chan %d!\n", ch->index); continue; } ch->pushWave(wave); ch->setName("TAKE-" + gu_iToString(patch::lastTakeId++)); // Increase lastTakeId channelsReady++; gu_log("[startInputRec] start input recs using chan %d with size %d " "frame=%d\n", ch->index, clock::getTotalFrames(), mixer::inputTracker); } if (channelsReady > 0) { mixer::recording = true; /* start to write from the currentFrame, not the beginning */ /** FIXME: this should be done before wave allocation */ mixer::inputTracker = clock::getCurrentFrame(); return true; } return false; } /* -------------------------------------------------------------------------- */ void stopInputRec() { mixer::mergeVirtualInput(); mixer::recording = false; mixer::waitRec = 0; // in case delay compensation is in use gu_log("[mh] stop input recs\n"); } /* -------------------------------------------------------------------------- */ bool hasArmedSampleChannels() { for (unsigned i=0; itype == CHANNEL_SAMPLE && ch->isArmed()) return true; } return false; } }}}; // giada::m::mh:: giada-0.14.5/src/core/mixerHandler.h000066400000000000000000000053151322662744500171770ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef G_MIXER_HANDLER_H #define G_MIXER_HANDLER_H #include class Channel; class SampleChannel; namespace giada { namespace m { namespace mh { /* addChannel Adds a new channel of type 'type' into mixer's stack. */ Channel* addChannel(int type); /* deleteChannel Completely removes a channel from the stack. */ int deleteChannel(Channel* ch); /* getChannelByIndex Returns channel with given index 'i'. */ Channel* getChannelByIndex(int i); /* hasLogicalSamples True if 1 or more samples are logical (memory only, such as takes) */ bool hasLogicalSamples(); /* hasEditedSamples True if 1 or more samples was edited via gEditor */ bool hasEditedSamples(); /* stopSequencer Stops the sequencer, with special case if samplesStopOnSeqHalt is true. */ void stopSequencer(); void rewindSequencer(); /* uniqueSolo * true if ch is the only solo'd channel in mixer. */ bool uniqueSolo(Channel* ch); /* loadPatch Loads a path or a project (if isProject) into Mixer. If isProject, path must contain the address of the project folder. */ void readPatch(); /* startInputRec - record from line in Creates a new empty wave in the first available channels. Returns false if something went wrong. */ bool startInputRec(); void stopInputRec(); /* uniqueSamplePath Returns true if path 'p' is unique. Requires SampleChannel 'skip' in order to skip check against itself. */ bool uniqueSamplePath(const SampleChannel* skip, const std::string& p); /* hasArmedSampleChannels Tells whether Mixer has one or more sample channels armed for input recording. */ bool hasArmedSampleChannels(); }}} // giada::m::mh:: #endif giada-0.14.5/src/core/patch.cpp000066400000000000000000000557041322662744500162160ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include "../utils/log.h" #include "../utils/string.h" #include "const.h" #include "storager.h" #include "conf.h" #include "mixer.h" #include "patch.h" using std::string; using std::vector; namespace giada { namespace m { namespace patch { namespace { /* sanitize Internal sanity check. */ void sanitize() { bpm = bpm < G_MIN_BPM || bpm > G_MAX_BPM ? G_DEFAULT_BPM : bpm; bars = bars <= 0 || bars > G_MAX_BARS ? G_DEFAULT_BARS : bars; beats = beats <= 0 || beats > G_MAX_BEATS ? G_DEFAULT_BEATS : beats; quantize = quantize < 0 || quantize > G_MAX_QUANTIZE ? G_DEFAULT_QUANTIZE : quantize; masterVolIn = masterVolIn < 0.0f || masterVolIn > 1.0f ? G_DEFAULT_VOL : masterVolIn; masterVolOut = masterVolOut < 0.0f || masterVolOut > 1.0f ? G_DEFAULT_VOL : masterVolOut; samplerate = samplerate <= 0 ? G_DEFAULT_SAMPLERATE : samplerate; for (unsigned i=0; iindex = col->index < 0 ? 0 : col->index; col->width = col->width < G_MIN_COLUMN_WIDTH ? G_MIN_COLUMN_WIDTH : col->width; } for (unsigned i=0; isize = ch->size < G_GUI_CHANNEL_H_1 || ch->size > G_GUI_CHANNEL_H_4 ? G_GUI_CHANNEL_H_1 : ch->size; ch->volume = ch->volume < 0.0f || ch->volume > 1.0f ? G_DEFAULT_VOL : ch->volume; ch->pan = ch->pan < 0.0f || ch->pan > 1.0f ? 1.0f : ch->pan; ch->boost = ch->boost < 1.0f ? G_DEFAULT_BOOST : ch->boost; ch->pitch = ch->pitch < 0.1f || ch->pitch > G_MAX_PITCH ? G_DEFAULT_PITCH : ch->pitch; } } /* -------------------------------------------------------------------------- */ /* setInvalid Helper function used to return invalid status while reading. */ int setInvalid(json_t* jRoot) { json_decref(jRoot); return PATCH_INVALID; } /* -------------------------------------------------------------------------- */ bool readCommons(json_t* jContainer) { if (!storager::setString(jContainer, PATCH_KEY_HEADER, header)) return 0; if (!storager::setString(jContainer, PATCH_KEY_VERSION, version)) return 0; if (!storager::setInt (jContainer, PATCH_KEY_VERSION_MAJOR, versionMajor)) return 0; if (!storager::setInt (jContainer, PATCH_KEY_VERSION_MINOR, versionMinor)) return 0; if (!storager::setInt (jContainer, PATCH_KEY_VERSION_PATCH, versionPatch)) return 0; if (!storager::setString(jContainer, PATCH_KEY_NAME, name)) return 0; if (!storager::setFloat (jContainer, PATCH_KEY_BPM, bpm)) return 0; if (!storager::setInt (jContainer, PATCH_KEY_BARS, bars)) return 0; if (!storager::setInt (jContainer, PATCH_KEY_BEATS, beats)) return 0; if (!storager::setInt (jContainer, PATCH_KEY_QUANTIZE, quantize)) return 0; if (!storager::setFloat (jContainer, PATCH_KEY_MASTER_VOL_IN, masterVolIn)) return 0; if (!storager::setFloat (jContainer, PATCH_KEY_MASTER_VOL_OUT, masterVolOut)) return 0; if (!storager::setInt (jContainer, PATCH_KEY_METRONOME, metronome)) return 0; if (!storager::setInt (jContainer, PATCH_KEY_LAST_TAKE_ID, lastTakeId)) return 0; if (!storager::setInt (jContainer, PATCH_KEY_SAMPLERATE, samplerate)) return 0; return 1; } /* -------------------------------------------------------------------------- */ #ifdef WITH_VST bool readPlugins(json_t* jContainer, vector* container, const char* key) { json_t* jPlugins = json_object_get(jContainer, key); if (!storager::checkArray(jPlugins, key)) return 0; size_t pluginIndex; json_t* jPlugin; json_array_foreach(jPlugins, pluginIndex, jPlugin) { if (!storager::checkObject(jPlugin, "")) // TODO pass pluginIndex as string return 0; plugin_t plugin; if (!storager::setString(jPlugin, PATCH_KEY_PLUGIN_PATH, plugin.path)) return 0; if (!storager::setBool (jPlugin, PATCH_KEY_PLUGIN_BYPASS, plugin.bypass)) return 0; /* read plugin params */ json_t* jParams = json_object_get(jPlugin, PATCH_KEY_PLUGIN_PARAMS); if (!storager::checkArray(jParams, PATCH_KEY_PLUGIN_PARAMS)) return 0; size_t paramIndex; json_t* jParam; json_array_foreach(jParams, paramIndex, jParam) plugin.params.push_back(json_real_value(jParam)); /* read midiIn params (midi learning on plugins' parameters) */ json_t* jMidiInParams = json_object_get(jPlugin, PATCH_KEY_PLUGIN_MIDI_IN_PARAMS); if (!storager::checkArray(jMidiInParams, PATCH_KEY_PLUGIN_MIDI_IN_PARAMS)) return 0; size_t midiInParamIndex; json_t* jMidiInParam; json_array_foreach(jMidiInParams, midiInParamIndex, jMidiInParam) plugin.midiInParams.push_back(json_integer_value(jMidiInParam)); container->push_back(plugin); } return 1; } #endif /* -------------------------------------------------------------------------- */ bool readActions(json_t* jContainer, channel_t* channel) { json_t* jActions = json_object_get(jContainer, PATCH_KEY_CHANNEL_ACTIONS); if (!storager::checkArray(jActions, PATCH_KEY_CHANNEL_ACTIONS)) return 0; size_t actionIndex; json_t* jAction; json_array_foreach(jActions, actionIndex, jAction) { if (!storager::checkObject(jAction, "")) // TODO pass actionIndex as string return 0; action_t action; if (!storager::setInt (jAction, PATCH_KEY_ACTION_TYPE, action.type)) return 0; if (!storager::setInt (jAction, PATCH_KEY_ACTION_FRAME, action.frame)) return 0; if (!storager::setFloat (jAction, PATCH_KEY_ACTION_F_VALUE, action.fValue)) return 0; if (!storager::setUint32(jAction, PATCH_KEY_ACTION_I_VALUE, action.iValue)) return 0; channel->actions.push_back(action); } return 1; } /* -------------------------------------------------------------------------- */ bool readChannels(json_t* jContainer) { json_t* jChannels = json_object_get(jContainer, PATCH_KEY_CHANNELS); if (!storager::checkArray(jChannels, PATCH_KEY_CHANNELS)) return 0; size_t channelIndex; json_t* jChannel; json_array_foreach(jChannels, channelIndex, jChannel) { string channelIndexStr = "channel " + gu_iToString(channelIndex); if (!storager::checkObject(jChannel, channelIndexStr.c_str())) return 0; channel_t channel; if (!storager::setInt (jChannel, PATCH_KEY_CHANNEL_TYPE, channel.type)) return 0; if (!storager::setInt (jChannel, PATCH_KEY_CHANNEL_INDEX, channel.index)) return 0; if (!storager::setInt (jChannel, PATCH_KEY_CHANNEL_SIZE, channel.size)) return 0; if (!storager::setString(jChannel, PATCH_KEY_CHANNEL_NAME, channel.name)) return 0; if (!storager::setInt (jChannel, PATCH_KEY_CHANNEL_COLUMN, channel.column)) return 0; if (!storager::setInt (jChannel, PATCH_KEY_CHANNEL_MUTE, channel.mute)) return 0; if (!storager::setInt (jChannel, PATCH_KEY_CHANNEL_MUTE_S, channel.mute_s)) return 0; if (!storager::setInt (jChannel, PATCH_KEY_CHANNEL_SOLO, channel.solo)) return 0; if (!storager::setFloat (jChannel, PATCH_KEY_CHANNEL_VOLUME, channel.volume)) return 0; if (!storager::setFloat (jChannel, PATCH_KEY_CHANNEL_PAN, channel.pan)) return 0; if (!storager::setBool (jChannel, PATCH_KEY_CHANNEL_MIDI_IN, channel.midiIn)) return 0; if (!storager::setUint32(jChannel, PATCH_KEY_CHANNEL_MIDI_IN_KEYPRESS, channel.midiInKeyPress)) return 0; if (!storager::setUint32(jChannel, PATCH_KEY_CHANNEL_MIDI_IN_KEYREL, channel.midiInKeyRel)) return 0; if (!storager::setUint32(jChannel, PATCH_KEY_CHANNEL_MIDI_IN_KILL, channel.midiInKill)) return 0; if (!storager::setUint32(jChannel, PATCH_KEY_CHANNEL_MIDI_IN_ARM, channel.midiInArm)) return 0; if (!storager::setUint32(jChannel, PATCH_KEY_CHANNEL_MIDI_IN_VOLUME, channel.midiInVolume)) return 0; if (!storager::setUint32(jChannel, PATCH_KEY_CHANNEL_MIDI_IN_MUTE, channel.midiInMute)) return 0; if (!storager::setUint32(jChannel, PATCH_KEY_CHANNEL_MIDI_IN_SOLO, channel.midiInSolo)) return 0; if (!storager::setInt (jChannel, PATCH_KEY_CHANNEL_MIDI_IN_FILTER, channel.midiInFilter)) return 0; if (!storager::setBool (jChannel, PATCH_KEY_CHANNEL_MIDI_OUT_L, channel.midiOutL)) return 0; if (!storager::setUint32(jChannel, PATCH_KEY_CHANNEL_MIDI_OUT_L_PLAYING, channel.midiOutLplaying)) return 0; if (!storager::setUint32(jChannel, PATCH_KEY_CHANNEL_MIDI_OUT_L_MUTE, channel.midiOutLmute)) return 0; if (!storager::setUint32(jChannel, PATCH_KEY_CHANNEL_MIDI_OUT_L_SOLO, channel.midiOutLsolo)) return 0; if (!storager::setString(jChannel, PATCH_KEY_CHANNEL_SAMPLE_PATH, channel.samplePath)) return 0; if (!storager::setInt (jChannel, PATCH_KEY_CHANNEL_KEY, channel.key)) return 0; if (!storager::setInt (jChannel, PATCH_KEY_CHANNEL_MODE, channel.mode)) return 0; if (!storager::setInt (jChannel, PATCH_KEY_CHANNEL_BEGIN, channel.begin)) return 0; if (!storager::setInt (jChannel, PATCH_KEY_CHANNEL_END, channel.end)) return 0; if (!storager::setFloat (jChannel, PATCH_KEY_CHANNEL_BOOST, channel.boost)) return 0; if (!storager::setInt (jChannel, PATCH_KEY_CHANNEL_REC_ACTIVE, channel.recActive)) return 0; if (!storager::setFloat (jChannel, PATCH_KEY_CHANNEL_PITCH, channel.pitch)) return 0; if (!storager::setBool (jChannel, PATCH_KEY_CHANNEL_INPUT_MONITOR, channel.inputMonitor)) return 0; if (!storager::setUint32(jChannel, PATCH_KEY_CHANNEL_MIDI_IN_READ_ACTIONS, channel.midiInReadActions)) return 0; if (!storager::setUint32(jChannel, PATCH_KEY_CHANNEL_MIDI_IN_PITCH, channel.midiInPitch)) return 0; if (!storager::setUint32(jChannel, PATCH_KEY_CHANNEL_MIDI_OUT, channel.midiOut)) return 0; if (!storager::setUint32(jChannel, PATCH_KEY_CHANNEL_MIDI_OUT_CHAN, channel.midiOutChan)) return 0; if (!storager::setBool (jChannel, PATCH_KEY_CHANNEL_ARMED, channel.armed)) return 0; readActions(jChannel, &channel); #ifdef WITH_VST readPlugins(jChannel, &channel.plugins, PATCH_KEY_CHANNEL_PLUGINS); #endif channels.push_back(channel); } return 1; } /* -------------------------------------------------------------------------- */ bool readColumns(json_t* jContainer) { json_t* jColumns = json_object_get(jContainer, PATCH_KEY_COLUMNS); if (!storager::checkArray(jColumns, PATCH_KEY_COLUMNS)) return 0; size_t columnIndex; json_t* jColumn; json_array_foreach(jColumns, columnIndex, jColumn) { string columnIndexStr = "column " + gu_iToString(columnIndex); if (!storager::checkObject(jColumn, columnIndexStr.c_str())) return 0; column_t column; if (!storager::setInt(jColumn, PATCH_KEY_COLUMN_INDEX, column.index)) return 0; if (!storager::setInt(jColumn, PATCH_KEY_COLUMN_WIDTH, column.width)) return 0; columns.push_back(column); } return 1; } /* -------------------------------------------------------------------------- */ #ifdef WITH_VST void writePlugins(json_t* jContainer, vector* plugins, const char* key) { json_t* jPlugins = json_array(); for (unsigned j=0; jsize(); j++) { json_t* jPlugin = json_object(); plugin_t plugin = plugins->at(j); json_object_set_new(jPlugin, PATCH_KEY_PLUGIN_PATH, json_string(plugin.path.c_str())); json_object_set_new(jPlugin, PATCH_KEY_PLUGIN_BYPASS, json_boolean(plugin.bypass)); json_array_append_new(jPlugins, jPlugin); /* plugin params */ json_t* jPluginParams = json_array(); for (unsigned z=0; z* columns) { json_t* jColumns = json_array(); for (unsigned i=0; isize(); i++) { json_t* jColumn = json_object(); column_t column = columns->at(i); json_object_set_new(jColumn, PATCH_KEY_COLUMN_INDEX, json_integer(column.index)); json_object_set_new(jColumn, PATCH_KEY_COLUMN_WIDTH, json_integer(column.width)); json_array_append_new(jColumns, jColumn); } json_object_set_new(jContainer, PATCH_KEY_COLUMNS, jColumns); } /* -------------------------------------------------------------------------- */ void writeActions(json_t*jContainer, vector*actions) { json_t* jActions = json_array(); for (unsigned k=0; ksize(); k++) { json_t* jAction = json_object(); action_t action = actions->at(k); json_object_set_new(jAction, PATCH_KEY_ACTION_TYPE, json_integer(action.type)); json_object_set_new(jAction, PATCH_KEY_ACTION_FRAME, json_integer(action.frame)); json_object_set_new(jAction, PATCH_KEY_ACTION_F_VALUE, json_real(action.fValue)); json_object_set_new(jAction, PATCH_KEY_ACTION_I_VALUE, json_integer(action.iValue)); json_array_append_new(jActions, jAction); } json_object_set_new(jContainer, PATCH_KEY_CHANNEL_ACTIONS, jActions); } /* -------------------------------------------------------------------------- */ void writeCommons(json_t* jContainer) { json_object_set_new(jContainer, PATCH_KEY_HEADER, json_string(header.c_str())); json_object_set_new(jContainer, PATCH_KEY_VERSION, json_string(version.c_str())); json_object_set_new(jContainer, PATCH_KEY_VERSION_MAJOR, json_integer(versionMajor)); json_object_set_new(jContainer, PATCH_KEY_VERSION_MINOR, json_integer(versionMinor)); json_object_set_new(jContainer, PATCH_KEY_VERSION_PATCH, json_integer(versionPatch)); json_object_set_new(jContainer, PATCH_KEY_NAME, json_string(name.c_str())); json_object_set_new(jContainer, PATCH_KEY_BPM, json_real(bpm)); json_object_set_new(jContainer, PATCH_KEY_BARS, json_integer(bars)); json_object_set_new(jContainer, PATCH_KEY_BEATS, json_integer(beats)); json_object_set_new(jContainer, PATCH_KEY_QUANTIZE, json_integer(quantize)); json_object_set_new(jContainer, PATCH_KEY_MASTER_VOL_IN, json_real(masterVolIn)); json_object_set_new(jContainer, PATCH_KEY_MASTER_VOL_OUT, json_real(masterVolOut)); json_object_set_new(jContainer, PATCH_KEY_METRONOME, json_integer(metronome)); json_object_set_new(jContainer, PATCH_KEY_LAST_TAKE_ID, json_integer(lastTakeId)); json_object_set_new(jContainer, PATCH_KEY_SAMPLERATE, json_integer(samplerate)); } /* -------------------------------------------------------------------------- */ void writeChannels(json_t* jContainer, vector* channels) { json_t* jChannels = json_array(); for (unsigned i=0; isize(); i++) { json_t* jChannel = json_object(); channel_t channel = channels->at(i); json_object_set_new(jChannel, PATCH_KEY_CHANNEL_TYPE, json_integer(channel.type)); json_object_set_new(jChannel, PATCH_KEY_CHANNEL_INDEX, json_integer(channel.index)); json_object_set_new(jChannel, PATCH_KEY_CHANNEL_SIZE, json_integer(channel.size)); json_object_set_new(jChannel, PATCH_KEY_CHANNEL_NAME, json_string(channel.name.c_str())); json_object_set_new(jChannel, PATCH_KEY_CHANNEL_COLUMN, json_integer(channel.column)); json_object_set_new(jChannel, PATCH_KEY_CHANNEL_MUTE, json_integer(channel.mute)); json_object_set_new(jChannel, PATCH_KEY_CHANNEL_MUTE_S, json_integer(channel.mute_s)); json_object_set_new(jChannel, PATCH_KEY_CHANNEL_SOLO, json_integer(channel.solo)); json_object_set_new(jChannel, PATCH_KEY_CHANNEL_VOLUME, json_real(channel.volume)); json_object_set_new(jChannel, PATCH_KEY_CHANNEL_PAN, json_real(channel.pan)); json_object_set_new(jChannel, PATCH_KEY_CHANNEL_MIDI_IN, json_boolean(channel.midiIn)); json_object_set_new(jChannel, PATCH_KEY_CHANNEL_MIDI_IN_KEYPRESS, json_integer(channel.midiInKeyPress)); json_object_set_new(jChannel, PATCH_KEY_CHANNEL_MIDI_IN_KEYREL, json_integer(channel.midiInKeyRel)); json_object_set_new(jChannel, PATCH_KEY_CHANNEL_MIDI_IN_KILL, json_integer(channel.midiInKill)); json_object_set_new(jChannel, PATCH_KEY_CHANNEL_MIDI_IN_ARM, json_integer(channel.midiInArm)); json_object_set_new(jChannel, PATCH_KEY_CHANNEL_MIDI_IN_VOLUME, json_integer(channel.midiInVolume)); json_object_set_new(jChannel, PATCH_KEY_CHANNEL_MIDI_IN_MUTE, json_integer(channel.midiInMute)); json_object_set_new(jChannel, PATCH_KEY_CHANNEL_MIDI_IN_FILTER, json_integer(channel.midiInFilter)); json_object_set_new(jChannel, PATCH_KEY_CHANNEL_MIDI_IN_SOLO, json_integer(channel.midiInSolo)); json_object_set_new(jChannel, PATCH_KEY_CHANNEL_MIDI_OUT_L, json_boolean(channel.midiOutL)); json_object_set_new(jChannel, PATCH_KEY_CHANNEL_MIDI_OUT_L_PLAYING, json_integer(channel.midiOutLplaying)); json_object_set_new(jChannel, PATCH_KEY_CHANNEL_MIDI_OUT_L_MUTE, json_integer(channel.midiOutLmute)); json_object_set_new(jChannel, PATCH_KEY_CHANNEL_MIDI_OUT_L_SOLO, json_integer(channel.midiOutLsolo)); json_object_set_new(jChannel, PATCH_KEY_CHANNEL_SAMPLE_PATH, json_string(channel.samplePath.c_str())); json_object_set_new(jChannel, PATCH_KEY_CHANNEL_KEY, json_integer(channel.key)); json_object_set_new(jChannel, PATCH_KEY_CHANNEL_MODE, json_integer(channel.mode)); json_object_set_new(jChannel, PATCH_KEY_CHANNEL_BEGIN, json_integer(channel.begin)); json_object_set_new(jChannel, PATCH_KEY_CHANNEL_END, json_integer(channel.end)); json_object_set_new(jChannel, PATCH_KEY_CHANNEL_BOOST, json_real(channel.boost)); json_object_set_new(jChannel, PATCH_KEY_CHANNEL_REC_ACTIVE, json_integer(channel.recActive)); json_object_set_new(jChannel, PATCH_KEY_CHANNEL_PITCH, json_real(channel.pitch)); json_object_set_new(jChannel, PATCH_KEY_CHANNEL_INPUT_MONITOR, json_boolean(channel.inputMonitor)); json_object_set_new(jChannel, PATCH_KEY_CHANNEL_MIDI_IN_READ_ACTIONS, json_integer(channel.midiInReadActions)); json_object_set_new(jChannel, PATCH_KEY_CHANNEL_MIDI_IN_PITCH, json_integer(channel.midiInPitch)); json_object_set_new(jChannel, PATCH_KEY_CHANNEL_MIDI_OUT, json_integer(channel.midiOut)); json_object_set_new(jChannel, PATCH_KEY_CHANNEL_MIDI_OUT_CHAN, json_integer(channel.midiOutChan)); json_object_set_new(jChannel, PATCH_KEY_CHANNEL_ARMED, json_boolean(channel.armed)); json_array_append_new(jChannels, jChannel); writeActions(jChannel, &channel.actions); #ifdef WITH_VST writePlugins(jChannel, &channel.plugins, PATCH_KEY_CHANNEL_PLUGINS); #endif } json_object_set_new(jContainer, PATCH_KEY_CHANNELS, jChannels); } }; // {anonymous} /* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */ std::string header; std::string version; int versionMajor; int versionMinor; int versionPatch; std::string name; float bpm; int bars; int beats; int quantize; float masterVolIn; float masterVolOut; int metronome; int lastTakeId; int samplerate; // original samplerate when the patch was saved std::vector columns; std::vector channels; #ifdef WITH_VST std::vector masterInPlugins; std::vector masterOutPlugins; #endif /* -------------------------------------------------------------------------- */ void init() { columns.clear(); channels.clear(); #ifdef WITH_VST masterInPlugins.clear(); masterOutPlugins.clear(); #endif header = "GIADAPTC"; lastTakeId = 0; samplerate = G_DEFAULT_SAMPLERATE; } /* -------------------------------------------------------------------------- */ int write(const string& file) { json_t* jRoot = json_object(); writeCommons(jRoot); writeColumns(jRoot, &columns); writeChannels(jRoot, &channels); #ifdef WITH_VST writePlugins(jRoot, &masterInPlugins, PATCH_KEY_MASTER_IN_PLUGINS); writePlugins(jRoot, &masterOutPlugins, PATCH_KEY_MASTER_OUT_PLUGINS); #endif if (json_dump_file(jRoot, file.c_str(), JSON_COMPACT) != 0) { gu_log("[patch::write] unable to write patch file!\n"); return 0; } return 1; } /* -------------------------------------------------------------------------- */ int read(const string& file) { json_error_t jError; json_t* jRoot = json_load_file(file.c_str(), 0, &jError); if (!jRoot) { gu_log("[patch::read] unable to read patch file! Error on line %d: %s\n", jError.line, jError.text); return PATCH_UNREADABLE; } if (!storager::checkObject(jRoot, "root element")) return PATCH_INVALID; init(); /* TODO json_decref also when PATCH_INVALID */ if (!readCommons(jRoot)) return setInvalid(jRoot); if (!readColumns(jRoot)) return setInvalid(jRoot); if (!readChannels(jRoot)) return setInvalid(jRoot); #ifdef WITH_VST if (!readPlugins(jRoot, &masterInPlugins, PATCH_KEY_MASTER_IN_PLUGINS)) return setInvalid(jRoot); if (!readPlugins(jRoot, &masterOutPlugins, PATCH_KEY_MASTER_OUT_PLUGINS)) return setInvalid(jRoot); #endif json_decref(jRoot); sanitize(); return PATCH_READ_OK; } }}}; // giada::m::patch:: giada-0.14.5/src/core/patch.h000066400000000000000000000067631322662744500156640ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef G_PATCH_H #define G_PATCH_H #include #include #ifdef __APPLE__ // our Clang still doesn't know about cstdint (c++11 stuff) #include #else #include #endif namespace giada { namespace m { namespace patch { struct action_t { int type; int frame; float fValue; uint32_t iValue; }; #ifdef WITH_VST struct plugin_t { std::string path; bool bypass; std::vector params; std::vector midiInParams; }; #endif struct channel_t { int type; int index; int size; std::string name; int column; int mute; int mute_s; int solo; float volume; float pan; bool midiIn; uint32_t midiInKeyPress; uint32_t midiInKeyRel; uint32_t midiInKill; uint32_t midiInArm; uint32_t midiInVolume; uint32_t midiInMute; uint32_t midiInSolo; int midiInFilter; bool midiOutL; uint32_t midiOutLplaying; uint32_t midiOutLmute; uint32_t midiOutLsolo; bool armed; // sample channel std::string samplePath; int key; int mode; int begin; int end; float boost; int recActive; float pitch; bool inputMonitor; uint32_t midiInReadActions; uint32_t midiInPitch; // midi channel uint32_t midiOut; uint32_t midiOutChan; std::vector actions; #ifdef WITH_VST std::vector plugins; #endif }; struct column_t { int index; int width; std::vector channels; }; extern std::string header; extern std::string version; extern int versionMajor; extern int versionMinor; extern int versionPatch; extern std::string name; extern float bpm; extern int bars; extern int beats; extern int quantize; extern float masterVolIn; extern float masterVolOut; extern int metronome; extern int lastTakeId; extern int samplerate; // original samplerate when the patch was saved extern std::vector columns; extern std::vector channels; #ifdef WITH_VST extern std::vector masterInPlugins; extern std::vector masterOutPlugins; #endif /* init * Init Patch with default values. */ void init(); /* read/write * Read/write patch to/from file. */ int write(const std::string& file); int read (const std::string& file); }}}; // giada::m::patch:: #endif giada-0.14.5/src/core/plugin.cpp000066400000000000000000000150001322662744500163760ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifdef WITH_VST #include #include "../utils/log.h" #include "../utils/time.h" #include "const.h" #include "plugin.h" using std::string; using namespace giada::u; int Plugin::idGenerator = 1; /* -------------------------------------------------------------------------- */ Plugin::Plugin(juce::AudioPluginInstance *plugin, double samplerate, int buffersize) : ui (nullptr), plugin(plugin), id (idGenerator++), bypass(false) { /* Init midiInParams. All values are empty (0x0): they will be filled during midi learning process. */ for (int i=0; igetNumParameters(); i++) midiInParams.push_back(0x0); plugin->prepareToPlay(samplerate, buffersize); gu_log("[Plugin] plugin initialized and ready. MIDI input params: %lu\n", midiInParams.size()); } /* -------------------------------------------------------------------------- */ Plugin::~Plugin() { closeEditor(); plugin->suspendProcessing(true); plugin->releaseResources(); } /* -------------------------------------------------------------------------- */ void Plugin::showEditor(void* parent) { ui = plugin->createEditorIfNeeded(); if (ui == nullptr) { gu_log("[Plugin::showEditor] unable to create editor!\n"); return; } /* A silly workaround on X: it seems that calling addToDesktop too fast, i.e. before the X Window is fully ready screws up the plugin's event dispatcher. */ #ifdef G_OS_LINUX time::sleep(500); #endif ui->setOpaque(true); ui->addToDesktop(0, parent); } /* -------------------------------------------------------------------------- */ bool Plugin::isEditorOpen() const { return ui != nullptr && ui->isVisible() && ui->isOnDesktop(); } /* -------------------------------------------------------------------------- */ string Plugin::getUniqueId() const { //return plugin->getPluginDescription().fileOrIdentifier.toStdString(); return plugin->getPluginDescription().createIdentifierString().toStdString(); } /* -------------------------------------------------------------------------- */ int Plugin::getNumParameters() const { return plugin->getNumParameters(); } /* -------------------------------------------------------------------------- */ float Plugin::getParameter(int paramIndex) const { return plugin->getParameter(paramIndex); } /* -------------------------------------------------------------------------- */ void Plugin::setParameter(int paramIndex, float value) const { return plugin->setParameter(paramIndex, value); } /* -------------------------------------------------------------------------- */ void Plugin::prepareToPlay(double samplerate, int buffersize) const { plugin->prepareToPlay(samplerate, buffersize); } /* -------------------------------------------------------------------------- */ string Plugin::getName() const { return plugin->getName().toStdString(); } /* -------------------------------------------------------------------------- */ bool Plugin::isSuspended() const { return plugin->isSuspended(); } /* -------------------------------------------------------------------------- */ bool Plugin::acceptsMidi() const { return plugin->acceptsMidi(); } /* -------------------------------------------------------------------------- */ bool Plugin::isBypassed() const { return bypass; } void Plugin::toggleBypass() { bypass = !bypass; } void Plugin::setBypass(bool b) { bypass = b; } /* -------------------------------------------------------------------------- */ int Plugin::getId() const { return id; } /* -------------------------------------------------------------------------- */ int Plugin::getEditorW() const { return ui->getWidth(); } int Plugin::getEditorH() const { return ui->getHeight(); } /* -------------------------------------------------------------------------- */ void Plugin::process(juce::AudioBuffer& b, juce::MidiBuffer m) const { plugin->processBlock(b, m); } /* -------------------------------------------------------------------------- */ int Plugin::getNumPrograms() const { return plugin->getNumPrograms(); } /* -------------------------------------------------------------------------- */ int Plugin::getCurrentProgram() const { return plugin->getCurrentProgram(); } /* -------------------------------------------------------------------------- */ void Plugin::setCurrentProgram(int index) const { plugin->setCurrentProgram(index); } /* -------------------------------------------------------------------------- */ bool Plugin::hasEditor() const { return plugin->hasEditor(); } /* -------------------------------------------------------------------------- */ string Plugin::getProgramName(int index) const { return plugin->getProgramName(index).toStdString(); } /* -------------------------------------------------------------------------- */ string Plugin::getParameterName(int index) const { return plugin->getParameterName(index).toStdString(); } /* -------------------------------------------------------------------------- */ string Plugin::getParameterText(int index) const { return plugin->getParameterText(index).toStdString(); } /* -------------------------------------------------------------------------- */ string Plugin::getParameterLabel(int index) const { return plugin->getParameterLabel(index).toStdString(); } /* -------------------------------------------------------------------------- */ void Plugin::closeEditor() { if (ui == nullptr) return; delete ui; ui = nullptr; } #endif giada-0.14.5/src/core/plugin.h000066400000000000000000000056621322662744500160600ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * plugin * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifdef WITH_VST #ifndef G_PLUGIN_H #define G_PLUGIN_H #include "../deps/juce-config.h" class Plugin { private: static int idGenerator; juce::AudioProcessorEditor* ui; // gui juce::AudioPluginInstance* plugin; // core int id; bool bypass; public: Plugin(juce::AudioPluginInstance* p, double samplerate, int buffersize); ~Plugin(); /* getUniqueId Returns a string-based UID. */ std::string getUniqueId() const; /* process Process the plug-in with audio and MIDI data. The audio buffer is a reference: it has to be altered by the plug-in itself. Conversely, the MIDI buffer must be passed by copy: each plug-in must receive its own copy of the event set, so that any attempt to change/clear the MIDI buffer will only modify the local copy. */ void process(juce::AudioBuffer& b, juce::MidiBuffer m) const; std::string getName() const; bool isEditorOpen() const; bool hasEditor() const; int getNumParameters() const; float getParameter(int index) const; std::string getParameterName(int index) const; std::string getParameterText(int index) const; std::string getParameterLabel(int index) const; bool isSuspended() const; bool isBypassed() const; int getNumPrograms() const; int getCurrentProgram() const; std::string getProgramName(int index) const; int getId() const; int getEditorW() const; int getEditorH() const; void setParameter(int index, float value) const; void prepareToPlay(double samplerate, int buffersize) const; void setCurrentProgram(int index) const; bool acceptsMidi() const; void showEditor(void* parent); /* closeEditor Shuts down plugin GUI. */ void closeEditor(); void toggleBypass(); void setBypass(bool b); /* midiInParams A list of midiIn hex values for parameter automation. */ std::vector midiInParams; }; #endif #endif // #ifdef WITH_VST giada-0.14.5/src/core/pluginHost.cpp000066400000000000000000000403061322662744500172430ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifdef WITH_VST #include "../utils/log.h" #include "../utils/fs.h" #include "../utils/string.h" #include "const.h" #include "channel.h" #include "plugin.h" #include "pluginHost.h" using std::vector; using std::string; namespace giada { namespace m { namespace pluginHost { namespace { juce::MessageManager* messageManager; /* pluginFormat * Plugin format manager. */ juce::VSTPluginFormat pluginFormat; /* knownPuginList * List of known (i.e. scanned) plugins. */ juce::KnownPluginList knownPluginList; /* unknownPluginList * List of unrecognized plugins found in a patch. */ vector unknownPluginList; vector masterOut; vector masterIn; /* Audio|MidiBuffer * Dynamic buffers. */ juce::AudioBuffer audioBuffer; int samplerate; int buffersize; /* missingPlugins * If some plugins from any stack are missing. */ bool missingPlugins; void splitPluginDescription(const string& descr, vector& out) { // input: VST-mda-Ambience-18fae2d2-6d646141 string // output: [2-------------] [1-----] [0-----] vector.size() == 3 string chunk = ""; int count = 2; for (int i=descr.length()-1; i >= 0; i--) { if (descr[i] == '-' && count != 0) { out.push_back(chunk); count--; chunk = ""; } else chunk += descr[i]; } out.push_back(chunk); } /* findPluginDescription Browses the list of known plug-ins until plug-in with id == 'id' is found. Unfortunately knownPluginList.getTypeForIdentifierString(id) doesn't work for VSTs: their ID is based on the plug-in file location. E.g.: /home/vst/mdaAmbience.so -> VST-mdaAmbience-18fae2d2-6d646141 /home/vst-test/mdaAmbience.so -> VST-mdaAmbience-b328b2f6-6d646141 The following function simply drops the first hash code during comparison. */ const juce::PluginDescription* findPluginDescription(const string& id) { vector idParts; splitPluginDescription(id, idParts); for (const juce::PluginDescription* pd : knownPluginList) { vector tmpIdParts; splitPluginDescription(pd->createIdentifierString().toStdString(), tmpIdParts); if (idParts[0] == tmpIdParts[0] && idParts[2] == tmpIdParts[2]) return pd; } return nullptr; } }; // {anonymous} /* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */ pthread_mutex_t mutex_midi; /* -------------------------------------------------------------------------- */ void close() { messageManager->deleteInstance(); } /* -------------------------------------------------------------------------- */ void init(int _buffersize, int _samplerate) { gu_log("[pluginHost::init] initialize with buffersize=%d, samplerate=%d\n", _buffersize, _samplerate); messageManager = juce::MessageManager::getInstance(); audioBuffer.setSize(2, _buffersize); samplerate = _samplerate; buffersize = _buffersize; missingPlugins = false; //unknownPluginList.empty(); loadList(gu_getHomePath() + G_SLASH + "plugins.xml"); pthread_mutex_init(&mutex_midi, nullptr); } /* -------------------------------------------------------------------------- */ int scanDirs(const string& dirs, const std::function& cb) { gu_log("[pluginHost::scanDir] requested directories: '%s'\n", dirs.c_str()); gu_log("[pluginHost::scanDir] current plugins: %d\n", knownPluginList.getNumTypes()); knownPluginList.clear(); // clear up previous plugins vector dirVec; gu_split(dirs, ";", &dirVec); juce::VSTPluginFormat format; juce::FileSearchPath searchPath; for (const string& dir : dirVec) searchPath.add(juce::File(dir)); juce::PluginDirectoryScanner scanner(knownPluginList, format, searchPath, true, juce::File::nonexistent); // true: recursive juce::String name; while (scanner.scanNextFile(false, name)) { gu_log("[pluginHost::scanDir] scanning '%s'\n", name.toRawUTF8()); cb(scanner.getProgress()); } gu_log("[pluginHost::scanDir] %d plugin(s) found\n", knownPluginList.getNumTypes()); return knownPluginList.getNumTypes(); } /* -------------------------------------------------------------------------- */ int saveList(const string& filepath) { int out = knownPluginList.createXml()->writeToFile(juce::File(filepath), ""); if (!out) gu_log("[pluginHost::saveList] unable to save plugin list to %s\n", filepath.c_str()); return out; } /* -------------------------------------------------------------------------- */ int loadList(const string& filepath) { juce::XmlElement* elem = juce::XmlDocument::parse(juce::File(filepath)); if (elem) { knownPluginList.recreateFromXml(*elem); delete elem; return 1; } return 0; } /* -------------------------------------------------------------------------- */ Plugin* addPlugin(const string& fid, int stackType, pthread_mutex_t* mutex, Channel* ch) { vector* pStack = getStack(stackType, ch); /* Initialize plugin. The default mode uses getTypeForIdentifierString, falling back to getTypeForFile (deprecated) for old patches (< 0.14.4). */ const juce::PluginDescription* pd = findPluginDescription(fid); if (pd == nullptr) { gu_log("[pluginHost::addPlugin] no plugin found with fid=%s! Trying with " "deprecated mode...\n", fid.c_str()); pd = knownPluginList.getTypeForFile(fid); if (pd == nullptr) { gu_log("[pluginHost::addPlugin] still nothing to do, returning unknown plugin\n"); missingPlugins = true; unknownPluginList.push_back(fid); return nullptr; } } juce::AudioPluginInstance* pi = pluginFormat.createInstanceFromDescription(*pd, samplerate, buffersize); if (!pi) { gu_log("[pluginHost::addPlugin] unable to create instance with fid=%s!\n", fid.c_str()); missingPlugins = true; return nullptr; } gu_log("[pluginHost::addPlugin] plugin instance with fid=%s created\n", fid.c_str()); Plugin* p = new Plugin(pi, samplerate, buffersize); /* Try to inject the plugin as soon as possible. */ while (true) { if (pthread_mutex_trylock(mutex) != 0) continue; pStack->push_back(p); pthread_mutex_unlock(mutex); break; } gu_log("[pluginHost::addPlugin] plugin id=%s loaded (%s), stack type=%d, stack size=%d\n", fid.c_str(), p->getName().c_str(), stackType, pStack->size()); return p; } /* -------------------------------------------------------------------------- */ Plugin* addPlugin(int index, int stackType, pthread_mutex_t* mutex, Channel* ch) { juce::PluginDescription* pd = knownPluginList.getType(index); if (pd) { gu_log("[pluginHost::addPlugin] plugin found, uid=%s, name=%s...\n", pd->createIdentifierString().toRawUTF8(), pd->name.toRawUTF8()); return addPlugin(pd->createIdentifierString().toStdString(), stackType, mutex, ch); } gu_log("[pluginHost::addPlugin] no plugins found at index=%d!\n", index); return nullptr; } /* -------------------------------------------------------------------------- */ vector* getStack(int stackType, Channel* ch) { switch(stackType) { case MASTER_OUT: return &masterOut; case MASTER_IN: return &masterIn; case CHANNEL: return &ch->plugins; default: return nullptr; } } /* -------------------------------------------------------------------------- */ unsigned countPlugins(int stackType, Channel* ch) { vector* pStack = getStack(stackType, ch); return pStack->size(); } /* -------------------------------------------------------------------------- */ int countAvailablePlugins() { return knownPluginList.getNumTypes(); } /* -------------------------------------------------------------------------- */ unsigned countUnknownPlugins() { return unknownPluginList.size(); } /* -------------------------------------------------------------------------- */ pluginHost::PluginInfo getAvailablePluginInfo(int i) { juce::PluginDescription* pd = knownPluginList.getType(i); PluginInfo pi; pi.uid = pd->fileOrIdentifier.toStdString(); pi.name = pd->name.toStdString(); pi.category = pd->category.toStdString(); pi.manufacturerName = pd->manufacturerName.toStdString(); pi.format = pd->pluginFormatName.toStdString(); pi.isInstrument = pd->isInstrument; return pi; } /* -------------------------------------------------------------------------- */ bool hasMissingPlugins() { return missingPlugins; }; /* -------------------------------------------------------------------------- */ string getUnknownPluginInfo(int i) { return unknownPluginList.at(i); } /* -------------------------------------------------------------------------- */ void freeStack(int stackType, pthread_mutex_t* mutex, Channel* ch) { vector* pStack = getStack(stackType, ch); if (pStack->size() == 0) return; while (true) { if (pthread_mutex_trylock(mutex) != 0) continue; for (unsigned i=0; isize(); i++) delete pStack->at(i); pStack->clear(); pthread_mutex_unlock(mutex); break; } gu_log("[pluginHost::freeStack] stack type=%d freed\n", stackType); } /* -------------------------------------------------------------------------- */ void processStack(float* buffer, int stackType, Channel* ch) { vector* pStack = getStack(stackType, ch); /* Empty stack, stack not found or mixer not ready: do nothing. */ if (pStack == nullptr || pStack->size() == 0) return; /* MIDI channels must not process the current buffer: give them an empty one. Sample channels and Master in/out want audio data instead: let's convert the internal buffer from Giada to Juce. */ if (ch != nullptr && ch->type == CHANNEL_MIDI) audioBuffer.clear(); else for (int i=0; iisSuspended() || plugin->isBypassed()) continue; /* If this is a Channel (ch != nullptr) and the current plugin is an instrument (i.e. accepts MIDI), don't let it fill the current audio buffer: create a new temporary one instead and then merge the result into the main one when done. This way each plug-in generates its own audio data and we can play more than one plug-in instrument in the same stack, driven by the same set of MIDI events. */ if (ch != nullptr && plugin->acceptsMidi()) { juce::AudioBuffer tmp(2, buffersize); plugin->process(tmp, ch->getPluginMidiEvents()); for (int i=0; iprocess(audioBuffer, juce::MidiBuffer()); // Empty MIDI buffer } if (ch != nullptr) { ch->clearMidiBuffer(); pthread_mutex_unlock(&mutex_midi); } /* Converting buffer from Juce to Giada. A note for the future: if we overwrite (=) (as we do now) it's SEND, if we add (+) it's INSERT. */ for (int i=0; i* pStack = getStack(stackType, ch); if (pStack->size() == 0) return nullptr; if ((unsigned) index >= pStack->size()) return nullptr; return pStack->at(index); } /* -------------------------------------------------------------------------- */ int getPluginIndex(int id, int stackType, Channel* ch) { vector* pStack = getStack(stackType, ch); for (unsigned i=0; isize(); i++) if (pStack->at(i)->getId() == id) return i; return -1; } /* -------------------------------------------------------------------------- */ void swapPlugin(unsigned indexA, unsigned indexB, int stackType, pthread_mutex_t* mutex, Channel* ch) { vector* pStack = getStack(stackType, ch); while (true) { if (pthread_mutex_trylock(mutex) != 0) continue; std::swap(pStack->at(indexA), pStack->at(indexB)); pthread_mutex_unlock(mutex); gu_log("[pluginHost::swapPlugin] plugin at index %d and %d swapped\n", indexA, indexB); return; } } /* -------------------------------------------------------------------------- */ int freePlugin(int id, int stackType, pthread_mutex_t* mutex, Channel* ch) { vector* pStack = getStack(stackType, ch); for (unsigned i=0; isize(); i++) { Plugin *pPlugin = pStack->at(i); if (pPlugin->getId() != id) continue; while (true) { if (pthread_mutex_trylock(mutex) != 0) continue; delete pPlugin; pStack->erase(pStack->begin() + i); pthread_mutex_unlock(mutex); gu_log("[pluginHost::freePlugin] plugin id=%d removed\n", id); return i; } } gu_log("[pluginHost::freePlugin] plugin id=%d not found\n", id); return -1; } /* -------------------------------------------------------------------------- */ void runDispatchLoop() { messageManager->runDispatchLoopUntil(10); //gu_log("[pluginHost::runDispatchLoop] %d, hasStopMessageBeenSent=%d\n", r, messageManager->hasStopMessageBeenSent()); } /* -------------------------------------------------------------------------- */ void freeAllStacks(vector* channels, pthread_mutex_t* mutex) { freeStack(pluginHost::MASTER_OUT, mutex); freeStack(pluginHost::MASTER_IN, mutex); for (unsigned i=0; isize(); i++) freeStack(pluginHost::CHANNEL, mutex, channels->at(i)); missingPlugins = false; unknownPluginList.clear(); } /* -------------------------------------------------------------------------- */ int clonePlugin(Plugin* src, int stackType, pthread_mutex_t* mutex, Channel* ch) { Plugin* p = addPlugin(src->getUniqueId(), stackType, mutex, ch); if (!p) { gu_log("[pluginHost::clonePlugin] unable to add new plugin to stack!\n"); return 0; } for (int k=0; kgetNumParameters(); k++) p->setParameter(k, src->getParameter(k)); return 1; } /* -------------------------------------------------------------------------- */ bool doesPluginExist(const string& fid) { return pluginFormat.doesPluginStillExist(*knownPluginList.getTypeForFile(fid)); } /* -------------------------------------------------------------------------- */ void sortPlugins(int method) { switch (method) { case sortMethod::NAME: knownPluginList.sort(juce::KnownPluginList::SortMethod::sortAlphabetically, true); break; case sortMethod::CATEGORY: knownPluginList.sort(juce::KnownPluginList::SortMethod::sortByCategory, true); break; case sortMethod::MANUFACTURER: knownPluginList.sort(juce::KnownPluginList::SortMethod::sortByManufacturer, true); break; case sortMethod::FORMAT: knownPluginList.sort(juce::KnownPluginList::SortMethod::sortByFormat, true); break; } } }}}; // giada::m::pluginHost:: #endif // #ifdef WITH_VST giada-0.14.5/src/core/pluginHost.h000066400000000000000000000111331322662744500167040ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifdef WITH_VST #ifndef G_PLUGIN_HOST_H #define G_PLUGIN_HOST_H #include #include #include "../deps/juce-config.h" class Plugin; class Channel; namespace giada { namespace m { namespace pluginHost { enum stackType { MASTER_OUT, MASTER_IN, CHANNEL }; enum sortMethod { NAME, CATEGORY, MANUFACTURER, FORMAT }; struct PluginInfo { std::string uid; std::string name; std::string category; std::string manufacturerName; std::string format; bool isInstrument; }; void init(int bufSize, int frequency); void close(); /* scanDirs Parses plugin directories (semicolon-separated) and store list in knownPluginList. The callback is called on each plugin found. Used to update the main window from the GUI thread. */ int scanDirs(const std::string& paths, const std::function& cb); /* (save|load)List * (Save|Load) knownPluginList (in|from) an XML file. */ int saveList(const std::string& path); int loadList(const std::string& path); /* addPlugin * Add a new plugin to 'stackType' by unique id or by index in knownPluginList * std::vector. Requires: * fid - plugin unique file id (i.e. path to dynamic library) * stackType - which stack to add plugin to * mutex - Mixer.mutex_plugin * freq - current audio frequency * bufSize - buffer size * ch - if stackType == CHANNEL. */ Plugin* addPlugin(const std::string& fid, int stackType, pthread_mutex_t* mutex, Channel* ch=nullptr); Plugin *addPlugin(int index, int stackType, pthread_mutex_t* mutex, Channel* ch=nullptr); /* countPlugins * Return size of 'stackType'. */ unsigned countPlugins(int stackType, Channel* ch=nullptr); /* countAvailablePlugins * Return size of knownPluginList. */ int countAvailablePlugins(); /* countUnknownPlugins * Return size of unknownPluginList. */ unsigned countUnknownPlugins(); /* getAvailablePluginInfo * Return the available plugin information (name, type, ...) from * knownPluginList at index 'index'. */ PluginInfo getAvailablePluginInfo(int index); std::string getUnknownPluginInfo(int index); /* freeStack * free plugin stack of type 'stackType'. */ void freeStack(int stackType, pthread_mutex_t* mutex, Channel* ch=nullptr); /* processStack * apply the fx list to the buffer. */ void processStack(float* buffer, int stackType, Channel* ch=nullptr); /* getStack * Return a std::vector given the stackType. If stackType == CHANNEL * a pointer to Channel is also required. */ std::vector* getStack(int stackType, Channel* ch=nullptr); /* getPluginByIndex */ Plugin* getPluginByIndex(int index, int stackType, Channel* ch=nullptr); /* getPluginIndex */ int getPluginIndex(int id, int stackType, Channel* ch=nullptr); /* swapPlugin */ void swapPlugin(unsigned indexA, unsigned indexB, int stackType, pthread_mutex_t* mutex, Channel* ch=nullptr); /* freePlugin. Returns the internal stack index of the deleted plugin. */ int freePlugin(int id, int stackType, pthread_mutex_t *mutex, Channel* ch=nullptr); /* runDispatchLoop * Wakes up plugins' GUI manager for N milliseconds. */ void runDispatchLoop(); /* freeAllStacks * Frees everything. */ void freeAllStacks(std::vector* channels, pthread_mutex_t* mutex); /* clonePlugin */ int clonePlugin(Plugin* src, int stackType, pthread_mutex_t* mutex, Channel* ch); /* doesPluginExist */ bool doesPluginExist(const std::string& fid); bool hasMissingPlugins(); void sortPlugins(int sortMethod); extern pthread_mutex_t mutex_midi; }}}; // giada::m::pluginHost:: #endif #endif // #ifdef WITH_VST giada-0.14.5/src/core/recorder.cpp000066400000000000000000000426711322662744500167230ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include #include #include "../utils/log.h" #include "const.h" #include "sampleChannel.h" #include "recorder.h" using std::vector; namespace giada { namespace m { namespace recorder { namespace { /* Composite A group of two actions (keypress+keyrel, muteon+muteoff) used during the overdub process. */ Composite cmp; /* -------------------------------------------------------------------------- */ /* fixOverdubTruncation Fixes underlying action truncation when overdubbing over a longer action. I.e.: Original: |#############| Overdub: ---|#######|--- fix: |#||#######|--- */ void fixOverdubTruncation(const Composite& comp, pthread_mutex_t* mixerMutex) { action* next = nullptr; int res = getNextAction(comp.a2.chan, comp.a1.type | comp.a2.type, comp.a2.frame, &next); if (res != 1 || next->type != comp.a2.type) return; gu_log("[recorder::fixOverdubTruncation] add truncation at frame %d, type=%d\n", next->frame, next->type); deleteAction(next->chan, next->frame, next->type, false, mixerMutex); } }; // {anonymous} /* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */ vector frames; vector> global; vector actions; // used internally bool active = false; bool sortedActions = false; /* -------------------------------------------------------------------------- */ void init() { active = false; sortedActions = false; clearAll(); } /* -------------------------------------------------------------------------- */ bool canRec(Channel* ch, bool clockRunning, bool mixerRecording) { /* NO recording if: * recorder is inactive * mixer is not running * mixer is recording a take somewhere * channel is empty */ if (!active || !clockRunning || mixerRecording || (ch->type == CHANNEL_SAMPLE && ((SampleChannel*)ch)->wave == nullptr) ) return false; return true; } /* -------------------------------------------------------------------------- */ void rec(int index, int type, int frame, uint32_t iValue, float fValue) { /* make sure frame is even */ if (frame % 2 != 0) frame++; /* allocating the action */ action *a = (action*) malloc(sizeof(action)); a->chan = index; a->type = type; a->frame = frame; a->iValue = iValue; a->fValue = fValue; /* check if the frame exists in the stack. If it exists, we don't extend * the stack, but we add (or push) a new action to it. */ int frameToExpand = frames.size(); for (int i=0; ichan == index && ac->type == type && ac->frame == frame && ac->iValue == iValue && ac->fValue == fValue) return; } global.at(frameToExpand).push_back(a); // expand array } sortedActions = false; gu_log("[recorder::rec] action recorded, type=%d frame=%d chan=%d iValue=%d (0x%X) fValue=%f\n", a->type, a->frame, a->chan, a->iValue, a->iValue, a->fValue); //print(); } /* -------------------------------------------------------------------------- */ void clearChan(int index) { gu_log("[recorder::clearChan] clearing chan %d...\n", index); for (unsigned i=0; ichan == index) { free(a); global.at(i).erase(global.at(i).begin() + j); } else j++; } } optimize(); //print(); } /* -------------------------------------------------------------------------- */ void clearAction(int index, char act) { gu_log("[recorder::clearAction] clearing action %d from chan %d...\n", act, index); for (unsigned i=0; ichan == index && (act & a->type) == a->type) { // bitmask free(a); global.at(i).erase(global.at(i).begin() + j); } else j++; } } optimize(); //print(); } /* -------------------------------------------------------------------------- */ void deleteAction(int chan, int frame, char type, bool checkValues, pthread_mutex_t* mixerMutex, uint32_t iValue, float fValue) { /* make sure frame is even */ if (frame % 2 != 0) frame++; /* find the frame 'frame' */ bool found = false; for (unsigned i=0; ichan == chan && a->type == (type & a->type)); if (checkValues) doit &= (a->iValue == iValue && a->fValue == fValue); if (!doit) continue; while (true) { if (pthread_mutex_trylock(mixerMutex)) { free(a); global.at(i).erase(global.at(i).begin() + j); pthread_mutex_unlock(mixerMutex); found = true; break; } else gu_log("[recorder::deleteAction] waiting for mutex...\n"); } } } if (found) { optimize(); gu_log("[recorder::deleteAction] action deleted, type=%d frame=%d chan=%d iValue=%d (%X) fValue=%f\n", type, frame, chan, iValue, iValue, fValue); } else gu_log("[recorder::deleteAction] unable to delete action, not found! type=%d frame=%d chan=%d iValue=%d (%X) fValue=%f\n", type, frame, chan, iValue, iValue, fValue); } /* -------------------------------------------------------------------------- */ void deleteActions(int chan, int frame_a, int frame_b, char type, pthread_mutex_t* mixerMutex) { sortActions(); vector dels; for (unsigned i=0; i frame_a && frames.at(i) < frame_b) dels.push_back(frames.at(i)); for (unsigned i=0; i 0) { for (unsigned i=0; i frames.at(i)) { std::swap(frames.at(j), frames.at(i)); std::swap(global.at(j), global.at(i)); } sortedActions = true; //print(); } /* -------------------------------------------------------------------------- */ void updateBpm(float oldval, float newval, int oldquanto) { for (unsigned i=0; i 0 && scarto <= 6) frames.at(i) = frames.at(i) + scarto; } /* never ever have odd frames. */ if (frames.at(i) % 2 != 0) frames.at(i)++; } /* update structs */ for (unsigned i=0; iframe = frames.at(i); } } //print(); } /* -------------------------------------------------------------------------- */ void updateSamplerate(int systemRate, int patchRate) { /* diff ratio: systemRate / patchRate * e.g. 44100 / 96000 = 0.4... */ if (systemRate == patchRate) return; gu_log("[recorder::updateSamplerate] systemRate (%d) != patchRate (%d), converting...\n", systemRate, patchRate); float ratio = systemRate / (float) patchRate; for (unsigned i=0; iframe = frames.at(i); } } } /* -------------------------------------------------------------------------- */ void expand(int old_fpb, int new_fpb) { /* this algorithm requires multiple passages if we expand from e.g. 2 * to 16 beats, precisely 16 / 2 - 1 = 7 times (-1 is the first group, * which exists yet). If we expand by a non-multiple, the result is zero, * due to float->int implicit cast */ unsigned pass = (int) (new_fpb / old_fpb) - 1; if (pass == 0) pass = 1; unsigned init_fs = frames.size(); for (unsigned z=1; z<=pass; z++) { for (unsigned i=0; ichan, a->type, newframe, a->iValue, a->fValue); } } } gu_log("[recorder::expand] expanded recs\n"); //print(); } /* -------------------------------------------------------------------------- */ void shrink(int new_fpb) { /* easier than expand(): here we delete eveything beyond old_framesPerBars. */ unsigned i=0; while (true) { if (i == frames.size()) break; if (frames.at(i) >= new_fpb) { for (unsigned k=0; kchan == chanIndex) return true; } } return false; } /* -------------------------------------------------------------------------- */ int getNextAction(int chan, char type, int fromFrame, action** out, uint32_t iValue, uint32_t mask) { sortActions(); // mandatory /* Increase 'i' until it reaches 'fromFrame'. That's the point where to start to look for the next action. */ unsigned i = 0; while (i < frames.size() && frames.at(i) <= fromFrame) i++; /* No other actions past 'fromFrame': there are no more actions to look for. Return -1. */ if (i == frames.size()) return -1; for (; ichan != chan || (type & a->type) != a->type) continue; /* If no iValue has been specified (iValue == 0), then the next action has been found, return it. Otherwise, make sure the iValue matches the action's iValue, according to the mask provided. */ if (iValue == 0 || (iValue != 0 && (a->iValue | mask) == (iValue | mask))) { *out = global.at(i).at(j); return 1; } } } return -2; // no 'type' actions found } /* -------------------------------------------------------------------------- */ int getAction(int chan, char action, int frame, struct action** out) { for (unsigned i=0; iframe && action == global.at(i).at(j)->type && chan == global.at(i).at(j)->chan) { *out = global.at(i).at(j); return 1; } return 0; } /* -------------------------------------------------------------------------- */ void startOverdub(int index, char actionMask, int frame, unsigned bufferSize) { /* prepare the composite struct */ if (actionMask == G_ACTION_KEYS) { cmp.a1.type = G_ACTION_KEYPRESS; cmp.a2.type = G_ACTION_KEYREL; } else { cmp.a1.type = G_ACTION_MUTEON; cmp.a2.type = G_ACTION_MUTEOFF; } cmp.a1.chan = index; cmp.a2.chan = index; cmp.a1.frame = frame; // cmp.a2.frame doesn't exist yet /* avoid underlying action truncation: if action2.type == nextAction: * you are in the middle of a composite action, truncation needed */ rec(index, cmp.a1.type, frame); action *act = nullptr; int res = getNextAction(index, cmp.a1.type | cmp.a2.type, cmp.a1.frame, &act); if (res == 1) { if (act->type == cmp.a2.type) { int truncFrame = cmp.a1.frame - bufferSize; if (truncFrame < 0) truncFrame = 0; gu_log("[recorder::startOverdub] add truncation at frame %d, type=%d\n", truncFrame, cmp.a2.type); rec(index, cmp.a2.type, truncFrame); } } } /* -------------------------------------------------------------------------- */ void stopOverdub(int currentFrame, int totalFrames, pthread_mutex_t* mixerMutex) { cmp.a2.frame = currentFrame; bool ringLoop = false; bool nullLoop = false; /* Check for ring loops or null loops. Ring loop: a composite action with key_press at frame N and key_release at frame M, with M <= N. Null loop: a composite action that begins and ends on the very same frame, i.e. with 0 size. Very unlikely. If ring loop: record the last action at the end of the sequencer (that is 'totalFrames'). If null loop: remove previous action and do nothing. Also make sure to avoid underlying action truncation, if the null loop occurs inside a composite action. */ if (cmp.a2.frame < cmp.a1.frame) { // ring loop ringLoop = true; gu_log("[recorder::stopOverdub] ring loop! frame1=%d < frame2=%d\n", cmp.a1.frame, cmp.a2.frame); rec(cmp.a2.chan, cmp.a2.type, totalFrames); } else if (cmp.a2.frame == cmp.a1.frame) { // null loop nullLoop = true; gu_log("[recorder::stopOverdub] null loop! frame1=%d == frame2=%d\n", cmp.a1.frame, cmp.a2.frame); deleteAction(cmp.a1.chan, cmp.a1.frame, cmp.a1.type, false, mixerMutex); // false == don't check values fixOverdubTruncation(cmp, mixerMutex); } if (nullLoop) return; /* Remove any nested action between keypress----keyrel. */ deleteActions(cmp.a2.chan, cmp.a1.frame, cmp.a2.frame, cmp.a1.type, mixerMutex); deleteActions(cmp.a2.chan, cmp.a1.frame, cmp.a2.frame, cmp.a2.type, mixerMutex); if (ringLoop) return; /* Record second part of the composite action. Also make sure to avoid underlying action truncation, if keyrel happens inside a composite action. */ rec(cmp.a2.chan, cmp.a2.type, cmp.a2.frame); fixOverdubTruncation(cmp, mixerMutex); } }}}; // giada::m::recorder:: giada-0.14.5/src/core/recorder.h000066400000000000000000000122701322662744500163600ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef G_RECORDER_H #define G_RECORDER_H #ifdef __APPLE__ // our Clang still doesn't know about cstdint (c++11 stuff) #include #else #include #endif #include #include class Channel; namespace giada { namespace m { namespace recorder { /* action * struct containing fields to describe an atomic action. Note from * VST sdk: parameter values, like all VST parameters, are declared as * floats with an inclusive range of 0.0 to 1.0 (fValue). */ struct action { int chan; // channel index, i.e. Channel->index int type; int frame; // redundant info, used by helper functions float fValue; // used only for envelopes (volumes, vst params). uint32_t iValue; // used only for MIDI events }; /* Composite A group of two actions (keypress+keyrel, muteon+muteoff). */ struct Composite { action a1; action a2; }; /* frames Frame counter sentinel. It tells which frames contain actions. E.g.: frames[0] = 155 // some actions on frame 155 frames[1] = 2048 // some actions on frame 2048 It always matches 'global''s size: frames.size() == global.size() */ extern std::vector frames; /* global Contains the actual actions. E.g.: global[0] = global[1] = */ extern std::vector> global; extern bool active; extern bool sortedActions; // are actions sorted via sortActions()? /* init * everything starts from here. */ void init(); /* hasActions Checks if the channel has at least one action recorded. Used after an action deletion. */ bool hasActions(int chanIndex); /* canRec * can a channel rec an action? Call this one BEFORE rec(). */ bool canRec(Channel *ch, bool clockRunning, bool mixerRecording); /* rec * record an action. */ void rec(int chan, int action, int frame, uint32_t iValue=0, float fValue=0.0f); /* clearChan * clear all actions from a channel. */ void clearChan(int chan); /* clearAction * clear the 'action' action type from a channel. */ void clearAction(int chan, char action); /* deleteAction * delete ONE action. Useful in the action editor. 'type' can be a mask. */ void deleteAction(int chan, int frame, char type, bool checkValues, pthread_mutex_t *mixerMutex, uint32_t iValue=0, float fValue=0.0); /* deleteActions Deletes A RANGE of actions from frame_a to frame_b in channel 'chan' of type 'type' (can be a bitmask). Exclusive range (frame_a, frame_b). */ void deleteActions(int chan, int frame_a, int frame_b, char type, pthread_mutex_t *mixerMutex); /* clearAll * delete everything. */ void clearAll(); /* optimize * clear frames without actions. */ void optimize(); /* sortActions * sorts actions by frame, asc mode. */ void sortActions(); /* updateBpm * reassign frames by calculating the new bpm value. */ void updateBpm(float oldval, float newval, int oldquanto); /* updateSamplerate * reassign frames taking in account the samplerate. If f_system == * f_patch nothing changes, otherwise the conversion is mandatory. */ void updateSamplerate(int systemRate, int patchRate); void expand(int old_fpb, int new_fpb); void shrink(int new_fpb); /* getNextAction Returns the nearest action in chan 'chan' of type 'action' starting from 'frame'. Action can be a bitmask. If iValue != 0 search for next action with iValue == iValue with 'mask' to ignore bytes. Useful for MIDI key_release. Mask example: iValue = 0x803D3F00 mask = 0x0000FF00 // ignore byte 3 action = 0x803D3200 // <--- this action will be found */ int getNextAction(int chan, char action, int frame, struct action** out, uint32_t iValue=0, uint32_t mask=0); /* getAction Returns a pointer to action in chan 'chan' of type 'action' at frame 'frame'. */ int getAction(int chan, char action, int frame, struct action** out); /* start/stopOverdub These functions are used when you overwrite existing actions. For example: pressing Mute button on a channel with some existing mute actions. */ void startOverdub(int chan, char action, int frame, unsigned bufferSize); void stopOverdub(int currentFrame, int totalFrames, pthread_mutex_t *mixerMutex); }}}; // giada::m::recorder:: #endif giada-0.14.5/src/core/sampleChannel.cpp000066400000000000000000000702311322662744500176610ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include #include #include #include "../utils/log.h" #include "../utils/fs.h" #include "../utils/string.h" #include "patch.h" #include "const.h" #include "conf.h" #include "clock.h" #include "mixer.h" #include "wave.h" #include "pluginHost.h" #include "waveFx.h" #include "waveManager.h" #include "mixerHandler.h" #include "kernelMidi.h" #include "kernelAudio.h" #include "sampleChannel.h" using std::string; using namespace giada::m; SampleChannel::SampleChannel(int bufferSize, bool inputMonitor) : Channel (CHANNEL_SAMPLE, STATUS_EMPTY, bufferSize), rsmp_state (nullptr), pChan (nullptr), vChanPreview (nullptr), frameRewind (-1), begin (0), end (0), boost (G_DEFAULT_BOOST), pitch (G_DEFAULT_PITCH), trackerPreview (0), shift (0), wave (nullptr), tracker (0), mode (G_DEFAULT_CHANMODE), qWait (false), fadeinOn (false), fadeinVol (1.0f), fadeoutOn (false), fadeoutVol (1.0f), fadeoutTracker (0), fadeoutStep (G_DEFAULT_FADEOUT_STEP), inputMonitor (inputMonitor), midiInReadActions(0x0), midiInPitch (0x0) { } /* -------------------------------------------------------------------------- */ SampleChannel::~SampleChannel() { if (wave != nullptr) delete wave; if (rsmp_state != nullptr) src_delete(rsmp_state); if (pChan != nullptr) delete[] pChan; if (vChanPreview != nullptr) delete[] vChanPreview; } /* -------------------------------------------------------------------------- */ bool SampleChannel::allocBuffers() { if (!Channel::allocBuffers()) return false; rsmp_state = src_new(SRC_LINEAR, 2, nullptr); if (rsmp_state == nullptr) { gu_log("[SampleChannel::allocBuffers] unable to alloc memory for SRC_STATE!\n"); return false; } pChan = new (std::nothrow) float[bufferSize]; if (pChan == nullptr) { gu_log("[SampleChannel::allocBuffers] unable to alloc memory for pChan!\n"); return false; } vChanPreview = new (std::nothrow) float[bufferSize]; if (vChanPreview == nullptr) { gu_log("[SampleChannel::allocBuffers] unable to alloc memory for vChanPreview!\n"); return false; } return true; } /* -------------------------------------------------------------------------- */ void SampleChannel::copy(const Channel* _src, pthread_mutex_t* pluginMutex) { Channel::copy(_src, pluginMutex); const SampleChannel* src = static_cast(_src); tracker = src->tracker; begin = src->begin; end = src->end; boost = src->boost; mode = src->mode; qWait = src->qWait; fadeinOn = src->fadeinOn; fadeinVol = src->fadeinVol; fadeoutOn = src->fadeoutOn; fadeoutVol = src->fadeoutVol; fadeoutTracker = src->fadeoutTracker; fadeoutStep = src->fadeoutStep; fadeoutType = src->fadeoutType; fadeoutEnd = src->fadeoutEnd; setPitch(src->pitch); if (src->wave) pushWave(new Wave(*src->wave)); // invoke Wave's copy constructor } /* -------------------------------------------------------------------------- */ void SampleChannel::clear() { /** TODO - these memsets may be done only if status PLAY (if below), * but it would require extra clearPChan calls when samples stop */ std::memset(vChan, 0, sizeof(float) * bufferSize); std::memset(pChan, 0, sizeof(float) * bufferSize); if (status & (STATUS_PLAY | STATUS_ENDING)) { tracker = fillChan(vChan, tracker, 0); if (fadeoutOn && fadeoutType == XFADE) { gu_log("[clear] filling pChan fadeoutTracker=%d\n", fadeoutTracker); fadeoutTracker = fillChan(pChan, fadeoutTracker, 0); } } } /* -------------------------------------------------------------------------- */ void SampleChannel::calcVolumeEnv(int frame) { /* method: check this frame && next frame, then calculate delta */ recorder::action *a0 = nullptr; recorder::action *a1 = nullptr; int res; /* get this action on frame 'frame'. It's unlikely that the action * is not found. */ res = recorder::getAction(index, G_ACTION_VOLUME, frame, &a0); if (res == 0) return; /* get the action next to this one. * res == -1: a1 not found, this is the last one. Rewind the search * and use action at frame number 0 (actions[0]). * res == -2 G_ACTION_VOLUME not found. This should never happen */ res = recorder::getNextAction(index, G_ACTION_VOLUME, frame, &a1); if (res == -1) res = recorder::getAction(index, G_ACTION_VOLUME, 0, &a1); volume_i = a0->fValue; volume_d = ((a1->fValue - a0->fValue) / ((a1->frame - a0->frame) / 2)) * 1.003f; } /* -------------------------------------------------------------------------- */ void SampleChannel::hardStop(int frame) { if (frame != 0) // clear data in range [frame, bufferSize-1] clearChan(vChan, frame); status = STATUS_OFF; sendMidiLplay(); reset(frame); } /* -------------------------------------------------------------------------- */ void SampleChannel::onBar(int frame) { ///if (mode == LOOP_REPEAT && status == STATUS_PLAY) /// //setXFade(frame); /// reset(frame); if (mode == LOOP_REPEAT) { if (status == STATUS_PLAY) //setXFade(frame); reset(frame); } else if (mode == LOOP_ONCE_BAR) { if (status == STATUS_WAIT) { status = STATUS_PLAY; tracker = fillChan(vChan, tracker, frame); sendMidiLplay(); } } } /* -------------------------------------------------------------------------- */ void SampleChannel::setBegin(int f) { /* TODO - Opaque channel's count - everything in SampleChannel should be frame-based, not sample-based. I.e. Remove * wave->getChannels() stuff. */ if (f < 0) begin = 0; else if (f > wave->getSize()) begin = wave->getSize() * wave->getChannels(); else if (f >= end) begin = end - wave->getChannels(); else begin = f * wave->getChannels(); tracker = begin; trackerPreview = begin; } /* -------------------------------------------------------------------------- */ void SampleChannel::setEnd(int f) { /* TODO - Opaque channel's count - everything in SampleChannel should be frame-based, not sample-based. I.e. Remove * wave->getChannels() stuff. */ if (f < 0) end = begin + wave->getChannels(); else if (f >= wave->getSize()) end = (wave->getSize() - 1) * wave->getChannels(); else if (f * wave->getChannels() <= begin) end = begin + wave->getChannels(); else end = f * wave->getChannels(); } /* -------------------------------------------------------------------------- */ int SampleChannel::getBegin() { /* TODO - Opaque channel's count - everything in SampleChannel should be frame-based, not sample-based. I.e. Remove / wave->getChannels() stuff. */ return begin / wave->getChannels(); } int SampleChannel::getEnd() { /* TODO - Opaque channel's count - everything in SampleChannel should be frame-based, not sample-based. I.e. Remove / wave->getChannels() stuff. */ return end / wave->getChannels(); } /* -------------------------------------------------------------------------- */ void SampleChannel::setTrackerPreview(int f) { /* TODO - Opaque channel's count - everything in SampleChannel should be frame-based, not sample-based. I.e. Remove * wave->getChannels() stuff. */ trackerPreview = f * wave->getChannels(); } int SampleChannel::getTrackerPreview() const { /* TODO - Opaque channel's count - everything in SampleChannel should be frame-based, not sample-based. I.e. Remove / wave->getChannels() stuff. */ return trackerPreview / wave->getChannels(); } /* -------------------------------------------------------------------------- */ int SampleChannel::getShift() const { return shift; } void SampleChannel::setShift(int s) { shift = s; } /* -------------------------------------------------------------------------- */ void SampleChannel::setPitch(float v) { if (v > G_MAX_PITCH) pitch = G_MAX_PITCH; else if (v < 0.1f) pitch = 0.1000f; else pitch = v; rsmp_data.src_ratio = 1/pitch; /* if status is off don't slide between frequencies */ if (status & (STATUS_OFF | STATUS_WAIT)) src_set_ratio(rsmp_state, 1/pitch); } float SampleChannel::getPitch() { return pitch; } /* -------------------------------------------------------------------------- */ void SampleChannel::rewind() { /* rewind LOOP_ANY or SINGLE_ANY only if it's in read-record-mode */ if (wave != nullptr) { if ((mode & LOOP_ANY) || (recStatus == REC_READING && (mode & SINGLE_ANY))) reset(0); // rewind is user-generated events, always on frame 0 } } /* -------------------------------------------------------------------------- */ void SampleChannel::parseAction(recorder::action* a, int localFrame, int globalFrame, int quantize, bool mixerIsRunning) { if (readActions == false) return; switch (a->type) { case G_ACTION_KEYPRESS: if (mode & SINGLE_ANY) start(localFrame, false, quantize, mixerIsRunning, false, false); break; case G_ACTION_KEYREL: if (mode & SINGLE_ANY) stop(); break; case G_ACTION_KILL: if (mode & SINGLE_ANY) kill(localFrame); break; case G_ACTION_MUTEON: setMute(true); // internal mute break; case G_ACTION_MUTEOFF: unsetMute(true); // internal mute break; case G_ACTION_VOLUME: calcVolumeEnv(globalFrame); break; } } /* -------------------------------------------------------------------------- */ void SampleChannel::sum(int frame, bool running) { if (wave == nullptr || status & ~(STATUS_PLAY | STATUS_ENDING)) return; if (frame != frameRewind) { /* volume envelope, only if seq is running */ if (running) { volume_i += volume_d; if (volume_i < 0.0f) volume_i = 0.0f; else if (volume_i > 1.0f) volume_i = 1.0f; } /* fadein or fadeout processes. If mute, delete any signal. */ /** TODO - big issue: fade[in/out]Vol * internal_volume might be a * bad choice: it causes glitches when muting on and off during a * volume envelope. */ if (mute || mute_i) { vChan[frame] = 0.0f; vChan[frame+1] = 0.0f; } else if (fadeinOn) { if (fadeinVol < 1.0f) { vChan[frame] *= fadeinVol * volume_i; vChan[frame+1] *= fadeinVol * volume_i; fadeinVol += 0.01f; } else { fadeinOn = false; fadeinVol = 0.0f; } } else if (fadeoutOn) { if (fadeoutVol > 0.0f) { // fadeout ongoing if (fadeoutType == XFADE) { vChan[frame] *= volume_i; vChan[frame+1] *= volume_i; vChan[frame] = pChan[frame] * fadeoutVol * volume_i; vChan[frame+1] = pChan[frame+1] * fadeoutVol * volume_i; } else { vChan[frame] *= fadeoutVol * volume_i; vChan[frame+1] *= fadeoutVol * volume_i; } fadeoutVol -= fadeoutStep; } else { // fadeout end fadeoutOn = false; fadeoutVol = 1.0f; /* QWait ends with the end of the xfade */ if (fadeoutType == XFADE) { qWait = false; } else { if (fadeoutEnd == DO_MUTE) mute = true; else if (fadeoutEnd == DO_MUTE_I) mute_i = true; else // DO_STOP hardStop(frame); } } } else { vChan[frame] *= volume_i; vChan[frame+1] *= volume_i; } } else { // at this point the sample has reached the end */ if (mode & (SINGLE_BASIC | SINGLE_PRESS | SINGLE_RETRIG) || (mode == SINGLE_ENDLESS && status == STATUS_ENDING) || (mode & LOOP_ANY && !running)) // stop loops when the seq is off { status = STATUS_OFF; sendMidiLplay(); } /* LOOP_ONCE or LOOP_ONCE_BAR: if ending (i.e. the user requested their * termination), kill 'em. Let them wait otherwise. But don't put back in * wait mode those already stopped by the conditionals above. */ if (mode & (LOOP_ONCE | LOOP_ONCE_BAR)) { if (status == STATUS_ENDING) status = STATUS_OFF; else if (status != STATUS_OFF) status = STATUS_WAIT; } /* check for end of samples. SINGLE_ENDLESS runs forever unless * it's in ENDING mode. */ reset(frame); } } /* -------------------------------------------------------------------------- */ void SampleChannel::onZero(int frame, bool recsStopOnChanHalt) { if (wave == nullptr) return; if (mode & LOOP_ANY) { /* do a crossfade if the sample is playing. Regular chanReset * instead if it's muted, otherwise a click occurs */ if (status == STATUS_PLAY) { /* if (mute || mute_i) reset(frame); else setXFade(frame); */ reset(frame); } else if (status == STATUS_ENDING) hardStop(frame); } if (status == STATUS_WAIT) { /// FIXME - should be inside previous if! status = STATUS_PLAY; sendMidiLplay(); tracker = fillChan(vChan, tracker, frame); } if (recStatus == REC_ENDING) { recStatus = REC_STOPPED; setReadActions(false, recsStopOnChanHalt); // rec stop } else if (recStatus == REC_WAITING) { recStatus = REC_READING; setReadActions(true, recsStopOnChanHalt); // rec start } } /* -------------------------------------------------------------------------- */ void SampleChannel::quantize(int index, int localFrame) { /* skip if LOOP_ANY or not in quantizer-wait mode */ if ((mode & LOOP_ANY) || !qWait) return; /* no fadeout if the sample starts for the first time (from a * STATUS_OFF), it would be meaningless. */ if (status == STATUS_OFF) { status = STATUS_PLAY; sendMidiLplay(); qWait = false; tracker = fillChan(vChan, tracker, localFrame); /// FIXME: ??? } else //setXFade(localFrame); reset(localFrame); /* this is the moment in which we record the keypress, if the * quantizer is on. SINGLE_PRESS needs overdub */ if (recorder::canRec(this, clock::isRunning(), mixer::recording)) { if (mode == SINGLE_PRESS) { recorder::startOverdub(index, G_ACTION_KEYS, clock::getCurrentFrame(), kernelAudio::getRealBufSize()); readActions = false; // don't read actions while overdubbing } else recorder::rec(index, G_ACTION_KEYPRESS, clock::getCurrentFrame()); hasActions = true; } } /* -------------------------------------------------------------------------- */ int SampleChannel::getPosition() { /* TODO - Opaque channel's count - everything in SampleChannel should be frame-based, not sample-based. I.e. Remove / wave->getChannels() stuff. */ if (status & ~(STATUS_EMPTY | STATUS_MISSING | STATUS_OFF)) // if is not (...) return (tracker - begin) / wave->getChannels(); else return -1; } /* -------------------------------------------------------------------------- */ void SampleChannel::setMute(bool internal) { if (internal) { /* global mute is on? don't waste time with fadeout, just mute it * internally */ if (mute) mute_i = true; else { if (isPlaying()) setFadeOut(DO_MUTE_I); else mute_i = true; } } else { /* internal mute is on? don't waste time with fadeout, just mute it * globally */ if (mute_i) mute = true; else { /* sample in play? fadeout needed. Else, just mute it globally */ if (isPlaying()) setFadeOut(DO_MUTE); else mute = true; } } sendMidiLmute(); } /* -------------------------------------------------------------------------- */ void SampleChannel::unsetMute(bool internal) { if (internal) { if (mute) mute_i = false; else { if (isPlaying()) setFadeIn(internal); else mute_i = false; } } else { if (mute_i) mute = false; else { if (isPlaying()) setFadeIn(internal); else mute = false; } } sendMidiLmute(); } /* -------------------------------------------------------------------------- */ void SampleChannel::setBoost(float v) { if (v > G_MAX_BOOST_DB) boost = G_MAX_BOOST_DB; else if (v < 0.0f) boost = 0.0f; else boost = v; } float SampleChannel::getBoost() { return boost; } /* -------------------------------------------------------------------------- */ void SampleChannel::calcFadeoutStep() { if (end - tracker < (1 / G_DEFAULT_FADEOUT_STEP) * 2) fadeoutStep = ceil((end - tracker) / volume) * 2; /// or volume_i ??? else fadeoutStep = G_DEFAULT_FADEOUT_STEP; } /* -------------------------------------------------------------------------- */ void SampleChannel::setReadActions(bool v, bool recsStopOnChanHalt) { readActions = v; if (!readActions && recsStopOnChanHalt) kill(0); /// FIXME - wrong frame value } /* -------------------------------------------------------------------------- */ void SampleChannel::setOnEndPreviewCb(std::function f) { onPreviewEnd = f; } /* -------------------------------------------------------------------------- */ void SampleChannel::setFadeIn(bool internal) { if (internal) mute_i = false; // remove mute before fading in else mute = false; fadeinOn = true; fadeinVol = 0.0f; } /* -------------------------------------------------------------------------- */ void SampleChannel::setFadeOut(int actionPostFadeout) { calcFadeoutStep(); fadeoutOn = true; fadeoutVol = 1.0f; fadeoutType = FADEOUT; fadeoutEnd = actionPostFadeout; } /* -------------------------------------------------------------------------- */ void SampleChannel::setXFade(int frame) { gu_log("[xFade] frame=%d tracker=%d\n", frame, tracker); calcFadeoutStep(); fadeoutOn = true; fadeoutVol = 1.0f; fadeoutType = XFADE; fadeoutTracker = fillChan(pChan, tracker, 0, false); reset(frame); } /* -------------------------------------------------------------------------- */ /* On reset, if frame > 0 and in play, fill again pChan to create something like this: |abcdefabcdefab*abcdefabcde| [old data-----]*[new data--] */ void SampleChannel::reset(int frame) { //fadeoutTracker = tracker; // store old frame number for xfade tracker = begin; mute_i = false; qWait = false; // Was in qWait mode? Reset occured, no more qWait now. if (frame > 0 && status & (STATUS_PLAY | STATUS_ENDING)) tracker = fillChan(vChan, tracker, frame); } /* -------------------------------------------------------------------------- */ void SampleChannel::empty() { status = STATUS_OFF; if (wave != nullptr) { delete wave; wave = nullptr; } begin = 0; end = 0; tracker = 0; status = STATUS_EMPTY; volume = G_DEFAULT_VOL; boost = G_DEFAULT_BOOST; sendMidiLplay(); } /* -------------------------------------------------------------------------- */ void SampleChannel::pushWave(Wave* w) { sendMidiLplay(); // FIXME - why here?!?! wave = w; status = STATUS_OFF; begin = 0; end = (wave->getSize() - 1) * wave->getChannels(); // TODO - Opaque channels' count name = wave->getBasename(); } /* -------------------------------------------------------------------------- */ void SampleChannel::process(float* outBuffer, float* inBuffer) { /* If armed and inbuffer is not nullptr (i.e. input device available) and input monitor is on, copy input buffer to vChan: this enables the input monitoring. The vChan will be overwritten later by pluginHost::processStack, so that you would record "clean" audio (i.e. not plugin-processed). */ if (armed && inBuffer && inputMonitor) for (int i=0; i= end) { int offset = end - trackerPreview; trackerPreview = fillChan(vChanPreview, trackerPreview, 0, false); trackerPreview = begin; if (previewMode == G_PREVIEW_LOOP) trackerPreview = fillChan(vChanPreview, begin, offset, false); else if (previewMode == G_PREVIEW_NORMAL) { previewMode = G_PREVIEW_NONE; if (onPreviewEnd) onPreviewEnd(); } } else trackerPreview = fillChan(vChanPreview, trackerPreview, 0, false); for (int j=0; jmode; boost = pch->boost; readActions = pch->recActive; recStatus = readActions ? REC_READING : REC_STOPPED; midiInReadActions = pch->midiInReadActions; midiInPitch = pch->midiInPitch; inputMonitor = pch->inputMonitor; Wave* w = nullptr; int res = waveManager::create(basePath + pch->samplePath, &w); if (res == G_RES_OK) { pushWave(w); setName(pch->name); setBegin(pch->begin); setEnd(pch->end); setPitch(pch->pitch); } else { if (res == G_RES_ERR_NO_DATA) status = STATUS_EMPTY; else if (res == G_RES_ERR_IO) status = STATUS_MISSING; sendMidiLplay(); // FIXME - why sending MIDI lightning if sample status is wrong? } return res; } /* -------------------------------------------------------------------------- */ bool SampleChannel::canInputRec() { return wave == nullptr && armed; } /* -------------------------------------------------------------------------- */ void SampleChannel::start(int frame, bool doQuantize, int quantize, bool mixerIsRunning, bool forceStart, bool isUserGenerated) { switch (status) { case STATUS_EMPTY: case STATUS_MISSING: case STATUS_WRONG: { return; } case STATUS_OFF: { if (mode & LOOP_ANY) { if (forceStart) { status = STATUS_PLAY; tracker = frame; } else status = STATUS_WAIT; sendMidiLplay(); } else { if (quantize > 0 && mixerIsRunning && doQuantize) qWait = true; else { status = STATUS_PLAY; sendMidiLplay(); /* Do fillChan only if this is not a user-generated event (i.e. is an action read by Mixer). Otherwise clear() will take take of calling fillChan on the next cycle. */ if (!isUserGenerated) tracker = fillChan(vChan, tracker, frame); } } break; } case STATUS_PLAY: { if (mode == SINGLE_BASIC) setFadeOut(DO_STOP); else if (mode == SINGLE_RETRIG) { if (quantize > 0 && mixerIsRunning && doQuantize) qWait = true; else reset(frame); } else if (mode & (LOOP_ANY | SINGLE_ENDLESS)) { status = STATUS_ENDING; sendMidiLplay(); } break; } case STATUS_WAIT: { status = STATUS_OFF; sendMidiLplay(); break; } case STATUS_ENDING: { status = STATUS_PLAY; sendMidiLplay(); break; } } } /* -------------------------------------------------------------------------- */ int SampleChannel::writePatch(int i, bool isProject) { // TODO - this code belongs to an upper level (glue) int pchIndex = Channel::writePatch(i, isProject); patch::channel_t *pch = &patch::channels.at(pchIndex); if (wave != nullptr) { pch->samplePath = wave->getPath(); if (isProject) pch->samplePath = gu_basename(wave->getPath()); // make it portable } else pch->samplePath = ""; pch->mode = mode; pch->begin = begin; pch->end = end; pch->boost = boost; pch->recActive = readActions; pch->pitch = pitch; pch->inputMonitor = inputMonitor; pch->midiInReadActions = midiInReadActions; pch->midiInPitch = midiInPitch; return 0; } /* -------------------------------------------------------------------------- */ void SampleChannel::clearChan(float *dest, int start) { memset(dest+start, 0, sizeof(float)*(bufferSize-start)); } /* -------------------------------------------------------------------------- */ int SampleChannel::fillChan(float *dest, int start, int offset, bool rewind) { int position; // return value: the new position if (pitch == 1.0f) { /* case 1: 'dest' lies within the original sample boundaries (start- * end) */ if (start+bufferSize-offset <= end) { memcpy(dest+offset, wave->getData()+start, (bufferSize-offset)*sizeof(float)); position = start+bufferSize-offset; if (rewind) frameRewind = -1; } /* case2: 'dest' lies outside the end of the sample, OR the sample * is smaller than 'dest' */ else { memcpy(dest+offset, wave->getData()+start, (end-start)*sizeof(float)); position = end; if (rewind) frameRewind = end-start+offset; } } else { rsmp_data.data_in = wave->getData()+start; // source data rsmp_data.input_frames = (end-start)/2; // how many readable bytes rsmp_data.data_out = dest+offset; // destination (processed data) rsmp_data.output_frames = (bufferSize-offset)/2; // how many bytes to process rsmp_data.end_of_input = false; src_process(rsmp_state, &rsmp_data); int gen = rsmp_data.output_frames_gen*2; // frames generated by this call position = start + rsmp_data.input_frames_used*2; // position goes forward of frames_used (i.e. read from wave) if (rewind) { if (gen == bufferSize-offset) frameRewind = -1; else frameRewind = gen+offset; } } return position; } giada-0.14.5/src/core/sampleChannel.h000066400000000000000000000131511322662744500173240ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef G_SAMPLE_CHANNEL_H #define G_SAMPLE_CHANNEL_H #include #include #include "channel.h" class Patch; class Wave; class SampleChannel : public Channel { private: /* fillChan Fills 'dest' buffer at point 'offset' with wave data taken from 'start'. If rewind=false don't rewind internal tracker. Returns new sample position, in frames. It resamples data if pitch != 1.0f. */ int fillChan(float* dest, int start, int offset, bool rewind=true); /* clearChan * set data to zero from start to bufferSize-1. */ void clearChan(float* dest, int start); /* calcFadeoutStep * how many frames are left before the end of the sample? Is there * enough room for a complete fadeout? Should we shorten it? */ void calcFadeoutStep(); /* calcVolumeEnv * compute any changes in volume done via envelope tool */ void calcVolumeEnv(int frame); /* rsmp_state, rsmp_data * structs from libsamplerate */ SRC_STATE* rsmp_state; SRC_DATA rsmp_data; /* pChan Extra virtual channel for processing resampled data. */ float* pChan; /* pChan Extra virtual channel for audio preview. */ float* vChanPreview; /* frameRewind Exact frame in which a rewind occurs. */ int frameRewind; int begin; int end; float boost; float pitch; int trackerPreview; // chan position for audio preview int shift; /* onPreviewEnd A callback fired when audio preview ends. */ std::function onPreviewEnd; public: SampleChannel(int bufferSize, bool inputMonitor); ~SampleChannel(); void copy(const Channel* src, pthread_mutex_t* pluginMutex) override; void clear() override; void process(float* outBuffer, float* inBuffer) override; void preview(float* outBuffer) override; void start(int frame, bool doQuantize, int quantize, bool mixerIsRunning, bool forceStart, bool isUserGenerated) override; void kill(int frame) override; void empty() override; void stopBySeq(bool chansStopOnSeqHalt) override; void stop() override; void rewind() override; void setMute(bool internal) override; void unsetMute(bool internal) override; int readPatch(const std::string& basePath, int i, pthread_mutex_t* pluginMutex, int samplerate, int rsmpQuality) override; int writePatch(int i, bool isProject) override; void quantize(int index, int localFrame) override; void onZero(int frame, bool recsStopOnChanHalt) override; void onBar(int frame) override; void parseAction(giada::m::recorder::action* a, int localFrame, int globalFrame, int quantize, bool mixerIsRunning) override; bool canInputRec() override; bool allocBuffers() override; int getTrackerPreview() const; int getShift() const; void setShift(int s); void reset(int frame); /* fade methods * prepare channel for fade, mixer will take care of the process * during master play. */ void setFadeIn(bool internal); void setFadeOut(int actionPostFadeout); void setXFade(int frame); /* pushWave Adds a new wave to an existing channel. */ void pushWave(Wave* w); /* getPosition * returns the position of an active sample. If EMPTY o MISSING * returns -1. */ int getPosition(); /* sum * add sample frames to virtual channel. Frame = processed frame in * Mixer. Running = is Mixer in play? */ void sum(int frame, bool running); void setPitch(float v); float getPitch(); void setBegin(int f); int getBegin(); void setEnd(int f); int getEnd(); void setTrackerPreview(int f); /* hardStop * stop the channel immediately, no further checks. */ void hardStop(int frame); /* setReadActions * if enabled (v == true), recorder will read actions from this channel. If * recsStopOnChanHalt == true, stop reading actions right away. */ void setReadActions(bool v, bool recsStopOnChanHalt); void setBoost(float v); float getBoost(); void setOnEndPreviewCb(std::function f); Wave* wave; int tracker; // chan position int mode; // mode: see const.h bool qWait; // quantizer wait bool fadeinOn; float fadeinVol; bool fadeoutOn; float fadeoutVol; // fadeout volume int fadeoutTracker; // tracker fadeout, xfade only float fadeoutStep; // fadeout decrease int fadeoutType; // xfade or fadeout int fadeoutEnd; // what to do when fadeout ends bool inputMonitor; /* midi stuff */ uint32_t midiInReadActions; uint32_t midiInPitch; /* const - what to do when a fadeout ends */ enum { DO_STOP = 0x01, DO_MUTE = 0x02, DO_MUTE_I = 0x04 }; /* const - fade types */ enum { FADEOUT = 0x01, XFADE = 0x02 }; }; #endif giada-0.14.5/src/core/storager.cpp000066400000000000000000000100461322662744500167330ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include #include "../utils/log.h" #include "storager.h" using std::string; namespace giada { namespace m { namespace storager { bool setString(json_t* jRoot, const char* key, string& output) { json_t* jObject = json_object_get(jRoot, key); if (!jObject) { gu_log("[storager::setString] key '%s' not found, using default value\n", key); output = ""; return true; } if (!json_is_string(jObject)) { gu_log("[storager::setString] key '%s' is not a string!\n", key); json_decref(jRoot); return false; } output = json_string_value(jObject); return true; } /* -------------------------------------------------------------------------- */ bool setFloat(json_t* jRoot, const char* key, float& output) { json_t* jObject = json_object_get(jRoot, key); if (!jObject) { gu_log("[storager::setFloat] key '%s' not found, using default value\n", key); output = 0.0f; return true; } if (!json_is_real(jObject)) { gu_log("[storager::setFloat] key '%s' is not a float!\n", key); json_decref(jRoot); return false; } output = json_real_value(jObject); return true; } /* -------------------------------------------------------------------------- */ bool setUint32(json_t* jRoot, const char* key, uint32_t &output) { json_t* jObject = json_object_get(jRoot, key); if (!jObject) { gu_log("[storager::setUint32] key '%s' not found, using default value\n", key); output = 0; return true; } if (!json_is_integer(jObject)) { gu_log("[storager::setUint32] key '%s' is not an integer!\n", key); json_decref(jRoot); return false; } output = json_integer_value(jObject); return true; } /* -------------------------------------------------------------------------- */ bool setBool(json_t* jRoot, const char* key, bool& output) { json_t* jObject = json_object_get(jRoot, key); if (!jObject) { gu_log("[storager::setBool] key '%s' not found, using default value\n", key); output = false; return true; } if (!json_is_boolean(jObject)) { gu_log("[storager::setBool] key '%s' is not a boolean!\n", key); json_decref(jRoot); return false; } output = json_boolean_value(jObject); return true; } /* -------------------------------------------------------------------------- */ bool setInt(json_t* jRoot, const char* key, int& output) { return setUint32(jRoot, key, (uint32_t&) output); } /* -------------------------------------------------------------------------- */ bool checkObject(json_t* jRoot, const char* key) { if (!json_is_object(jRoot)) { gu_log("[DataStorageJson::checkObject] malformed json: %s is not an object!\n", key); json_decref(jRoot); return false; } return true; } /* -------------------------------------------------------------------------- */ bool checkArray(json_t* jRoot, const char* key) { if (!json_is_array(jRoot)) { gu_log("[DataStorageJson::checkObject] malformed json: %s is not an array!\n", key); json_decref(jRoot); return false; } return true; } }}}; // giada::m::storager:: giada-0.14.5/src/core/storager.h000066400000000000000000000034761322662744500164110ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef G_STORAGER_H #define G_STORAGER_H #include namespace giada { namespace m { namespace storager { bool setString(json_t *jRoot, const char *key, std::string &output); bool setFloat(json_t *jRoot, const char *key, float &output); bool setUint32(json_t *jRoot, const char *key, uint32_t &output); bool setInt(json_t *jRoot, const char *key, int &output); bool setBool(json_t *jRoot, const char *key, bool &output); /* checkObject check whether the jRoot object is a valid json object {} */ bool checkObject(json_t *jRoot, const char *key); /* checkArray check whether the jRoot object is a valid json array [] */ bool checkArray(json_t *jRoot, const char *key); }}}; // giada::m::storager:: #endif giada-0.14.5/src/core/wave.cpp000066400000000000000000000107571322662744500160600ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * wave * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include #include // memcpy #include "../utils/fs.h" #include "../utils/log.h" #include "../utils/string.h" #include "const.h" #include "wave.h" using std::string; Wave::Wave() : m_data (nullptr), m_size (0), m_logical(0), m_edited (0) {} /* -------------------------------------------------------------------------- */ Wave::Wave(float* data, int size, int channels, int rate, int bits, const std::string& path) : m_data (data), m_size (size), m_channels(channels), m_rate (rate), m_bits (bits), m_logical (false), m_edited (false), m_path (path) { } /* -------------------------------------------------------------------------- */ Wave::~Wave() { clear(); } /* -------------------------------------------------------------------------- */ Wave::Wave(const Wave& other) : m_data (nullptr), m_size (other.m_size), m_channels(other.m_channels), m_rate (other.m_rate), m_bits (other.m_bits), m_logical (true), // a cloned wave does not exist on disk m_edited (false), m_path (other.m_path) { m_data = new float[m_size]; memcpy(m_data, other.m_data, m_size * sizeof(float)); } /* -------------------------------------------------------------------------- */ void Wave::clear() { free(); m_path = ""; m_size = 0; } /* -------------------------------------------------------------------------- */ void Wave::free() { if (m_data == nullptr) return; delete[] m_data; m_data = nullptr; } /* -------------------------------------------------------------------------- */ string Wave::getBasename(bool ext) const { return ext ? gu_basename(m_path) : gu_stripExt(gu_basename(m_path)); } /* -------------------------------------------------------------------------- */ int Wave::getRate() const { return m_rate; } int Wave::getChannels() const { return m_channels; } std::string Wave::getPath() const { return m_path; } float* Wave::getData() const { return m_data; } int Wave::getSize() const { return m_size / m_channels; } int Wave::getBits() const { return m_bits; } bool Wave::isLogical() const { return m_logical; } bool Wave::isEdited() const { return m_edited; } /* -------------------------------------------------------------------------- */ int Wave::getDuration() const { return m_size / m_channels / m_rate; } /* -------------------------------------------------------------------------- */ std::string Wave::getExtension() const { return gu_getExt(m_path); } /* -------------------------------------------------------------------------- */ float* Wave::getFrame(int f) const { assert(f >= 0); assert(f < getSize()); f *= m_channels; // convert frame to sample return m_data + f; // i.e. a pointer to m_data[f] } /* -------------------------------------------------------------------------- */ void Wave::setRate(int v) { m_rate = v; } void Wave::setChannels(int v) { m_channels = v; } void Wave::setLogical(bool l) { m_logical = l; } void Wave::setEdited(bool e) { m_edited = e; } /* -------------------------------------------------------------------------- */ void Wave::setPath(const string& p, int id) { if (id == -1) m_path = p; else m_path = gu_stripExt(p) + "-" + gu_iToString(id) + "." + gu_getExt(p); } /* -------------------------------------------------------------------------- */ void Wave::setData(float* d, int size) { m_data = d; m_size = size; } giada-0.14.5/src/core/wave.h000066400000000000000000000052761322662744500155250ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * wave * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef G_WAVE_H #define G_WAVE_H #include #include #include "const.h" class Wave { private: float* m_data; int m_size; // Wave size in bytes (size in stereo: size / 2) int m_channels; int m_rate; int m_bits; bool m_logical; // memory only (a take) bool m_edited; // edited via editor std::string m_path; // E.g. /path/to/my/sample.wav public: Wave(); Wave(float* data, int size, int channels, int rate, int bits, const std::string& path); ~Wave(); Wave(const Wave& other); void setRate(int v); void setChannels(int v); void setData(float* data, int size); void setLogical(bool l); void setEdited(bool e); /* setPath Sets new path 'p'. If 'id' != -1 inserts a numeric id next to the file extension, e.g. : /path/to/sample-[id].wav */ void setPath(const std::string& p, int id=-1); std::string getBasename(bool ext=false) const; std::string getExtension() const; int getRate() const; int getChannels() const; std::string getPath() const; int getBits() const; float* getData() const; int getSize() const; // with channels count int getDuration() const; bool isLogical() const; bool isEdited() const; /* clear Resets Wave to init state. */ void clear(); /* free Frees memory, leaving everything else untouched. */ void free(); /* getFrame Given a frame 'f', returns a pointer to it. This is useful for digging inside a frame, i.e. parsing each channel. How to use it: float* frame = w->getFrame(40); for (int i=0; igetChannels(); i++) ... frame[i] ... */ float* getFrame(int f) const; }; #endif giada-0.14.5/src/core/waveFx.cpp000066400000000000000000000176401322662744500163540ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include #include #include "../utils/log.h" #include "wave.h" #include "waveFx.h" namespace giada { namespace m { namespace wfx { namespace { void fadeFrame(Wave* w, int i, float val) { float* frame = w->getFrame(i); for (int j=0; jgetChannels(); j++) frame[j] *= val; } /* -------------------------------------------------------------------------- */ float getPeak(Wave* w, int a, int b) { float peak = 0.0f; float abs = 0.0f; for (int i=a; igetFrame(i); for (int j=0; jgetChannels(); j++) // Find highest value in any channel abs = fabs(frame[j]); if (abs > peak) peak = abs; } return peak; } }; // {anonymous} /* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */ float normalizeSoft(Wave* w) { float peak = getPeak(w, 0, w->getSize()); /* peak == 0.0f: don't normalize the silence * peak > 1.0f: don't reduce the amplitude, just leave it alone */ if (peak == 0.0f || peak > 1.0f) return 1.0f; return 1.0f / peak; } /* -------------------------------------------------------------------------- */ void normalizeHard(Wave* w, int a, int b) { float peak = getPeak(w, a, b); if (peak == 0.0f || peak > 1.0f) // as in ::normalizeSoft return; for (int i=a; igetFrame(i); for (int j=0; jgetChannels(); j++) frame[j] = frame[j] * (1.0f / peak); } w->setEdited(true); } /* -------------------------------------------------------------------------- */ int monoToStereo(Wave* w) { if (w->getChannels() >= G_DEFAULT_AUDIO_CHANS) return G_RES_OK; unsigned newSize = w->getSize() * G_DEFAULT_AUDIO_CHANS; float* newData = new (std::nothrow) float[newSize]; if (newData == nullptr) { gu_log("[wfx::monoToStereo] unable to allocate memory!\n"); return G_RES_ERR_MEMORY; } for (int i=0, k=0; igetSize(); i++, k+=G_DEFAULT_AUDIO_CHANS) { float* frame = w->getFrame(i); for (int j=0; jfree(); w->setData(newData, newSize); w->setChannels(G_DEFAULT_AUDIO_CHANS); return G_RES_OK; } /* -------------------------------------------------------------------------- */ void silence(Wave* w, int a, int b) { gu_log("[wfx::silence] silencing from %d to %d\n", a, b); for (int i=a; igetFrame(i); for (int j=0; jgetChannels(); j++) frame[j] = 0.0f; } w->setEdited(true); } /* -------------------------------------------------------------------------- */ int cut(Wave* w, int a, int b) { if (a < 0) a = 0; if (b > w->getSize()) b = w->getSize(); /* Create a new temp wave and copy there the original one, skipping the a-b range. */ unsigned newSize = (w->getSize() - (b - a)) * w->getChannels(); float* newData = new (std::nothrow) float[newSize]; if (newData == nullptr) { gu_log("[wfx::cut] unable to allocate memory!\n"); return G_RES_ERR_MEMORY; } gu_log("[wfx::cut] cutting from %d to %d\n", a, b); for (int i=0, k=0; igetSize(); i++) { if (i < a || i >= b) { float* frame = w->getFrame(i); for (int j=0; jgetChannels(); j++) newData[k+j] = frame[j]; k += w->getChannels(); } } w->free(); w->setData(newData, newSize); w->setEdited(true); return G_RES_OK; } /* -------------------------------------------------------------------------- */ int trim(Wave* w, int a, int b) { if (a < 0) a = 0; if (b > w->getSize()) b = w->getSize(); int newSize = (b - a) * w->getChannels(); float* newData = new (std::nothrow) float[newSize]; if (newData == nullptr) { gu_log("[wfx::trim] unable to allocate memory!\n"); return G_RES_ERR_MEMORY; } gu_log("[wfx::trim] trimming from %d to %d (area = %d)\n", a, b, b-a); for (int i=a, k=0; igetChannels()) { float* frame = w->getFrame(i); for (int j=0; jgetChannels(); j++) newData[k+j] = frame[j]; } w->free(); w->setData(newData, newSize); w->setEdited(true); return G_RES_OK; } /* -------------------------------------------------------------------------- */ int paste(Wave* src, Wave* des, int aFrame) { int srcNumSamples = src->getSize() * src->getChannels(); int desNumSamples = des->getSize() * des->getChannels(); int aSample = aFrame * des->getChannels(); int newSize = srcNumSamples + desNumSamples; float* newData = new (std::nothrow) float[newSize]; if (newData == nullptr) { gu_log("[wfx::paste] unable to allocate memory!\n"); return G_RES_ERR_MEMORY; } /* |---original data---|///paste data///|---original data---| chunk 1 chunk 2 chunk 3 */ float* chunk1a = des->getData(); float* chunk1b = des->getData() + aSample; float* chunk2a = src->getData(); float* chunk2b = src->getData() + srcNumSamples; float* chunk3a = chunk1b; float* chunk3b = des->getData() + desNumSamples; std::copy(chunk1a, chunk1b, newData); std::copy(chunk2a, chunk2b, newData + aSample); std::copy(chunk3a, chunk3b, newData + aSample + srcNumSamples); des->free(); des->setData(newData, newSize); des->setEdited(true); return G_RES_OK; } /* -------------------------------------------------------------------------- */ void fade(Wave* w, int a, int b, int type) { gu_log("[wfx::fade] fade from %d to %d (range = %d)\n", a, b, b-a); float m = 0.0f; float d = 1.0f / (float) (b - a); if (type == FADE_IN) for (int i=a; i<=b; i++, m+=d) fadeFrame(w, i, m); else for (int i=b; i>=a; i--, m+=d) fadeFrame(w, i, m); w->setEdited(true); } /* -------------------------------------------------------------------------- */ void smooth(Wave* w, int a, int b) { /* Do nothing if fade edges (both of SMOOTH_SIZE samples) are > than selected portion of wave. SMOOTH_SIZE*2 to count both edges. */ if (SMOOTH_SIZE*2 > (b-a)) { gu_log("[wfx::smooth] selection is too small, nothing to do\n"); return; } fade(w, a, a+SMOOTH_SIZE, FADE_IN); fade(w, b-SMOOTH_SIZE, b, FADE_OUT); w->setEdited(true); } /* -------------------------------------------------------------------------- */ void shift(Wave* w, int offset) { if (offset < 0) offset = (w->getSize() + w->getChannels()) + offset; float* begin = w->getData(); float* end = w->getData() + (w->getSize() * w->getChannels()); std::rotate(begin, end - (offset * w->getChannels()), end); w->setEdited(true); } /* -------------------------------------------------------------------------- */ void reverse(Wave* w, int a, int b) { /* https://stackoverflow.com/questions/33201528/reversing-an-array-of-structures-in-c */ float* begin = w->getData() + (a * w->getChannels()); float* end = w->getData() + (b * w->getChannels()); std::reverse(begin, end); w->setEdited(true); } }}}; // giada::m::wfx::giada-0.14.5/src/core/waveFx.h000066400000000000000000000040541322662744500160140ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef G_WAVE_FX_H #define G_WAVE_FX_H class Wave; namespace giada { namespace m { namespace wfx { static const int FADE_IN = 0; static const int FADE_OUT = 1; static const int SMOOTH_SIZE = 32; /* normalizeSoft Normalizes the wave by returning the dB value for the boost volume. */ float normalizeSoft(Wave* w); /* normalizeHard Normalizes the wave in range a-b by altering values in memory. */ void normalizeHard(Wave* w, int a, int b); int monoToStereo(Wave* w); void silence(Wave* w, int a, int b); int cut(Wave* w, int a, int b); int trim(Wave* w, int a, int b); int paste(Wave* src, Wave* dest, int a); /* fade Fades in or fades out selection. Fade In = type 0, Fade Out = type 1 */ void fade(Wave* w, int a, int b, int type); /* smooth Smooth edges of selection. */ void smooth(Wave* w, int a, int b); /* reverse Flips Wave's data. */ void reverse(Wave* w, int a, int b); void shift(Wave* w, int offset); }}}; // giada::m::wfx:: #endif giada-0.14.5/src/core/waveManager.cpp000066400000000000000000000147641322662744500173550ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include #include #include #include "../utils/log.h" #include "../utils/fs.h" #include "const.h" #include "wave.h" #include "waveFx.h" #include "waveManager.h" using std::string; namespace giada { namespace m { namespace waveManager { namespace { int getBits(SF_INFO& header) { if (header.format & SF_FORMAT_PCM_S8) return 8; else if (header.format & SF_FORMAT_PCM_16) return 16; else if (header.format & SF_FORMAT_PCM_24) return 24; else if (header.format & SF_FORMAT_PCM_32) return 32; else if (header.format & SF_FORMAT_PCM_U8) return 8; else if (header.format & SF_FORMAT_FLOAT) return 32; else if (header.format & SF_FORMAT_DOUBLE) return 64; return 0; } }; // {anonymous} /* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */ int create(const string& path, Wave** out) { if (path == "" || gu_isDir(path)) { gu_log("[waveManager::create] malformed path (was '%s')\n", path.c_str()); return G_RES_ERR_NO_DATA; } if (path.size() > FILENAME_MAX) return G_RES_ERR_PATH_TOO_LONG; SF_INFO header; SNDFILE* fileIn = sf_open(path.c_str(), SFM_READ, &header); if (fileIn == nullptr) { gu_log("[waveManager::create] unable to read %s. %s\n", path.c_str(), sf_strerror(fileIn)); return G_RES_ERR_IO; } if (header.channels > 2) { gu_log("[waveManager::create] unsupported multi-channel sample\n"); return G_RES_ERR_WRONG_DATA; } /* Libsndfile's frame structure: frame1 = [leftChannel, rightChannel] frame2 = [leftChannel, rightChannel] ... */ int size = header.frames * header.channels; float* data = new (std::nothrow) float[size]; if (data == nullptr) { gu_log("[waveManager::create] unable to allocate memory\n"); return G_RES_ERR_MEMORY; } if (sf_read_float(fileIn, data, size) != size) gu_log("[waveManager::create] warning: incomplete read!\n"); sf_close(fileIn); Wave* wave = new Wave(data, size, header.channels, header.samplerate, getBits(header), path); if (header.channels == 1 && !wfx::monoToStereo(wave)) { delete wave; return G_RES_ERR_PROCESSING; } *out = wave; gu_log("[waveManager::create] new Wave created, %d frames\n", wave->getSize()); return G_RES_OK; } /* -------------------------------------------------------------------------- */ int createEmpty(int size, int samplerate, const string& name, Wave** out) { float* data = new (std::nothrow) float[size]; if (data == nullptr) { gu_log("[waveManager::createEmpty] unable to allocate memory\n"); return G_RES_ERR_MEMORY; } Wave* wave = new Wave(data, size, G_DEFAULT_AUDIO_CHANS, samplerate, G_DEFAULT_BIT_DEPTH, ""); wave->setLogical(true); *out = wave; gu_log("[waveManager::createEmpty] new empty Wave created, %d frames\n", size); return G_RES_OK; } /* -------------------------------------------------------------------------- */ int createFromWave(const Wave* src, int a, int b, Wave** out) { int numChans = src->getChannels(); int size = (b - a) * numChans; float* data = new (std::nothrow) float[size]; if (data == nullptr) { gu_log("[waveManager::createFromWave] unable to allocate memory\n"); return G_RES_ERR_MEMORY; } std::copy(src->getData() + (a*numChans), src->getData() + (b*numChans), data); Wave* wave = new Wave(data, size, numChans, src->getRate(), src->getBits(), src->getPath()); wave->setLogical(true); *out = wave; gu_log("[waveManager::createFromWave] new Wave created, %d frames\n", size); return G_RES_OK; } /* -------------------------------------------------------------------------- */ int resample(Wave* w, int quality, int samplerate) { float ratio = samplerate / (float) w->getRate(); int newSizeFrames = ceil(w->getSize() * ratio); int newSizeSamples = newSizeFrames * w->getChannels(); float* newData = new (std::nothrow) float[newSizeSamples]; if (newData == nullptr) { gu_log("[waveManager::resample] unable to allocate memory\n"); return G_RES_ERR_MEMORY; } SRC_DATA src_data; src_data.data_in = w->getData(); src_data.input_frames = w->getSize(); src_data.data_out = newData; src_data.output_frames = newSizeFrames; src_data.src_ratio = ratio; gu_log("[waveManager::resample] resampling: new size=%d (%d frames)\n", newSizeSamples, newSizeFrames); int ret = src_simple(&src_data, quality, w->getChannels()); if (ret != 0) { gu_log("[waveManager::resample] resampling error: %s\n", src_strerror(ret)); delete[] newData; return G_RES_ERR_PROCESSING; } w->free(); w->setData(newData, newSizeSamples); w->setRate(samplerate); return G_RES_OK; } /* -------------------------------------------------------------------------- */ int save(Wave* w, const string& path) { SF_INFO header; header.samplerate = w->getRate(); header.channels = w->getChannels(); header.format = SF_FORMAT_WAV | SF_FORMAT_FLOAT; SNDFILE* file = sf_open(path.c_str(), SFM_WRITE, &header); if (file == nullptr) { gu_log("[waveManager::save] unable to open %s for exporting: %s\n", path.c_str(), sf_strerror(file)); return G_RES_ERR_IO; } if (sf_writef_float(file, w->getData(), w->getSize()) != w->getSize()) gu_log("[waveManager::save] warning: incomplete write!\n"); sf_close(file); w->setLogical(false); w->setEdited(false); return G_RES_OK; } }}}; // giada::m::waveManager giada-0.14.5/src/core/waveManager.h000066400000000000000000000035301322662744500170070ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef G_WAVE_MANAGER_H #define G_WAVE_MANAGER_H #include class Wave; namespace giada { namespace m { namespace waveManager { /* create Creates a new Wave object with data read from file 'path'. */ int create(const std::string& path, Wave** out); /* createEmpty Creates a new silent Wave object. Note: 'size' must take 2 channels into account (stereo). */ int createEmpty(int size, int samplerate, const std::string& name, Wave** out); /* createFromWave Creates a new Wave from an existing one, copying the data in range a - b. */ int createFromWave(const Wave* src, int a, int b, Wave** out); int resample(Wave* w, int quality, int samplerate); int save(Wave* w, const std::string& path); }}}; // giada::m::waveManager #endifgiada-0.14.5/src/deps/000077500000000000000000000000001322662744500144035ustar00rootroot00000000000000giada-0.14.5/src/deps/juce-config.h000066400000000000000000000011251322662744500167440ustar00rootroot00000000000000#ifndef JUCE_APPCONFIG_H #define JUCE_APPCONFIG_H #ifdef _WIN32 #include #include #endif #include "juce/modules/juce_audio_basics/juce_audio_basics.h" #include "juce/modules/juce_audio_processors/juce_audio_processors.h" #include "juce/modules/juce_core/juce_core.h" #include "juce/modules/juce_data_structures/juce_data_structures.h" #include "juce/modules/juce_events/juce_events.h" #include "juce/modules/juce_graphics/juce_graphics.h" #include "juce/modules/juce_gui_basics/juce_gui_basics.h" #include "juce/modules/juce_gui_extra/juce_gui_extra.h" #endif giada-0.14.5/src/deps/rtaudio-mod/000077500000000000000000000000001322662744500166275ustar00rootroot00000000000000giada-0.14.5/src/deps/rtaudio-mod/RtAudio.cpp000077500000000000000000013224531322662744500207170ustar00rootroot00000000000000/************************************************************************/ /*! \class RtAudio \brief Realtime audio i/o C++ classes. RtAudio provides a common API (Application Programming Interface) for realtime audio input/output across Linux (native ALSA, Jack, and OSS), Macintosh OS X (CoreAudio and Jack), and Windows (DirectSound, ASIO and WASAPI) operating systems. RtAudio WWW site: http://www.music.mcgill.ca/~gary/rtaudio/ RtAudio: realtime audio i/o C++ classes Copyright (c) 2001-2016 Gary P. Scavone Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. Any person wishing to distribute modifications to the Software is asked to send the modifications to the original developer so that they can be incorporated into the canonical version. This is, however, not a binding provision of this license. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /************************************************************************/ // RtAudio: Version 4.1.2 #include "RtAudio.h" #include #include #include #include #include // Static variable definitions. const unsigned int RtApi::MAX_SAMPLE_RATES = 14; const unsigned int RtApi::SAMPLE_RATES[] = { 4000, 5512, 8000, 9600, 11025, 16000, 22050, 32000, 44100, 48000, 88200, 96000, 176400, 192000 }; #if defined(__WINDOWS_DS__) || defined(__WINDOWS_ASIO__) || defined(__WINDOWS_WASAPI__) #define MUTEX_INITIALIZE(A) InitializeCriticalSection(A) #define MUTEX_DESTROY(A) DeleteCriticalSection(A) #define MUTEX_LOCK(A) EnterCriticalSection(A) #define MUTEX_UNLOCK(A) LeaveCriticalSection(A) #include "tchar.h" static std::string convertCharPointerToStdString(const char *text) { return std::string(text); } static std::string convertCharPointerToStdString(const wchar_t *text) { int length = WideCharToMultiByte(CP_UTF8, 0, text, -1, NULL, 0, NULL, NULL); std::string s( length-1, '\0' ); WideCharToMultiByte(CP_UTF8, 0, text, -1, &s[0], length, NULL, NULL); return s; } #elif defined(__LINUX_ALSA__) || defined(__LINUX_PULSE__) || defined(__UNIX_JACK__) || defined(__LINUX_OSS__) || defined(__MACOSX_CORE__) // pthread API #define MUTEX_INITIALIZE(A) pthread_mutex_init(A, NULL) #define MUTEX_DESTROY(A) pthread_mutex_destroy(A) #define MUTEX_LOCK(A) pthread_mutex_lock(A) #define MUTEX_UNLOCK(A) pthread_mutex_unlock(A) #else #define MUTEX_INITIALIZE(A) abs(*A) // dummy definitions #define MUTEX_DESTROY(A) abs(*A) // dummy definitions #endif // *************************************************** // // // RtAudio definitions. // // *************************************************** // std::string RtAudio :: getVersion( void ) throw() { return RTAUDIO_VERSION; } void RtAudio :: getCompiledApi( std::vector &apis ) throw() { apis.clear(); // The order here will control the order of RtAudio's API search in // the constructor. #if defined(__UNIX_JACK__) apis.push_back( UNIX_JACK ); #endif #if defined(__LINUX_ALSA__) apis.push_back( LINUX_ALSA ); #endif #if defined(__LINUX_PULSE__) apis.push_back( LINUX_PULSE ); #endif #if defined(__LINUX_OSS__) apis.push_back( LINUX_OSS ); #endif #if defined(__WINDOWS_ASIO__) apis.push_back( WINDOWS_ASIO ); #endif #if defined(__WINDOWS_WASAPI__) apis.push_back( WINDOWS_WASAPI ); #endif #if defined(__WINDOWS_DS__) apis.push_back( WINDOWS_DS ); #endif #if defined(__MACOSX_CORE__) apis.push_back( MACOSX_CORE ); #endif #if defined(__RTAUDIO_DUMMY__) apis.push_back( RTAUDIO_DUMMY ); #endif } void RtAudio :: openRtApi( RtAudio::Api api ) { if ( rtapi_ ) delete rtapi_; rtapi_ = 0; #if defined(__UNIX_JACK__) if ( api == UNIX_JACK ) rtapi_ = new RtApiJack(); #endif #if defined(__LINUX_ALSA__) if ( api == LINUX_ALSA ) rtapi_ = new RtApiAlsa(); #endif #if defined(__LINUX_PULSE__) if ( api == LINUX_PULSE ) rtapi_ = new RtApiPulse(); #endif #if defined(__LINUX_OSS__) if ( api == LINUX_OSS ) rtapi_ = new RtApiOss(); #endif #if defined(__WINDOWS_ASIO__) if ( api == WINDOWS_ASIO ) rtapi_ = new RtApiAsio(); #endif #if defined(__WINDOWS_WASAPI__) if ( api == WINDOWS_WASAPI ) rtapi_ = new RtApiWasapi(); #endif #if defined(__WINDOWS_DS__) if ( api == WINDOWS_DS ) rtapi_ = new RtApiDs(); #endif #if defined(__MACOSX_CORE__) if ( api == MACOSX_CORE ) rtapi_ = new RtApiCore(); #endif #if defined(__RTAUDIO_DUMMY__) if ( api == RTAUDIO_DUMMY ) rtapi_ = new RtApiDummy(); #endif } RtAudio :: RtAudio( RtAudio::Api api ) { rtapi_ = 0; if ( api != UNSPECIFIED ) { // Attempt to open the specified API. openRtApi( api ); if ( rtapi_ ) return; // No compiled support for specified API value. Issue a debug // warning and continue as if no API was specified. std::cerr << "\nRtAudio: no compiled support for specified API argument!\n" << std::endl; } // Iterate through the compiled APIs and return as soon as we find // one with at least one device or we reach the end of the list. std::vector< RtAudio::Api > apis; getCompiledApi( apis ); for ( unsigned int i=0; igetDeviceCount() ) break; } if ( rtapi_ ) return; // It should not be possible to get here because the preprocessor // definition __RTAUDIO_DUMMY__ is automatically defined if no // API-specific definitions are passed to the compiler. But just in // case something weird happens, we'll thow an error. std::string errorText = "\nRtAudio: no compiled API support found ... critical error!!\n\n"; throw( RtAudioError( errorText, RtAudioError::UNSPECIFIED ) ); } RtAudio :: ~RtAudio() throw() { if ( rtapi_ ) delete rtapi_; } void RtAudio :: openStream( RtAudio::StreamParameters *outputParameters, RtAudio::StreamParameters *inputParameters, RtAudioFormat format, unsigned int sampleRate, unsigned int *bufferFrames, RtAudioCallback callback, void *userData, RtAudio::StreamOptions *options, RtAudioErrorCallback errorCallback ) { return rtapi_->openStream( outputParameters, inputParameters, format, sampleRate, bufferFrames, callback, userData, options, errorCallback ); } // *************************************************** // // // Public RtApi definitions (see end of file for // private or protected utility functions). // // *************************************************** // RtApi :: RtApi() { stream_.state = STREAM_CLOSED; stream_.mode = UNINITIALIZED; stream_.apiHandle = 0; stream_.userBuffer[0] = 0; stream_.userBuffer[1] = 0; MUTEX_INITIALIZE( &stream_.mutex ); showWarnings_ = true; firstErrorOccurred_ = false; } RtApi :: ~RtApi() { MUTEX_DESTROY( &stream_.mutex ); } void RtApi :: openStream( RtAudio::StreamParameters *oParams, RtAudio::StreamParameters *iParams, RtAudioFormat format, unsigned int sampleRate, unsigned int *bufferFrames, RtAudioCallback callback, void *userData, RtAudio::StreamOptions *options, RtAudioErrorCallback errorCallback ) { if ( stream_.state != STREAM_CLOSED ) { errorText_ = "RtApi::openStream: a stream is already open!"; error( RtAudioError::INVALID_USE ); return; } // Clear stream information potentially left from a previously open stream. clearStreamInfo(); if ( oParams && oParams->nChannels < 1 ) { errorText_ = "RtApi::openStream: a non-NULL output StreamParameters structure cannot have an nChannels value less than one."; error( RtAudioError::INVALID_USE ); return; } if ( iParams && iParams->nChannels < 1 ) { errorText_ = "RtApi::openStream: a non-NULL input StreamParameters structure cannot have an nChannels value less than one."; error( RtAudioError::INVALID_USE ); return; } if ( oParams == NULL && iParams == NULL ) { errorText_ = "RtApi::openStream: input and output StreamParameters structures are both NULL!"; error( RtAudioError::INVALID_USE ); return; } if ( formatBytes(format) == 0 ) { errorText_ = "RtApi::openStream: 'format' parameter value is undefined."; error( RtAudioError::INVALID_USE ); return; } unsigned int nDevices = getDeviceCount(); unsigned int oChannels = 0; if ( oParams ) { oChannels = oParams->nChannels; if ( oParams->deviceId >= nDevices ) { errorText_ = "RtApi::openStream: output device parameter value is invalid."; error( RtAudioError::INVALID_USE ); return; } } unsigned int iChannels = 0; if ( iParams ) { iChannels = iParams->nChannels; if ( iParams->deviceId >= nDevices ) { errorText_ = "RtApi::openStream: input device parameter value is invalid."; error( RtAudioError::INVALID_USE ); return; } } bool result; if ( oChannels > 0 ) { result = probeDeviceOpen( oParams->deviceId, OUTPUT, oChannels, oParams->firstChannel, sampleRate, format, bufferFrames, options ); if ( result == false ) { error( RtAudioError::SYSTEM_ERROR ); return; } } if ( iChannels > 0 ) { result = probeDeviceOpen( iParams->deviceId, INPUT, iChannels, iParams->firstChannel, sampleRate, format, bufferFrames, options ); if ( result == false ) { if ( oChannels > 0 ) closeStream(); error( RtAudioError::SYSTEM_ERROR ); return; } } stream_.callbackInfo.callback = (void *) callback; stream_.callbackInfo.userData = userData; stream_.callbackInfo.errorCallback = (void *) errorCallback; if ( options ) options->numberOfBuffers = stream_.nBuffers; stream_.state = STREAM_STOPPED; } unsigned int RtApi :: getDefaultInputDevice( void ) { // Should be implemented in subclasses if possible. return 0; } unsigned int RtApi :: getDefaultOutputDevice( void ) { // Should be implemented in subclasses if possible. return 0; } void RtApi :: closeStream( void ) { // MUST be implemented in subclasses! return; } bool RtApi :: probeDeviceOpen( unsigned int /*device*/, StreamMode /*mode*/, unsigned int /*channels*/, unsigned int /*firstChannel*/, unsigned int /*sampleRate*/, RtAudioFormat /*format*/, unsigned int * /*bufferSize*/, RtAudio::StreamOptions * /*options*/ ) { // MUST be implemented in subclasses! return FAILURE; } void RtApi :: tickStreamTime( void ) { // Subclasses that do not provide their own implementation of // getStreamTime should call this function once per buffer I/O to // provide basic stream time support. stream_.streamTime += ( stream_.bufferSize * 1.0 / stream_.sampleRate ); #if defined( HAVE_GETTIMEOFDAY ) gettimeofday( &stream_.lastTickTimestamp, NULL ); #endif } long RtApi :: getStreamLatency( void ) { verifyStream(); long totalLatency = 0; if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) totalLatency = stream_.latency[0]; if ( stream_.mode == INPUT || stream_.mode == DUPLEX ) totalLatency += stream_.latency[1]; return totalLatency; } double RtApi :: getStreamTime( void ) { verifyStream(); #if defined( HAVE_GETTIMEOFDAY ) // Return a very accurate estimate of the stream time by // adding in the elapsed time since the last tick. struct timeval then; struct timeval now; if ( stream_.state != STREAM_RUNNING || stream_.streamTime == 0.0 ) return stream_.streamTime; gettimeofday( &now, NULL ); then = stream_.lastTickTimestamp; return stream_.streamTime + ((now.tv_sec + 0.000001 * now.tv_usec) - (then.tv_sec + 0.000001 * then.tv_usec)); #else return stream_.streamTime; #endif } void RtApi :: setStreamTime( double time ) { verifyStream(); if ( time >= 0.0 ) stream_.streamTime = time; } unsigned int RtApi :: getStreamSampleRate( void ) { verifyStream(); return stream_.sampleRate; } // *************************************************** // // // OS/API-specific methods. // // *************************************************** // #if defined(__MACOSX_CORE__) // The OS X CoreAudio API is designed to use a separate callback // procedure for each of its audio devices. A single RtAudio duplex // stream using two different devices is supported here, though it // cannot be guaranteed to always behave correctly because we cannot // synchronize these two callbacks. // // A property listener is installed for over/underrun information. // However, no functionality is currently provided to allow property // listeners to trigger user handlers because it is unclear what could // be done if a critical stream parameter (buffer size, sample rate, // device disconnect) notification arrived. The listeners entail // quite a bit of extra code and most likely, a user program wouldn't // be prepared for the result anyway. However, we do provide a flag // to the client callback function to inform of an over/underrun. // A structure to hold various information related to the CoreAudio API // implementation. struct CoreHandle { AudioDeviceID id[2]; // device ids #if defined( MAC_OS_X_VERSION_10_5 ) && ( MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 ) AudioDeviceIOProcID procId[2]; #endif UInt32 iStream[2]; // device stream index (or first if using multiple) UInt32 nStreams[2]; // number of streams to use bool xrun[2]; char *deviceBuffer; pthread_cond_t condition; int drainCounter; // Tracks callback counts when draining bool internalDrain; // Indicates if stop is initiated from callback or not. CoreHandle() :deviceBuffer(0), drainCounter(0), internalDrain(false) { nStreams[0] = 1; nStreams[1] = 1; id[0] = 0; id[1] = 0; xrun[0] = false; xrun[1] = false; } }; RtApiCore:: RtApiCore() { #if defined( AVAILABLE_MAC_OS_X_VERSION_10_6_AND_LATER ) // This is a largely undocumented but absolutely necessary // requirement starting with OS-X 10.6. If not called, queries and // updates to various audio device properties are not handled // correctly. CFRunLoopRef theRunLoop = NULL; AudioObjectPropertyAddress property = { kAudioHardwarePropertyRunLoop, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; OSStatus result = AudioObjectSetPropertyData( kAudioObjectSystemObject, &property, 0, NULL, sizeof(CFRunLoopRef), &theRunLoop); if ( result != noErr ) { errorText_ = "RtApiCore::RtApiCore: error setting run loop property!"; error( RtAudioError::WARNING ); } #endif } RtApiCore :: ~RtApiCore() { // The subclass destructor gets called before the base class // destructor, so close an existing stream before deallocating // apiDeviceId memory. if ( stream_.state != STREAM_CLOSED ) closeStream(); } unsigned int RtApiCore :: getDeviceCount( void ) { // Find out how many audio devices there are, if any. UInt32 dataSize; AudioObjectPropertyAddress propertyAddress = { kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; OSStatus result = AudioObjectGetPropertyDataSize( kAudioObjectSystemObject, &propertyAddress, 0, NULL, &dataSize ); if ( result != noErr ) { errorText_ = "RtApiCore::getDeviceCount: OS-X error getting device info!"; error( RtAudioError::WARNING ); return 0; } return dataSize / sizeof( AudioDeviceID ); } unsigned int RtApiCore :: getDefaultInputDevice( void ) { unsigned int nDevices = getDeviceCount(); if ( nDevices <= 1 ) return 0; AudioDeviceID id; UInt32 dataSize = sizeof( AudioDeviceID ); AudioObjectPropertyAddress property = { kAudioHardwarePropertyDefaultInputDevice, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; OSStatus result = AudioObjectGetPropertyData( kAudioObjectSystemObject, &property, 0, NULL, &dataSize, &id ); if ( result != noErr ) { errorText_ = "RtApiCore::getDefaultInputDevice: OS-X system error getting device."; error( RtAudioError::WARNING ); return 0; } dataSize *= nDevices; AudioDeviceID deviceList[ nDevices ]; property.mSelector = kAudioHardwarePropertyDevices; result = AudioObjectGetPropertyData( kAudioObjectSystemObject, &property, 0, NULL, &dataSize, (void *) &deviceList ); if ( result != noErr ) { errorText_ = "RtApiCore::getDefaultInputDevice: OS-X system error getting device IDs."; error( RtAudioError::WARNING ); return 0; } for ( unsigned int i=0; i= nDevices ) { errorText_ = "RtApiCore::getDeviceInfo: device ID is invalid!"; error( RtAudioError::INVALID_USE ); return info; } AudioDeviceID deviceList[ nDevices ]; UInt32 dataSize = sizeof( AudioDeviceID ) * nDevices; AudioObjectPropertyAddress property = { kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; OSStatus result = AudioObjectGetPropertyData( kAudioObjectSystemObject, &property, 0, NULL, &dataSize, (void *) &deviceList ); if ( result != noErr ) { errorText_ = "RtApiCore::getDeviceInfo: OS-X system error getting device IDs."; error( RtAudioError::WARNING ); return info; } AudioDeviceID id = deviceList[ device ]; // Get the device name. info.name.erase(); CFStringRef cfname; dataSize = sizeof( CFStringRef ); property.mSelector = kAudioObjectPropertyManufacturer; result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, &cfname ); if ( result != noErr ) { errorStream_ << "RtApiCore::probeDeviceInfo: system error (" << getErrorCode( result ) << ") getting device manufacturer."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); return info; } //const char *mname = CFStringGetCStringPtr( cfname, CFStringGetSystemEncoding() ); int length = CFStringGetLength(cfname); char *mname = (char *)malloc(length * 3 + 1); #if defined( UNICODE ) || defined( _UNICODE ) CFStringGetCString(cfname, mname, length * 3 + 1, kCFStringEncodingUTF8); #else CFStringGetCString(cfname, mname, length * 3 + 1, CFStringGetSystemEncoding()); #endif info.name.append( (const char *)mname, strlen(mname) ); info.name.append( ": " ); CFRelease( cfname ); free(mname); property.mSelector = kAudioObjectPropertyName; result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, &cfname ); if ( result != noErr ) { errorStream_ << "RtApiCore::probeDeviceInfo: system error (" << getErrorCode( result ) << ") getting device name."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); return info; } //const char *name = CFStringGetCStringPtr( cfname, CFStringGetSystemEncoding() ); length = CFStringGetLength(cfname); char *name = (char *)malloc(length * 3 + 1); #if defined( UNICODE ) || defined( _UNICODE ) CFStringGetCString(cfname, name, length * 3 + 1, kCFStringEncodingUTF8); #else CFStringGetCString(cfname, name, length * 3 + 1, CFStringGetSystemEncoding()); #endif info.name.append( (const char *)name, strlen(name) ); CFRelease( cfname ); free(name); // Get the output stream "configuration". AudioBufferList *bufferList = nil; property.mSelector = kAudioDevicePropertyStreamConfiguration; property.mScope = kAudioDevicePropertyScopeOutput; // property.mElement = kAudioObjectPropertyElementWildcard; dataSize = 0; result = AudioObjectGetPropertyDataSize( id, &property, 0, NULL, &dataSize ); if ( result != noErr || dataSize == 0 ) { errorStream_ << "RtApiCore::getDeviceInfo: system error (" << getErrorCode( result ) << ") getting output stream configuration info for device (" << device << ")."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); return info; } // Allocate the AudioBufferList. bufferList = (AudioBufferList *) malloc( dataSize ); if ( bufferList == NULL ) { errorText_ = "RtApiCore::getDeviceInfo: memory error allocating output AudioBufferList."; error( RtAudioError::WARNING ); return info; } result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, bufferList ); if ( result != noErr || dataSize == 0 ) { free( bufferList ); errorStream_ << "RtApiCore::getDeviceInfo: system error (" << getErrorCode( result ) << ") getting output stream configuration for device (" << device << ")."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); return info; } // Get output channel information. unsigned int i, nStreams = bufferList->mNumberBuffers; for ( i=0; imBuffers[i].mNumberChannels; free( bufferList ); // Get the input stream "configuration". property.mScope = kAudioDevicePropertyScopeInput; result = AudioObjectGetPropertyDataSize( id, &property, 0, NULL, &dataSize ); if ( result != noErr || dataSize == 0 ) { errorStream_ << "RtApiCore::getDeviceInfo: system error (" << getErrorCode( result ) << ") getting input stream configuration info for device (" << device << ")."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); return info; } // Allocate the AudioBufferList. bufferList = (AudioBufferList *) malloc( dataSize ); if ( bufferList == NULL ) { errorText_ = "RtApiCore::getDeviceInfo: memory error allocating input AudioBufferList."; error( RtAudioError::WARNING ); return info; } result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, bufferList ); if (result != noErr || dataSize == 0) { free( bufferList ); errorStream_ << "RtApiCore::getDeviceInfo: system error (" << getErrorCode( result ) << ") getting input stream configuration for device (" << device << ")."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); return info; } // Get input channel information. nStreams = bufferList->mNumberBuffers; for ( i=0; imBuffers[i].mNumberChannels; free( bufferList ); // If device opens for both playback and capture, we determine the channels. if ( info.outputChannels > 0 && info.inputChannels > 0 ) info.duplexChannels = (info.outputChannels > info.inputChannels) ? info.inputChannels : info.outputChannels; // Probe the device sample rates. bool isInput = false; if ( info.outputChannels == 0 ) isInput = true; // Determine the supported sample rates. property.mSelector = kAudioDevicePropertyAvailableNominalSampleRates; if ( isInput == false ) property.mScope = kAudioDevicePropertyScopeOutput; result = AudioObjectGetPropertyDataSize( id, &property, 0, NULL, &dataSize ); if ( result != kAudioHardwareNoError || dataSize == 0 ) { errorStream_ << "RtApiCore::getDeviceInfo: system error (" << getErrorCode( result ) << ") getting sample rate info."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); return info; } UInt32 nRanges = dataSize / sizeof( AudioValueRange ); AudioValueRange rangeList[ nRanges ]; result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, &rangeList ); if ( result != kAudioHardwareNoError ) { errorStream_ << "RtApiCore::getDeviceInfo: system error (" << getErrorCode( result ) << ") getting sample rates."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); return info; } // The sample rate reporting mechanism is a bit of a mystery. It // seems that it can either return individual rates or a range of // rates. I assume that if the min / max range values are the same, // then that represents a single supported rate and if the min / max // range values are different, the device supports an arbitrary // range of values (though there might be multiple ranges, so we'll // use the most conservative range). Float64 minimumRate = 1.0, maximumRate = 10000000000.0; bool haveValueRange = false; info.sampleRates.clear(); for ( UInt32 i=0; i info.preferredSampleRate ) ) info.preferredSampleRate = tmpSr; } else { haveValueRange = true; if ( rangeList[i].mMinimum > minimumRate ) minimumRate = rangeList[i].mMinimum; if ( rangeList[i].mMaximum < maximumRate ) maximumRate = rangeList[i].mMaximum; } } if ( haveValueRange ) { for ( unsigned int k=0; k= (unsigned int) minimumRate && SAMPLE_RATES[k] <= (unsigned int) maximumRate ) { info.sampleRates.push_back( SAMPLE_RATES[k] ); if ( !info.preferredSampleRate || ( SAMPLE_RATES[k] <= 48000 && SAMPLE_RATES[k] > info.preferredSampleRate ) ) info.preferredSampleRate = SAMPLE_RATES[k]; } } } // Sort and remove any redundant values std::sort( info.sampleRates.begin(), info.sampleRates.end() ); info.sampleRates.erase( unique( info.sampleRates.begin(), info.sampleRates.end() ), info.sampleRates.end() ); if ( info.sampleRates.size() == 0 ) { errorStream_ << "RtApiCore::probeDeviceInfo: No supported sample rates found for device (" << device << ")."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); return info; } // CoreAudio always uses 32-bit floating point data for PCM streams. // Thus, any other "physical" formats supported by the device are of // no interest to the client. info.nativeFormats = RTAUDIO_FLOAT32; if ( info.outputChannels > 0 ) if ( getDefaultOutputDevice() == device ) info.isDefaultOutput = true; if ( info.inputChannels > 0 ) if ( getDefaultInputDevice() == device ) info.isDefaultInput = true; info.probed = true; return info; } static OSStatus callbackHandler( AudioDeviceID inDevice, const AudioTimeStamp* /*inNow*/, const AudioBufferList* inInputData, const AudioTimeStamp* /*inInputTime*/, AudioBufferList* outOutputData, const AudioTimeStamp* /*inOutputTime*/, void* infoPointer ) { CallbackInfo *info = (CallbackInfo *) infoPointer; RtApiCore *object = (RtApiCore *) info->object; if ( object->callbackEvent( inDevice, inInputData, outOutputData ) == false ) return kAudioHardwareUnspecifiedError; else return kAudioHardwareNoError; } static OSStatus xrunListener( AudioObjectID /*inDevice*/, UInt32 nAddresses, const AudioObjectPropertyAddress properties[], void* handlePointer ) { CoreHandle *handle = (CoreHandle *) handlePointer; for ( UInt32 i=0; ixrun[1] = true; else handle->xrun[0] = true; } } return kAudioHardwareNoError; } static OSStatus rateListener( AudioObjectID inDevice, UInt32 /*nAddresses*/, const AudioObjectPropertyAddress /*properties*/[], void* ratePointer ) { Float64 *rate = (Float64 *) ratePointer; UInt32 dataSize = sizeof( Float64 ); AudioObjectPropertyAddress property = { kAudioDevicePropertyNominalSampleRate, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; AudioObjectGetPropertyData( inDevice, &property, 0, NULL, &dataSize, rate ); return kAudioHardwareNoError; } bool RtApiCore :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, RtAudio::StreamOptions *options ) { // Get device ID unsigned int nDevices = getDeviceCount(); if ( nDevices == 0 ) { // This should not happen because a check is made before this function is called. errorText_ = "RtApiCore::probeDeviceOpen: no devices found!"; return FAILURE; } if ( device >= nDevices ) { // This should not happen because a check is made before this function is called. errorText_ = "RtApiCore::probeDeviceOpen: device ID is invalid!"; return FAILURE; } AudioDeviceID deviceList[ nDevices ]; UInt32 dataSize = sizeof( AudioDeviceID ) * nDevices; AudioObjectPropertyAddress property = { kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; OSStatus result = AudioObjectGetPropertyData( kAudioObjectSystemObject, &property, 0, NULL, &dataSize, (void *) &deviceList ); if ( result != noErr ) { errorText_ = "RtApiCore::probeDeviceOpen: OS-X system error getting device IDs."; return FAILURE; } AudioDeviceID id = deviceList[ device ]; // Setup for stream mode. bool isInput = false; if ( mode == INPUT ) { isInput = true; property.mScope = kAudioDevicePropertyScopeInput; } else property.mScope = kAudioDevicePropertyScopeOutput; // Get the stream "configuration". AudioBufferList *bufferList = nil; dataSize = 0; property.mSelector = kAudioDevicePropertyStreamConfiguration; result = AudioObjectGetPropertyDataSize( id, &property, 0, NULL, &dataSize ); if ( result != noErr || dataSize == 0 ) { errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") getting stream configuration info for device (" << device << ")."; errorText_ = errorStream_.str(); return FAILURE; } // Allocate the AudioBufferList. bufferList = (AudioBufferList *) malloc( dataSize ); if ( bufferList == NULL ) { errorText_ = "RtApiCore::probeDeviceOpen: memory error allocating AudioBufferList."; return FAILURE; } result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, bufferList ); if (result != noErr || dataSize == 0) { free( bufferList ); errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") getting stream configuration for device (" << device << ")."; errorText_ = errorStream_.str(); return FAILURE; } // Search for one or more streams that contain the desired number of // channels. CoreAudio devices can have an arbitrary number of // streams and each stream can have an arbitrary number of channels. // For each stream, a single buffer of interleaved samples is // provided. RtAudio prefers the use of one stream of interleaved // data or multiple consecutive single-channel streams. However, we // now support multiple consecutive multi-channel streams of // interleaved data as well. UInt32 iStream, offsetCounter = firstChannel; UInt32 nStreams = bufferList->mNumberBuffers; bool monoMode = false; bool foundStream = false; // First check that the device supports the requested number of // channels. UInt32 deviceChannels = 0; for ( iStream=0; iStreammBuffers[iStream].mNumberChannels; if ( deviceChannels < ( channels + firstChannel ) ) { free( bufferList ); errorStream_ << "RtApiCore::probeDeviceOpen: the device (" << device << ") does not support the requested channel count."; errorText_ = errorStream_.str(); return FAILURE; } // Look for a single stream meeting our needs. UInt32 firstStream, streamCount = 1, streamChannels = 0, channelOffset = 0; for ( iStream=0; iStreammBuffers[iStream].mNumberChannels; if ( streamChannels >= channels + offsetCounter ) { firstStream = iStream; channelOffset = offsetCounter; foundStream = true; break; } if ( streamChannels > offsetCounter ) break; offsetCounter -= streamChannels; } // If we didn't find a single stream above, then we should be able // to meet the channel specification with multiple streams. if ( foundStream == false ) { monoMode = true; offsetCounter = firstChannel; for ( iStream=0; iStreammBuffers[iStream].mNumberChannels; if ( streamChannels > offsetCounter ) break; offsetCounter -= streamChannels; } firstStream = iStream; channelOffset = offsetCounter; Int32 channelCounter = channels + offsetCounter - streamChannels; if ( streamChannels > 1 ) monoMode = false; while ( channelCounter > 0 ) { streamChannels = bufferList->mBuffers[++iStream].mNumberChannels; if ( streamChannels > 1 ) monoMode = false; channelCounter -= streamChannels; streamCount++; } } free( bufferList ); // Determine the buffer size. AudioValueRange bufferRange; dataSize = sizeof( AudioValueRange ); property.mSelector = kAudioDevicePropertyBufferFrameSizeRange; result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, &bufferRange ); if ( result != noErr ) { errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") getting buffer size range for device (" << device << ")."; errorText_ = errorStream_.str(); return FAILURE; } if ( bufferRange.mMinimum > *bufferSize ) *bufferSize = (unsigned long) bufferRange.mMinimum; else if ( bufferRange.mMaximum < *bufferSize ) *bufferSize = (unsigned long) bufferRange.mMaximum; if ( options && options->flags & RTAUDIO_MINIMIZE_LATENCY ) *bufferSize = (unsigned long) bufferRange.mMinimum; // Set the buffer size. For multiple streams, I'm assuming we only // need to make this setting for the master channel. UInt32 theSize = (UInt32) *bufferSize; dataSize = sizeof( UInt32 ); property.mSelector = kAudioDevicePropertyBufferFrameSize; result = AudioObjectSetPropertyData( id, &property, 0, NULL, dataSize, &theSize ); if ( result != noErr ) { errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") setting the buffer size for device (" << device << ")."; errorText_ = errorStream_.str(); return FAILURE; } // If attempting to setup a duplex stream, the bufferSize parameter // MUST be the same in both directions! *bufferSize = theSize; if ( stream_.mode == OUTPUT && mode == INPUT && *bufferSize != stream_.bufferSize ) { errorStream_ << "RtApiCore::probeDeviceOpen: system error setting buffer size for duplex stream on device (" << device << ")."; errorText_ = errorStream_.str(); return FAILURE; } stream_.bufferSize = *bufferSize; stream_.nBuffers = 1; // Try to set "hog" mode ... it's not clear to me this is working. if ( options && options->flags & RTAUDIO_HOG_DEVICE ) { pid_t hog_pid; dataSize = sizeof( hog_pid ); property.mSelector = kAudioDevicePropertyHogMode; result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, &hog_pid ); if ( result != noErr ) { errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") getting 'hog' state!"; errorText_ = errorStream_.str(); return FAILURE; } if ( hog_pid != getpid() ) { hog_pid = getpid(); result = AudioObjectSetPropertyData( id, &property, 0, NULL, dataSize, &hog_pid ); if ( result != noErr ) { errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") setting 'hog' state!"; errorText_ = errorStream_.str(); return FAILURE; } } } // Check and if necessary, change the sample rate for the device. Float64 nominalRate; dataSize = sizeof( Float64 ); property.mSelector = kAudioDevicePropertyNominalSampleRate; result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, &nominalRate ); if ( result != noErr ) { errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") getting current sample rate."; errorText_ = errorStream_.str(); return FAILURE; } // Only change the sample rate if off by more than 1 Hz. if ( fabs( nominalRate - (double)sampleRate ) > 1.0 ) { // Set a property listener for the sample rate change Float64 reportedRate = 0.0; AudioObjectPropertyAddress tmp = { kAudioDevicePropertyNominalSampleRate, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; result = AudioObjectAddPropertyListener( id, &tmp, rateListener, (void *) &reportedRate ); if ( result != noErr ) { errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") setting sample rate property listener for device (" << device << ")."; errorText_ = errorStream_.str(); return FAILURE; } nominalRate = (Float64) sampleRate; result = AudioObjectSetPropertyData( id, &property, 0, NULL, dataSize, &nominalRate ); if ( result != noErr ) { AudioObjectRemovePropertyListener( id, &tmp, rateListener, (void *) &reportedRate ); errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") setting sample rate for device (" << device << ")."; errorText_ = errorStream_.str(); return FAILURE; } // Now wait until the reported nominal rate is what we just set. UInt32 microCounter = 0; while ( reportedRate != nominalRate ) { microCounter += 5000; if ( microCounter > 5000000 ) break; usleep( 5000 ); } // Remove the property listener. AudioObjectRemovePropertyListener( id, &tmp, rateListener, (void *) &reportedRate ); if ( microCounter > 5000000 ) { errorStream_ << "RtApiCore::probeDeviceOpen: timeout waiting for sample rate update for device (" << device << ")."; errorText_ = errorStream_.str(); return FAILURE; } } // Now set the stream format for all streams. Also, check the // physical format of the device and change that if necessary. AudioStreamBasicDescription description; dataSize = sizeof( AudioStreamBasicDescription ); property.mSelector = kAudioStreamPropertyVirtualFormat; result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, &description ); if ( result != noErr ) { errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") getting stream format for device (" << device << ")."; errorText_ = errorStream_.str(); return FAILURE; } // Set the sample rate and data format id. However, only make the // change if the sample rate is not within 1.0 of the desired // rate and the format is not linear pcm. bool updateFormat = false; if ( fabs( description.mSampleRate - (Float64)sampleRate ) > 1.0 ) { description.mSampleRate = (Float64) sampleRate; updateFormat = true; } if ( description.mFormatID != kAudioFormatLinearPCM ) { description.mFormatID = kAudioFormatLinearPCM; updateFormat = true; } if ( updateFormat ) { result = AudioObjectSetPropertyData( id, &property, 0, NULL, dataSize, &description ); if ( result != noErr ) { errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") setting sample rate or data format for device (" << device << ")."; errorText_ = errorStream_.str(); return FAILURE; } } // Now check the physical format. property.mSelector = kAudioStreamPropertyPhysicalFormat; result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, &description ); if ( result != noErr ) { errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") getting stream physical format for device (" << device << ")."; errorText_ = errorStream_.str(); return FAILURE; } //std::cout << "Current physical stream format:" << std::endl; //std::cout << " mBitsPerChan = " << description.mBitsPerChannel << std::endl; //std::cout << " aligned high = " << (description.mFormatFlags & kAudioFormatFlagIsAlignedHigh) << ", isPacked = " << (description.mFormatFlags & kAudioFormatFlagIsPacked) << std::endl; //std::cout << " bytesPerFrame = " << description.mBytesPerFrame << std::endl; //std::cout << " sample rate = " << description.mSampleRate << std::endl; if ( description.mFormatID != kAudioFormatLinearPCM || description.mBitsPerChannel < 16 ) { description.mFormatID = kAudioFormatLinearPCM; //description.mSampleRate = (Float64) sampleRate; AudioStreamBasicDescription testDescription = description; UInt32 formatFlags; // We'll try higher bit rates first and then work our way down. std::vector< std::pair > physicalFormats; formatFlags = (description.mFormatFlags | kLinearPCMFormatFlagIsFloat) & ~kLinearPCMFormatFlagIsSignedInteger; physicalFormats.push_back( std::pair( 32, formatFlags ) ); formatFlags = (description.mFormatFlags | kLinearPCMFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked) & ~kLinearPCMFormatFlagIsFloat; physicalFormats.push_back( std::pair( 32, formatFlags ) ); physicalFormats.push_back( std::pair( 24, formatFlags ) ); // 24-bit packed formatFlags &= ~( kAudioFormatFlagIsPacked | kAudioFormatFlagIsAlignedHigh ); physicalFormats.push_back( std::pair( 24.2, formatFlags ) ); // 24-bit in 4 bytes, aligned low formatFlags |= kAudioFormatFlagIsAlignedHigh; physicalFormats.push_back( std::pair( 24.4, formatFlags ) ); // 24-bit in 4 bytes, aligned high formatFlags = (description.mFormatFlags | kLinearPCMFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked) & ~kLinearPCMFormatFlagIsFloat; physicalFormats.push_back( std::pair( 16, formatFlags ) ); physicalFormats.push_back( std::pair( 8, formatFlags ) ); bool setPhysicalFormat = false; for( unsigned int i=0; iflags & RTAUDIO_NONINTERLEAVED ) stream_.userInterleaved = false; else stream_.userInterleaved = true; stream_.deviceInterleaved[mode] = true; if ( monoMode == true ) stream_.deviceInterleaved[mode] = false; // Set flags for buffer conversion. stream_.doConvertBuffer[mode] = false; if ( stream_.userFormat != stream_.deviceFormat[mode] ) stream_.doConvertBuffer[mode] = true; if ( stream_.nUserChannels[mode] < stream_.nDeviceChannels[mode] ) stream_.doConvertBuffer[mode] = true; if ( streamCount == 1 ) { if ( stream_.nUserChannels[mode] > 1 && stream_.userInterleaved != stream_.deviceInterleaved[mode] ) stream_.doConvertBuffer[mode] = true; } else if ( monoMode && stream_.userInterleaved ) stream_.doConvertBuffer[mode] = true; // Allocate our CoreHandle structure for the stream. CoreHandle *handle = 0; if ( stream_.apiHandle == 0 ) { try { handle = new CoreHandle; } catch ( std::bad_alloc& ) { errorText_ = "RtApiCore::probeDeviceOpen: error allocating CoreHandle memory."; goto error; } if ( pthread_cond_init( &handle->condition, NULL ) ) { errorText_ = "RtApiCore::probeDeviceOpen: error initializing pthread condition variable."; goto error; } stream_.apiHandle = (void *) handle; } else handle = (CoreHandle *) stream_.apiHandle; handle->iStream[mode] = firstStream; handle->nStreams[mode] = streamCount; handle->id[mode] = id; // Allocate necessary internal buffers. unsigned long bufferBytes; bufferBytes = stream_.nUserChannels[mode] * *bufferSize * formatBytes( stream_.userFormat ); // stream_.userBuffer[mode] = (char *) calloc( bufferBytes, 1 ); stream_.userBuffer[mode] = (char *) malloc( bufferBytes * sizeof(char) ); memset( stream_.userBuffer[mode], 0, bufferBytes * sizeof(char) ); if ( stream_.userBuffer[mode] == NULL ) { errorText_ = "RtApiCore::probeDeviceOpen: error allocating user buffer memory."; goto error; } // If possible, we will make use of the CoreAudio stream buffers as // "device buffers". However, we can't do this if using multiple // streams. if ( stream_.doConvertBuffer[mode] && handle->nStreams[mode] > 1 ) { bool makeBuffer = true; bufferBytes = stream_.nDeviceChannels[mode] * formatBytes( stream_.deviceFormat[mode] ); if ( mode == INPUT ) { if ( stream_.mode == OUTPUT && stream_.deviceBuffer ) { unsigned long bytesOut = stream_.nDeviceChannels[0] * formatBytes( stream_.deviceFormat[0] ); if ( bufferBytes <= bytesOut ) makeBuffer = false; } } if ( makeBuffer ) { bufferBytes *= *bufferSize; if ( stream_.deviceBuffer ) free( stream_.deviceBuffer ); stream_.deviceBuffer = (char *) calloc( bufferBytes, 1 ); if ( stream_.deviceBuffer == NULL ) { errorText_ = "RtApiCore::probeDeviceOpen: error allocating device buffer memory."; goto error; } } } stream_.sampleRate = sampleRate; stream_.device[mode] = device; stream_.state = STREAM_STOPPED; stream_.callbackInfo.object = (void *) this; // Setup the buffer conversion information structure. if ( stream_.doConvertBuffer[mode] ) { if ( streamCount > 1 ) setConvertInfo( mode, 0 ); else setConvertInfo( mode, channelOffset ); } if ( mode == INPUT && stream_.mode == OUTPUT && stream_.device[0] == device ) // Only one callback procedure per device. stream_.mode = DUPLEX; else { #if defined( MAC_OS_X_VERSION_10_5 ) && ( MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 ) result = AudioDeviceCreateIOProcID( id, callbackHandler, (void *) &stream_.callbackInfo, &handle->procId[mode] ); #else // deprecated in favor of AudioDeviceCreateIOProcID() result = AudioDeviceAddIOProc( id, callbackHandler, (void *) &stream_.callbackInfo ); #endif if ( result != noErr ) { errorStream_ << "RtApiCore::probeDeviceOpen: system error setting callback for device (" << device << ")."; errorText_ = errorStream_.str(); goto error; } if ( stream_.mode == OUTPUT && mode == INPUT ) stream_.mode = DUPLEX; else stream_.mode = mode; } // Setup the device property listener for over/underload. property.mSelector = kAudioDeviceProcessorOverload; property.mScope = kAudioObjectPropertyScopeGlobal; result = AudioObjectAddPropertyListener( id, &property, xrunListener, (void *) handle ); return SUCCESS; error: if ( handle ) { pthread_cond_destroy( &handle->condition ); delete handle; stream_.apiHandle = 0; } for ( int i=0; i<2; i++ ) { if ( stream_.userBuffer[i] ) { free( stream_.userBuffer[i] ); stream_.userBuffer[i] = 0; } } if ( stream_.deviceBuffer ) { free( stream_.deviceBuffer ); stream_.deviceBuffer = 0; } stream_.state = STREAM_CLOSED; return FAILURE; } void RtApiCore :: closeStream( void ) { if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiCore::closeStream(): no open stream to close!"; error( RtAudioError::WARNING ); return; } CoreHandle *handle = (CoreHandle *) stream_.apiHandle; if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { if (handle) { AudioObjectPropertyAddress property = { kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; property.mSelector = kAudioDeviceProcessorOverload; property.mScope = kAudioObjectPropertyScopeGlobal; if (AudioObjectRemovePropertyListener( handle->id[0], &property, xrunListener, (void *) handle ) != noErr) { errorText_ = "RtApiCore::closeStream(): error removing property listener!"; error( RtAudioError::WARNING ); } } if ( stream_.state == STREAM_RUNNING ) AudioDeviceStop( handle->id[0], callbackHandler ); #if defined( MAC_OS_X_VERSION_10_5 ) && ( MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 ) AudioDeviceDestroyIOProcID( handle->id[0], handle->procId[0] ); #else // deprecated in favor of AudioDeviceDestroyIOProcID() AudioDeviceRemoveIOProc( handle->id[0], callbackHandler ); #endif } if ( stream_.mode == INPUT || ( stream_.mode == DUPLEX && stream_.device[0] != stream_.device[1] ) ) { if (handle) { AudioObjectPropertyAddress property = { kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; property.mSelector = kAudioDeviceProcessorOverload; property.mScope = kAudioObjectPropertyScopeGlobal; if (AudioObjectRemovePropertyListener( handle->id[1], &property, xrunListener, (void *) handle ) != noErr) { errorText_ = "RtApiCore::closeStream(): error removing property listener!"; error( RtAudioError::WARNING ); } } if ( stream_.state == STREAM_RUNNING ) AudioDeviceStop( handle->id[1], callbackHandler ); #if defined( MAC_OS_X_VERSION_10_5 ) && ( MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 ) AudioDeviceDestroyIOProcID( handle->id[1], handle->procId[1] ); #else // deprecated in favor of AudioDeviceDestroyIOProcID() AudioDeviceRemoveIOProc( handle->id[1], callbackHandler ); #endif } for ( int i=0; i<2; i++ ) { if ( stream_.userBuffer[i] ) { free( stream_.userBuffer[i] ); stream_.userBuffer[i] = 0; } } if ( stream_.deviceBuffer ) { free( stream_.deviceBuffer ); stream_.deviceBuffer = 0; } // Destroy pthread condition variable. pthread_cond_destroy( &handle->condition ); delete handle; stream_.apiHandle = 0; stream_.mode = UNINITIALIZED; stream_.state = STREAM_CLOSED; } void RtApiCore :: startStream( void ) { verifyStream(); if ( stream_.state == STREAM_RUNNING ) { errorText_ = "RtApiCore::startStream(): the stream is already running!"; error( RtAudioError::WARNING ); return; } OSStatus result = noErr; CoreHandle *handle = (CoreHandle *) stream_.apiHandle; if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { result = AudioDeviceStart( handle->id[0], callbackHandler ); if ( result != noErr ) { errorStream_ << "RtApiCore::startStream: system error (" << getErrorCode( result ) << ") starting callback procedure on device (" << stream_.device[0] << ")."; errorText_ = errorStream_.str(); goto unlock; } } if ( stream_.mode == INPUT || ( stream_.mode == DUPLEX && stream_.device[0] != stream_.device[1] ) ) { result = AudioDeviceStart( handle->id[1], callbackHandler ); if ( result != noErr ) { errorStream_ << "RtApiCore::startStream: system error starting input callback procedure on device (" << stream_.device[1] << ")."; errorText_ = errorStream_.str(); goto unlock; } } handle->drainCounter = 0; handle->internalDrain = false; stream_.state = STREAM_RUNNING; unlock: if ( result == noErr ) return; error( RtAudioError::SYSTEM_ERROR ); } void RtApiCore :: stopStream( void ) { verifyStream(); if ( stream_.state == STREAM_STOPPED ) { errorText_ = "RtApiCore::stopStream(): the stream is already stopped!"; error( RtAudioError::WARNING ); return; } OSStatus result = noErr; CoreHandle *handle = (CoreHandle *) stream_.apiHandle; if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { if ( handle->drainCounter == 0 ) { handle->drainCounter = 2; pthread_cond_wait( &handle->condition, &stream_.mutex ); // block until signaled } result = AudioDeviceStop( handle->id[0], callbackHandler ); if ( result != noErr ) { errorStream_ << "RtApiCore::stopStream: system error (" << getErrorCode( result ) << ") stopping callback procedure on device (" << stream_.device[0] << ")."; errorText_ = errorStream_.str(); goto unlock; } } if ( stream_.mode == INPUT || ( stream_.mode == DUPLEX && stream_.device[0] != stream_.device[1] ) ) { result = AudioDeviceStop( handle->id[1], callbackHandler ); if ( result != noErr ) { errorStream_ << "RtApiCore::stopStream: system error (" << getErrorCode( result ) << ") stopping input callback procedure on device (" << stream_.device[1] << ")."; errorText_ = errorStream_.str(); goto unlock; } } stream_.state = STREAM_STOPPED; unlock: if ( result == noErr ) return; error( RtAudioError::SYSTEM_ERROR ); } void RtApiCore :: abortStream( void ) { verifyStream(); if ( stream_.state == STREAM_STOPPED ) { errorText_ = "RtApiCore::abortStream(): the stream is already stopped!"; error( RtAudioError::WARNING ); return; } CoreHandle *handle = (CoreHandle *) stream_.apiHandle; handle->drainCounter = 2; stopStream(); } // This function will be called by a spawned thread when the user // callback function signals that the stream should be stopped or // aborted. It is better to handle it this way because the // callbackEvent() function probably should return before the AudioDeviceStop() // function is called. static void *coreStopStream( void *ptr ) { CallbackInfo *info = (CallbackInfo *) ptr; RtApiCore *object = (RtApiCore *) info->object; object->stopStream(); pthread_exit( NULL ); } bool RtApiCore :: callbackEvent( AudioDeviceID deviceId, const AudioBufferList *inBufferList, const AudioBufferList *outBufferList ) { if ( stream_.state == STREAM_STOPPED || stream_.state == STREAM_STOPPING ) return SUCCESS; if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiCore::callbackEvent(): the stream is closed ... this shouldn't happen!"; error( RtAudioError::WARNING ); return FAILURE; } CallbackInfo *info = (CallbackInfo *) &stream_.callbackInfo; CoreHandle *handle = (CoreHandle *) stream_.apiHandle; // Check if we were draining the stream and signal is finished. if ( handle->drainCounter > 3 ) { ThreadHandle threadId; stream_.state = STREAM_STOPPING; if ( handle->internalDrain == true ) pthread_create( &threadId, NULL, coreStopStream, info ); else // external call to stopStream() pthread_cond_signal( &handle->condition ); return SUCCESS; } AudioDeviceID outputDevice = handle->id[0]; // Invoke user callback to get fresh output data UNLESS we are // draining stream or duplex mode AND the input/output devices are // different AND this function is called for the input device. if ( handle->drainCounter == 0 && ( stream_.mode != DUPLEX || deviceId == outputDevice ) ) { RtAudioCallback callback = (RtAudioCallback) info->callback; double streamTime = getStreamTime(); RtAudioStreamStatus status = 0; if ( stream_.mode != INPUT && handle->xrun[0] == true ) { status |= RTAUDIO_OUTPUT_UNDERFLOW; handle->xrun[0] = false; } if ( stream_.mode != OUTPUT && handle->xrun[1] == true ) { status |= RTAUDIO_INPUT_OVERFLOW; handle->xrun[1] = false; } int cbReturnValue = callback( stream_.userBuffer[0], stream_.userBuffer[1], stream_.bufferSize, streamTime, status, info->userData ); if ( cbReturnValue == 2 ) { stream_.state = STREAM_STOPPING; handle->drainCounter = 2; abortStream(); return SUCCESS; } else if ( cbReturnValue == 1 ) { handle->drainCounter = 1; handle->internalDrain = true; } } if ( stream_.mode == OUTPUT || ( stream_.mode == DUPLEX && deviceId == outputDevice ) ) { if ( handle->drainCounter > 1 ) { // write zeros to the output stream if ( handle->nStreams[0] == 1 ) { memset( outBufferList->mBuffers[handle->iStream[0]].mData, 0, outBufferList->mBuffers[handle->iStream[0]].mDataByteSize ); } else { // fill multiple streams with zeros for ( unsigned int i=0; inStreams[0]; i++ ) { memset( outBufferList->mBuffers[handle->iStream[0]+i].mData, 0, outBufferList->mBuffers[handle->iStream[0]+i].mDataByteSize ); } } } else if ( handle->nStreams[0] == 1 ) { if ( stream_.doConvertBuffer[0] ) { // convert directly to CoreAudio stream buffer convertBuffer( (char *) outBufferList->mBuffers[handle->iStream[0]].mData, stream_.userBuffer[0], stream_.convertInfo[0] ); } else { // copy from user buffer memcpy( outBufferList->mBuffers[handle->iStream[0]].mData, stream_.userBuffer[0], outBufferList->mBuffers[handle->iStream[0]].mDataByteSize ); } } else { // fill multiple streams Float32 *inBuffer = (Float32 *) stream_.userBuffer[0]; if ( stream_.doConvertBuffer[0] ) { convertBuffer( stream_.deviceBuffer, stream_.userBuffer[0], stream_.convertInfo[0] ); inBuffer = (Float32 *) stream_.deviceBuffer; } if ( stream_.deviceInterleaved[0] == false ) { // mono mode UInt32 bufferBytes = outBufferList->mBuffers[handle->iStream[0]].mDataByteSize; for ( unsigned int i=0; imBuffers[handle->iStream[0]+i].mData, (void *)&inBuffer[i*stream_.bufferSize], bufferBytes ); } } else { // fill multiple multi-channel streams with interleaved data UInt32 streamChannels, channelsLeft, inJump, outJump, inOffset; Float32 *out, *in; bool inInterleaved = ( stream_.userInterleaved ) ? true : false; UInt32 inChannels = stream_.nUserChannels[0]; if ( stream_.doConvertBuffer[0] ) { inInterleaved = true; // device buffer will always be interleaved for nStreams > 1 and not mono mode inChannels = stream_.nDeviceChannels[0]; } if ( inInterleaved ) inOffset = 1; else inOffset = stream_.bufferSize; channelsLeft = inChannels; for ( unsigned int i=0; inStreams[0]; i++ ) { in = inBuffer; out = (Float32 *) outBufferList->mBuffers[handle->iStream[0]+i].mData; streamChannels = outBufferList->mBuffers[handle->iStream[0]+i].mNumberChannels; outJump = 0; // Account for possible channel offset in first stream if ( i == 0 && stream_.channelOffset[0] > 0 ) { streamChannels -= stream_.channelOffset[0]; outJump = stream_.channelOffset[0]; out += outJump; } // Account for possible unfilled channels at end of the last stream if ( streamChannels > channelsLeft ) { outJump = streamChannels - channelsLeft; streamChannels = channelsLeft; } // Determine input buffer offsets and skips if ( inInterleaved ) { inJump = inChannels; in += inChannels - channelsLeft; } else { inJump = 1; in += (inChannels - channelsLeft) * inOffset; } for ( unsigned int i=0; idrainCounter ) { handle->drainCounter++; goto unlock; } AudioDeviceID inputDevice; inputDevice = handle->id[1]; if ( stream_.mode == INPUT || ( stream_.mode == DUPLEX && deviceId == inputDevice ) ) { if ( handle->nStreams[1] == 1 ) { if ( stream_.doConvertBuffer[1] ) { // convert directly from CoreAudio stream buffer convertBuffer( stream_.userBuffer[1], (char *) inBufferList->mBuffers[handle->iStream[1]].mData, stream_.convertInfo[1] ); } else { // copy to user buffer memcpy( stream_.userBuffer[1], inBufferList->mBuffers[handle->iStream[1]].mData, inBufferList->mBuffers[handle->iStream[1]].mDataByteSize ); } } else { // read from multiple streams Float32 *outBuffer = (Float32 *) stream_.userBuffer[1]; if ( stream_.doConvertBuffer[1] ) outBuffer = (Float32 *) stream_.deviceBuffer; if ( stream_.deviceInterleaved[1] == false ) { // mono mode UInt32 bufferBytes = inBufferList->mBuffers[handle->iStream[1]].mDataByteSize; for ( unsigned int i=0; imBuffers[handle->iStream[1]+i].mData, bufferBytes ); } } else { // read from multiple multi-channel streams UInt32 streamChannels, channelsLeft, inJump, outJump, outOffset; Float32 *out, *in; bool outInterleaved = ( stream_.userInterleaved ) ? true : false; UInt32 outChannels = stream_.nUserChannels[1]; if ( stream_.doConvertBuffer[1] ) { outInterleaved = true; // device buffer will always be interleaved for nStreams > 1 and not mono mode outChannels = stream_.nDeviceChannels[1]; } if ( outInterleaved ) outOffset = 1; else outOffset = stream_.bufferSize; channelsLeft = outChannels; for ( unsigned int i=0; inStreams[1]; i++ ) { out = outBuffer; in = (Float32 *) inBufferList->mBuffers[handle->iStream[1]+i].mData; streamChannels = inBufferList->mBuffers[handle->iStream[1]+i].mNumberChannels; inJump = 0; // Account for possible channel offset in first stream if ( i == 0 && stream_.channelOffset[1] > 0 ) { streamChannels -= stream_.channelOffset[1]; inJump = stream_.channelOffset[1]; in += inJump; } // Account for possible unread channels at end of the last stream if ( streamChannels > channelsLeft ) { inJump = streamChannels - channelsLeft; streamChannels = channelsLeft; } // Determine output buffer offsets and skips if ( outInterleaved ) { outJump = outChannels; out += outChannels - channelsLeft; } else { outJump = 1; out += (outChannels - channelsLeft) * outOffset; } for ( unsigned int i=0; i #include #include // A structure to hold various information related to the Jack API // implementation. struct JackHandle { jack_client_t *client; jack_port_t **ports[2]; std::string deviceName[2]; bool xrun[2]; pthread_cond_t condition; int drainCounter; // Tracks callback counts when draining bool internalDrain; // Indicates if stop is initiated from callback or not. JackHandle() :client(0), drainCounter(0), internalDrain(false) { ports[0] = 0; ports[1] = 0; xrun[0] = false; xrun[1] = false; } }; /* --- Monocasual hack ------------------------------------------------------ */ #ifdef __linux__ void *RtApi :: __HACK__getJackClient() { JackHandle *handle = (JackHandle *) stream_.apiHandle; return (void*) handle->client; } #endif /* -------------------------------------------------------------------------- */ static void jackSilentError( const char * ) {} RtApiJack :: RtApiJack() { // Nothing to do here. #if !defined(__RTAUDIO_DEBUG__) // Turn off Jack's internal error reporting. jack_set_error_function( &jackSilentError ); #endif } RtApiJack :: ~RtApiJack() { if ( stream_.state != STREAM_CLOSED ) closeStream(); } unsigned int RtApiJack :: getDeviceCount( void ) { // See if we can become a jack client. jack_options_t options = (jack_options_t) ( JackNoStartServer ); //JackNullOption; jack_status_t *status = NULL; jack_client_t *client = jack_client_open( "RtApiJackCount", options, status ); if ( client == 0 ) return 0; const char **ports; std::string port, previousPort; unsigned int nChannels = 0, nDevices = 0; ports = jack_get_ports( client, NULL, NULL, 0 ); if ( ports ) { // Parse the port names up to the first colon (:). size_t iColon = 0; do { port = (char *) ports[ nChannels ]; iColon = port.find(":"); if ( iColon != std::string::npos ) { port = port.substr( 0, iColon + 1 ); if ( port != previousPort ) { nDevices++; previousPort = port; } } } while ( ports[++nChannels] ); free( ports ); } jack_client_close( client ); return nDevices; } RtAudio::DeviceInfo RtApiJack :: getDeviceInfo( unsigned int device ) { RtAudio::DeviceInfo info; info.probed = false; jack_options_t options = (jack_options_t) ( JackNoStartServer ); //JackNullOption jack_status_t *status = NULL; jack_client_t *client = jack_client_open( "RtApiJackInfo", options, status ); if ( client == 0 ) { errorText_ = "RtApiJack::getDeviceInfo: Jack server not found or connection error!"; error( RtAudioError::WARNING ); return info; } const char **ports; std::string port, previousPort; unsigned int nPorts = 0, nDevices = 0; ports = jack_get_ports( client, NULL, NULL, 0 ); if ( ports ) { // Parse the port names up to the first colon (:). size_t iColon = 0; do { port = (char *) ports[ nPorts ]; iColon = port.find(":"); if ( iColon != std::string::npos ) { port = port.substr( 0, iColon ); if ( port != previousPort ) { if ( nDevices == device ) info.name = port; nDevices++; previousPort = port; } } } while ( ports[++nPorts] ); free( ports ); } if ( device >= nDevices ) { jack_client_close( client ); errorText_ = "RtApiJack::getDeviceInfo: device ID is invalid!"; error( RtAudioError::INVALID_USE ); return info; } // Get the current jack server sample rate. info.sampleRates.clear(); info.preferredSampleRate = jack_get_sample_rate( client ); info.sampleRates.push_back( info.preferredSampleRate ); // Count the available ports containing the client name as device // channels. Jack "input ports" equal RtAudio output channels. unsigned int nChannels = 0; ports = jack_get_ports( client, info.name.c_str(), NULL, JackPortIsInput ); if ( ports ) { while ( ports[ nChannels ] ) nChannels++; free( ports ); info.outputChannels = nChannels; } // Jack "output ports" equal RtAudio input channels. nChannels = 0; ports = jack_get_ports( client, info.name.c_str(), NULL, JackPortIsOutput ); if ( ports ) { while ( ports[ nChannels ] ) nChannels++; free( ports ); info.inputChannels = nChannels; } if ( info.outputChannels == 0 && info.inputChannels == 0 ) { jack_client_close(client); errorText_ = "RtApiJack::getDeviceInfo: error determining Jack input/output channels!"; error( RtAudioError::WARNING ); return info; } // If device opens for both playback and capture, we determine the channels. if ( info.outputChannels > 0 && info.inputChannels > 0 ) info.duplexChannels = (info.outputChannels > info.inputChannels) ? info.inputChannels : info.outputChannels; // Jack always uses 32-bit floats. info.nativeFormats = RTAUDIO_FLOAT32; // Jack doesn't provide default devices so we'll use the first available one. if ( device == 0 && info.outputChannels > 0 ) info.isDefaultOutput = true; if ( device == 0 && info.inputChannels > 0 ) info.isDefaultInput = true; jack_client_close(client); info.probed = true; return info; } static int jackCallbackHandler( jack_nframes_t nframes, void *infoPointer ) { CallbackInfo *info = (CallbackInfo *) infoPointer; RtApiJack *object = (RtApiJack *) info->object; if ( object->callbackEvent( (unsigned long) nframes ) == false ) return 1; return 0; } // This function will be called by a spawned thread when the Jack // server signals that it is shutting down. It is necessary to handle // it this way because the jackShutdown() function must return before // the jack_deactivate() function (in closeStream()) will return. static void *jackCloseStream( void *ptr ) { CallbackInfo *info = (CallbackInfo *) ptr; RtApiJack *object = (RtApiJack *) info->object; object->closeStream(); pthread_exit( NULL ); } static void jackShutdown( void *infoPointer ) { CallbackInfo *info = (CallbackInfo *) infoPointer; RtApiJack *object = (RtApiJack *) info->object; // Check current stream state. If stopped, then we'll assume this // was called as a result of a call to RtApiJack::stopStream (the // deactivation of a client handle causes this function to be called). // If not, we'll assume the Jack server is shutting down or some // other problem occurred and we should close the stream. if ( object->isStreamRunning() == false ) return; ThreadHandle threadId; pthread_create( &threadId, NULL, jackCloseStream, info ); std::cerr << "\nRtApiJack: the Jack server is shutting down this client ... stream stopped and closed!!\n" << std::endl; } static int jackXrun( void *infoPointer ) { JackHandle *handle = (JackHandle *) infoPointer; if ( handle->ports[0] ) handle->xrun[0] = true; if ( handle->ports[1] ) handle->xrun[1] = true; return 0; } bool RtApiJack :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, RtAudio::StreamOptions *options ) { JackHandle *handle = (JackHandle *) stream_.apiHandle; // Look for jack server and try to become a client (only do once per stream). jack_client_t *client = 0; if ( mode == OUTPUT || ( mode == INPUT && stream_.mode != OUTPUT ) ) { jack_options_t jackoptions = (jack_options_t) ( JackNoStartServer ); //JackNullOption; jack_status_t *status = NULL; if ( options && !options->streamName.empty() ) client = jack_client_open( options->streamName.c_str(), jackoptions, status ); else client = jack_client_open( "RtApiJack", jackoptions, status ); if ( client == 0 ) { errorText_ = "RtApiJack::probeDeviceOpen: Jack server not found or connection error!"; error( RtAudioError::WARNING ); return FAILURE; } } else { // The handle must have been created on an earlier pass. client = handle->client; } const char **ports; std::string port, previousPort, deviceName; unsigned int nPorts = 0, nDevices = 0; ports = jack_get_ports( client, NULL, NULL, 0 ); if ( ports ) { // Parse the port names up to the first colon (:). size_t iColon = 0; do { port = (char *) ports[ nPorts ]; iColon = port.find(":"); if ( iColon != std::string::npos ) { port = port.substr( 0, iColon ); if ( port != previousPort ) { if ( nDevices == device ) deviceName = port; nDevices++; previousPort = port; } } } while ( ports[++nPorts] ); free( ports ); } if ( device >= nDevices ) { errorText_ = "RtApiJack::probeDeviceOpen: device ID is invalid!"; return FAILURE; } // Count the available ports containing the client name as device // channels. Jack "input ports" equal RtAudio output channels. unsigned int nChannels = 0; unsigned long flag = JackPortIsInput; if ( mode == INPUT ) flag = JackPortIsOutput; ports = jack_get_ports( client, deviceName.c_str(), NULL, flag ); if ( ports ) { while ( ports[ nChannels ] ) nChannels++; free( ports ); } // Compare the jack ports for specified client to the requested number of channels. if ( nChannels < (channels + firstChannel) ) { errorStream_ << "RtApiJack::probeDeviceOpen: requested number of channels (" << channels << ") + offset (" << firstChannel << ") not found for specified device (" << device << ":" << deviceName << ")."; errorText_ = errorStream_.str(); return FAILURE; } // Check the jack server sample rate. unsigned int jackRate = jack_get_sample_rate( client ); if ( sampleRate != jackRate ) { jack_client_close( client ); errorStream_ << "RtApiJack::probeDeviceOpen: the requested sample rate (" << sampleRate << ") is different than the JACK server rate (" << jackRate << ")."; errorText_ = errorStream_.str(); return FAILURE; } stream_.sampleRate = jackRate; // Get the latency of the JACK port. ports = jack_get_ports( client, deviceName.c_str(), NULL, flag ); if ( ports[ firstChannel ] ) { // Added by Ge Wang jack_latency_callback_mode_t cbmode = (mode == INPUT ? JackCaptureLatency : JackPlaybackLatency); // the range (usually the min and max are equal) jack_latency_range_t latrange; latrange.min = latrange.max = 0; // get the latency range jack_port_get_latency_range( jack_port_by_name( client, ports[firstChannel] ), cbmode, &latrange ); // be optimistic, use the min! stream_.latency[mode] = latrange.min; //stream_.latency[mode] = jack_port_get_latency( jack_port_by_name( client, ports[ firstChannel ] ) ); } free( ports ); // The jack server always uses 32-bit floating-point data. stream_.deviceFormat[mode] = RTAUDIO_FLOAT32; stream_.userFormat = format; if ( options && options->flags & RTAUDIO_NONINTERLEAVED ) stream_.userInterleaved = false; else stream_.userInterleaved = true; // Jack always uses non-interleaved buffers. stream_.deviceInterleaved[mode] = false; // Jack always provides host byte-ordered data. stream_.doByteSwap[mode] = false; // Get the buffer size. The buffer size and number of buffers // (periods) is set when the jack server is started. stream_.bufferSize = (int) jack_get_buffer_size( client ); *bufferSize = stream_.bufferSize; stream_.nDeviceChannels[mode] = channels; stream_.nUserChannels[mode] = channels; // Set flags for buffer conversion. stream_.doConvertBuffer[mode] = false; if ( stream_.userFormat != stream_.deviceFormat[mode] ) stream_.doConvertBuffer[mode] = true; if ( stream_.userInterleaved != stream_.deviceInterleaved[mode] && stream_.nUserChannels[mode] > 1 ) stream_.doConvertBuffer[mode] = true; // Allocate our JackHandle structure for the stream. if ( handle == 0 ) { try { handle = new JackHandle; } catch ( std::bad_alloc& ) { errorText_ = "RtApiJack::probeDeviceOpen: error allocating JackHandle memory."; goto error; } if ( pthread_cond_init(&handle->condition, NULL) ) { errorText_ = "RtApiJack::probeDeviceOpen: error initializing pthread condition variable."; goto error; } stream_.apiHandle = (void *) handle; handle->client = client; } handle->deviceName[mode] = deviceName; // Allocate necessary internal buffers. unsigned long bufferBytes; bufferBytes = stream_.nUserChannels[mode] * *bufferSize * formatBytes( stream_.userFormat ); stream_.userBuffer[mode] = (char *) calloc( bufferBytes, 1 ); if ( stream_.userBuffer[mode] == NULL ) { errorText_ = "RtApiJack::probeDeviceOpen: error allocating user buffer memory."; goto error; } if ( stream_.doConvertBuffer[mode] ) { bool makeBuffer = true; if ( mode == OUTPUT ) bufferBytes = stream_.nDeviceChannels[0] * formatBytes( stream_.deviceFormat[0] ); else { // mode == INPUT bufferBytes = stream_.nDeviceChannels[1] * formatBytes( stream_.deviceFormat[1] ); if ( stream_.mode == OUTPUT && stream_.deviceBuffer ) { unsigned long bytesOut = stream_.nDeviceChannels[0] * formatBytes(stream_.deviceFormat[0]); if ( bufferBytes < bytesOut ) makeBuffer = false; } } if ( makeBuffer ) { bufferBytes *= *bufferSize; if ( stream_.deviceBuffer ) free( stream_.deviceBuffer ); stream_.deviceBuffer = (char *) calloc( bufferBytes, 1 ); if ( stream_.deviceBuffer == NULL ) { errorText_ = "RtApiJack::probeDeviceOpen: error allocating device buffer memory."; goto error; } } } // Allocate memory for the Jack ports (channels) identifiers. handle->ports[mode] = (jack_port_t **) malloc ( sizeof (jack_port_t *) * channels ); if ( handle->ports[mode] == NULL ) { errorText_ = "RtApiJack::probeDeviceOpen: error allocating port memory."; goto error; } stream_.device[mode] = device; stream_.channelOffset[mode] = firstChannel; stream_.state = STREAM_STOPPED; stream_.callbackInfo.object = (void *) this; if ( stream_.mode == OUTPUT && mode == INPUT ) // We had already set up the stream for output. stream_.mode = DUPLEX; else { stream_.mode = mode; jack_set_process_callback( handle->client, jackCallbackHandler, (void *) &stream_.callbackInfo ); jack_set_xrun_callback( handle->client, jackXrun, (void *) &handle ); jack_on_shutdown( handle->client, jackShutdown, (void *) &stream_.callbackInfo ); } // Register our ports. char label[64]; if ( mode == OUTPUT ) { for ( unsigned int i=0; iports[0][i] = jack_port_register( handle->client, (const char *)label, JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0 ); } } else { for ( unsigned int i=0; iports[1][i] = jack_port_register( handle->client, (const char *)label, JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0 ); } } // Setup the buffer conversion information structure. We don't use // buffers to do channel offsets, so we override that parameter // here. if ( stream_.doConvertBuffer[mode] ) setConvertInfo( mode, 0 ); return SUCCESS; error: if ( handle ) { pthread_cond_destroy( &handle->condition ); jack_client_close( handle->client ); if ( handle->ports[0] ) free( handle->ports[0] ); if ( handle->ports[1] ) free( handle->ports[1] ); delete handle; stream_.apiHandle = 0; } for ( int i=0; i<2; i++ ) { if ( stream_.userBuffer[i] ) { free( stream_.userBuffer[i] ); stream_.userBuffer[i] = 0; } } if ( stream_.deviceBuffer ) { free( stream_.deviceBuffer ); stream_.deviceBuffer = 0; } return FAILURE; } void RtApiJack :: closeStream( void ) { if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiJack::closeStream(): no open stream to close!"; error( RtAudioError::WARNING ); return; } JackHandle *handle = (JackHandle *) stream_.apiHandle; if ( handle ) { if ( stream_.state == STREAM_RUNNING ) jack_deactivate( handle->client ); jack_client_close( handle->client ); } if ( handle ) { if ( handle->ports[0] ) free( handle->ports[0] ); if ( handle->ports[1] ) free( handle->ports[1] ); pthread_cond_destroy( &handle->condition ); delete handle; stream_.apiHandle = 0; } for ( int i=0; i<2; i++ ) { if ( stream_.userBuffer[i] ) { free( stream_.userBuffer[i] ); stream_.userBuffer[i] = 0; } } if ( stream_.deviceBuffer ) { free( stream_.deviceBuffer ); stream_.deviceBuffer = 0; } stream_.mode = UNINITIALIZED; stream_.state = STREAM_CLOSED; } void RtApiJack :: startStream( void ) { verifyStream(); if ( stream_.state == STREAM_RUNNING ) { errorText_ = "RtApiJack::startStream(): the stream is already running!"; error( RtAudioError::WARNING ); return; } JackHandle *handle = (JackHandle *) stream_.apiHandle; int result = jack_activate( handle->client ); if ( result ) { errorText_ = "RtApiJack::startStream(): unable to activate JACK client!"; goto unlock; } const char **ports; // Get the list of available ports. if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { result = 1; ports = jack_get_ports( handle->client, handle->deviceName[0].c_str(), NULL, JackPortIsInput); if ( ports == NULL) { errorText_ = "RtApiJack::startStream(): error determining available JACK input ports!"; goto unlock; } // Now make the port connections. Since RtAudio wasn't designed to // allow the user to select particular channels of a device, we'll // just open the first "nChannels" ports with offset. for ( unsigned int i=0; iclient, jack_port_name( handle->ports[0][i] ), ports[ stream_.channelOffset[0] + i ] ); if ( result ) { free( ports ); errorText_ = "RtApiJack::startStream(): error connecting output ports!"; goto unlock; } } free(ports); } if ( stream_.mode == INPUT || stream_.mode == DUPLEX ) { result = 1; ports = jack_get_ports( handle->client, handle->deviceName[1].c_str(), NULL, JackPortIsOutput ); if ( ports == NULL) { errorText_ = "RtApiJack::startStream(): error determining available JACK output ports!"; goto unlock; } // Now make the port connections. See note above. for ( unsigned int i=0; iclient, ports[ stream_.channelOffset[1] + i ], jack_port_name( handle->ports[1][i] ) ); if ( result ) { free( ports ); errorText_ = "RtApiJack::startStream(): error connecting input ports!"; goto unlock; } } free(ports); } handle->drainCounter = 0; handle->internalDrain = false; stream_.state = STREAM_RUNNING; unlock: if ( result == 0 ) return; error( RtAudioError::SYSTEM_ERROR ); } void RtApiJack :: stopStream( void ) { verifyStream(); if ( stream_.state == STREAM_STOPPED ) { errorText_ = "RtApiJack::stopStream(): the stream is already stopped!"; error( RtAudioError::WARNING ); return; } JackHandle *handle = (JackHandle *) stream_.apiHandle; if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { if ( handle->drainCounter == 0 ) { handle->drainCounter = 2; pthread_cond_wait( &handle->condition, &stream_.mutex ); // block until signaled } } jack_deactivate( handle->client ); stream_.state = STREAM_STOPPED; } void RtApiJack :: abortStream( void ) { verifyStream(); if ( stream_.state == STREAM_STOPPED ) { errorText_ = "RtApiJack::abortStream(): the stream is already stopped!"; error( RtAudioError::WARNING ); return; } JackHandle *handle = (JackHandle *) stream_.apiHandle; handle->drainCounter = 2; stopStream(); } // This function will be called by a spawned thread when the user // callback function signals that the stream should be stopped or // aborted. It is necessary to handle it this way because the // callbackEvent() function must return before the jack_deactivate() // function will return. static void *jackStopStream( void *ptr ) { CallbackInfo *info = (CallbackInfo *) ptr; RtApiJack *object = (RtApiJack *) info->object; object->stopStream(); pthread_exit( NULL ); } bool RtApiJack :: callbackEvent( unsigned long nframes ) { if ( stream_.state == STREAM_STOPPED || stream_.state == STREAM_STOPPING ) return SUCCESS; if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiCore::callbackEvent(): the stream is closed ... this shouldn't happen!"; error( RtAudioError::WARNING ); return FAILURE; } if ( stream_.bufferSize != nframes ) { errorText_ = "RtApiCore::callbackEvent(): the JACK buffer size has changed ... cannot process!"; error( RtAudioError::WARNING ); return FAILURE; } CallbackInfo *info = (CallbackInfo *) &stream_.callbackInfo; JackHandle *handle = (JackHandle *) stream_.apiHandle; // Check if we were draining the stream and signal is finished. if ( handle->drainCounter > 3 ) { ThreadHandle threadId; stream_.state = STREAM_STOPPING; if ( handle->internalDrain == true ) pthread_create( &threadId, NULL, jackStopStream, info ); else pthread_cond_signal( &handle->condition ); return SUCCESS; } // Invoke user callback first, to get fresh output data. if ( handle->drainCounter == 0 ) { RtAudioCallback callback = (RtAudioCallback) info->callback; double streamTime = getStreamTime(); RtAudioStreamStatus status = 0; if ( stream_.mode != INPUT && handle->xrun[0] == true ) { status |= RTAUDIO_OUTPUT_UNDERFLOW; handle->xrun[0] = false; } if ( stream_.mode != OUTPUT && handle->xrun[1] == true ) { status |= RTAUDIO_INPUT_OVERFLOW; handle->xrun[1] = false; } int cbReturnValue = callback( stream_.userBuffer[0], stream_.userBuffer[1], stream_.bufferSize, streamTime, status, info->userData ); if ( cbReturnValue == 2 ) { stream_.state = STREAM_STOPPING; handle->drainCounter = 2; ThreadHandle id; pthread_create( &id, NULL, jackStopStream, info ); return SUCCESS; } else if ( cbReturnValue == 1 ) { handle->drainCounter = 1; handle->internalDrain = true; } } jack_default_audio_sample_t *jackbuffer; unsigned long bufferBytes = nframes * sizeof( jack_default_audio_sample_t ); if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { if ( handle->drainCounter > 1 ) { // write zeros to the output stream for ( unsigned int i=0; iports[0][i], (jack_nframes_t) nframes ); memset( jackbuffer, 0, bufferBytes ); } } else if ( stream_.doConvertBuffer[0] ) { convertBuffer( stream_.deviceBuffer, stream_.userBuffer[0], stream_.convertInfo[0] ); for ( unsigned int i=0; iports[0][i], (jack_nframes_t) nframes ); memcpy( jackbuffer, &stream_.deviceBuffer[i*bufferBytes], bufferBytes ); } } else { // no buffer conversion for ( unsigned int i=0; iports[0][i], (jack_nframes_t) nframes ); memcpy( jackbuffer, &stream_.userBuffer[0][i*bufferBytes], bufferBytes ); } } } // Don't bother draining input if ( handle->drainCounter ) { handle->drainCounter++; goto unlock; } if ( stream_.mode == INPUT || stream_.mode == DUPLEX ) { if ( stream_.doConvertBuffer[1] ) { for ( unsigned int i=0; iports[1][i], (jack_nframes_t) nframes ); memcpy( &stream_.deviceBuffer[i*bufferBytes], jackbuffer, bufferBytes ); } convertBuffer( stream_.userBuffer[1], stream_.deviceBuffer, stream_.convertInfo[1] ); } else { // no buffer conversion for ( unsigned int i=0; iports[1][i], (jack_nframes_t) nframes ); memcpy( &stream_.userBuffer[1][i*bufferBytes], jackbuffer, bufferBytes ); } } } unlock: RtApi::tickStreamTime(); return SUCCESS; } //******************** End of __UNIX_JACK__ *********************// #endif #if defined(__WINDOWS_ASIO__) // ASIO API on Windows // The ASIO API is designed around a callback scheme, so this // implementation is similar to that used for OS-X CoreAudio and Linux // Jack. The primary constraint with ASIO is that it only allows // access to a single driver at a time. Thus, it is not possible to // have more than one simultaneous RtAudio stream. // // This implementation also requires a number of external ASIO files // and a few global variables. The ASIO callback scheme does not // allow for the passing of user data, so we must create a global // pointer to our callbackInfo structure. // // On unix systems, we make use of a pthread condition variable. // Since there is no equivalent in Windows, I hacked something based // on information found in // http://www.cs.wustl.edu/~schmidt/win32-cv-1.html. #include "asiosys.h" #include "asio.h" #include "iasiothiscallresolver.h" #include "asiodrivers.h" #include static AsioDrivers drivers; static ASIOCallbacks asioCallbacks; static ASIODriverInfo driverInfo; static CallbackInfo *asioCallbackInfo; static bool asioXRun; struct AsioHandle { int drainCounter; // Tracks callback counts when draining bool internalDrain; // Indicates if stop is initiated from callback or not. ASIOBufferInfo *bufferInfos; HANDLE condition; AsioHandle() :drainCounter(0), internalDrain(false), bufferInfos(0) {} }; // Function declarations (definitions at end of section) static const char* getAsioErrorString( ASIOError result ); static void sampleRateChanged( ASIOSampleRate sRate ); static long asioMessages( long selector, long value, void* message, double* opt ); RtApiAsio :: RtApiAsio() { // ASIO cannot run on a multi-threaded appartment. You can call // CoInitialize beforehand, but it must be for appartment threading // (in which case, CoInitilialize will return S_FALSE here). coInitialized_ = false; HRESULT hr = CoInitialize( NULL ); if ( FAILED(hr) ) { errorText_ = "RtApiAsio::ASIO requires a single-threaded appartment. Call CoInitializeEx(0,COINIT_APARTMENTTHREADED)"; error( RtAudioError::WARNING ); } coInitialized_ = true; drivers.removeCurrentDriver(); driverInfo.asioVersion = 2; // See note in DirectSound implementation about GetDesktopWindow(). driverInfo.sysRef = GetForegroundWindow(); } RtApiAsio :: ~RtApiAsio() { if ( stream_.state != STREAM_CLOSED ) closeStream(); if ( coInitialized_ ) CoUninitialize(); } unsigned int RtApiAsio :: getDeviceCount( void ) { return (unsigned int) drivers.asioGetNumDev(); } RtAudio::DeviceInfo RtApiAsio :: getDeviceInfo( unsigned int device ) { RtAudio::DeviceInfo info; info.probed = false; // Get device ID unsigned int nDevices = getDeviceCount(); if ( nDevices == 0 ) { errorText_ = "RtApiAsio::getDeviceInfo: no devices found!"; error( RtAudioError::INVALID_USE ); return info; } if ( device >= nDevices ) { errorText_ = "RtApiAsio::getDeviceInfo: device ID is invalid!"; error( RtAudioError::INVALID_USE ); return info; } // If a stream is already open, we cannot probe other devices. Thus, use the saved results. if ( stream_.state != STREAM_CLOSED ) { if ( device >= devices_.size() ) { errorText_ = "RtApiAsio::getDeviceInfo: device ID was not present before stream was opened."; error( RtAudioError::WARNING ); return info; } return devices_[ device ]; } char driverName[32]; ASIOError result = drivers.asioGetDriverName( (int) device, driverName, 32 ); if ( result != ASE_OK ) { errorStream_ << "RtApiAsio::getDeviceInfo: unable to get driver name (" << getAsioErrorString( result ) << ")."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); return info; } info.name = driverName; if ( !drivers.loadDriver( driverName ) ) { errorStream_ << "RtApiAsio::getDeviceInfo: unable to load driver (" << driverName << ")."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); return info; } result = ASIOInit( &driverInfo ); if ( result != ASE_OK ) { errorStream_ << "RtApiAsio::getDeviceInfo: error (" << getAsioErrorString( result ) << ") initializing driver (" << driverName << ")."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); return info; } // Determine the device channel information. long inputChannels, outputChannels; result = ASIOGetChannels( &inputChannels, &outputChannels ); if ( result != ASE_OK ) { drivers.removeCurrentDriver(); errorStream_ << "RtApiAsio::getDeviceInfo: error (" << getAsioErrorString( result ) << ") getting channel count (" << driverName << ")."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); return info; } info.outputChannels = outputChannels; info.inputChannels = inputChannels; if ( info.outputChannels > 0 && info.inputChannels > 0 ) info.duplexChannels = (info.outputChannels > info.inputChannels) ? info.inputChannels : info.outputChannels; // Determine the supported sample rates. info.sampleRates.clear(); for ( unsigned int i=0; i info.preferredSampleRate ) ) info.preferredSampleRate = SAMPLE_RATES[i]; } } // Determine supported data types ... just check first channel and assume rest are the same. ASIOChannelInfo channelInfo; channelInfo.channel = 0; channelInfo.isInput = true; if ( info.inputChannels <= 0 ) channelInfo.isInput = false; result = ASIOGetChannelInfo( &channelInfo ); if ( result != ASE_OK ) { drivers.removeCurrentDriver(); errorStream_ << "RtApiAsio::getDeviceInfo: error (" << getAsioErrorString( result ) << ") getting driver channel info (" << driverName << ")."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); return info; } info.nativeFormats = 0; if ( channelInfo.type == ASIOSTInt16MSB || channelInfo.type == ASIOSTInt16LSB ) info.nativeFormats |= RTAUDIO_SINT16; else if ( channelInfo.type == ASIOSTInt32MSB || channelInfo.type == ASIOSTInt32LSB ) info.nativeFormats |= RTAUDIO_SINT32; else if ( channelInfo.type == ASIOSTFloat32MSB || channelInfo.type == ASIOSTFloat32LSB ) info.nativeFormats |= RTAUDIO_FLOAT32; else if ( channelInfo.type == ASIOSTFloat64MSB || channelInfo.type == ASIOSTFloat64LSB ) info.nativeFormats |= RTAUDIO_FLOAT64; else if ( channelInfo.type == ASIOSTInt24MSB || channelInfo.type == ASIOSTInt24LSB ) info.nativeFormats |= RTAUDIO_SINT24; if ( info.outputChannels > 0 ) if ( getDefaultOutputDevice() == device ) info.isDefaultOutput = true; if ( info.inputChannels > 0 ) if ( getDefaultInputDevice() == device ) info.isDefaultInput = true; info.probed = true; drivers.removeCurrentDriver(); return info; } static void bufferSwitch( long index, ASIOBool /*processNow*/ ) { RtApiAsio *object = (RtApiAsio *) asioCallbackInfo->object; object->callbackEvent( index ); } void RtApiAsio :: saveDeviceInfo( void ) { devices_.clear(); unsigned int nDevices = getDeviceCount(); devices_.resize( nDevices ); for ( unsigned int i=0; isaveDeviceInfo(); if ( !drivers.loadDriver( driverName ) ) { errorStream_ << "RtApiAsio::probeDeviceOpen: unable to load driver (" << driverName << ")."; errorText_ = errorStream_.str(); return FAILURE; } result = ASIOInit( &driverInfo ); if ( result != ASE_OK ) { errorStream_ << "RtApiAsio::probeDeviceOpen: error (" << getAsioErrorString( result ) << ") initializing driver (" << driverName << ")."; errorText_ = errorStream_.str(); return FAILURE; } } // keep them before any "goto error", they are used for error cleanup + goto device boundary checks bool buffersAllocated = false; AsioHandle *handle = (AsioHandle *) stream_.apiHandle; unsigned int nChannels; // Check the device channel count. long inputChannels, outputChannels; result = ASIOGetChannels( &inputChannels, &outputChannels ); if ( result != ASE_OK ) { errorStream_ << "RtApiAsio::probeDeviceOpen: error (" << getAsioErrorString( result ) << ") getting channel count (" << driverName << ")."; errorText_ = errorStream_.str(); goto error; } if ( ( mode == OUTPUT && (channels+firstChannel) > (unsigned int) outputChannels) || ( mode == INPUT && (channels+firstChannel) > (unsigned int) inputChannels) ) { errorStream_ << "RtApiAsio::probeDeviceOpen: driver (" << driverName << ") does not support requested channel count (" << channels << ") + offset (" << firstChannel << ")."; errorText_ = errorStream_.str(); goto error; } stream_.nDeviceChannels[mode] = channels; stream_.nUserChannels[mode] = channels; stream_.channelOffset[mode] = firstChannel; // Verify the sample rate is supported. result = ASIOCanSampleRate( (ASIOSampleRate) sampleRate ); if ( result != ASE_OK ) { errorStream_ << "RtApiAsio::probeDeviceOpen: driver (" << driverName << ") does not support requested sample rate (" << sampleRate << ")."; errorText_ = errorStream_.str(); goto error; } // Get the current sample rate ASIOSampleRate currentRate; result = ASIOGetSampleRate( ¤tRate ); if ( result != ASE_OK ) { errorStream_ << "RtApiAsio::probeDeviceOpen: driver (" << driverName << ") error getting sample rate."; errorText_ = errorStream_.str(); goto error; } // Set the sample rate only if necessary if ( currentRate != sampleRate ) { result = ASIOSetSampleRate( (ASIOSampleRate) sampleRate ); if ( result != ASE_OK ) { errorStream_ << "RtApiAsio::probeDeviceOpen: driver (" << driverName << ") error setting sample rate (" << sampleRate << ")."; errorText_ = errorStream_.str(); goto error; } } // Determine the driver data type. ASIOChannelInfo channelInfo; channelInfo.channel = 0; if ( mode == OUTPUT ) channelInfo.isInput = false; else channelInfo.isInput = true; result = ASIOGetChannelInfo( &channelInfo ); if ( result != ASE_OK ) { errorStream_ << "RtApiAsio::probeDeviceOpen: driver (" << driverName << ") error (" << getAsioErrorString( result ) << ") getting data format."; errorText_ = errorStream_.str(); goto error; } // Assuming WINDOWS host is always little-endian. stream_.doByteSwap[mode] = false; stream_.userFormat = format; stream_.deviceFormat[mode] = 0; if ( channelInfo.type == ASIOSTInt16MSB || channelInfo.type == ASIOSTInt16LSB ) { stream_.deviceFormat[mode] = RTAUDIO_SINT16; if ( channelInfo.type == ASIOSTInt16MSB ) stream_.doByteSwap[mode] = true; } else if ( channelInfo.type == ASIOSTInt32MSB || channelInfo.type == ASIOSTInt32LSB ) { stream_.deviceFormat[mode] = RTAUDIO_SINT32; if ( channelInfo.type == ASIOSTInt32MSB ) stream_.doByteSwap[mode] = true; } else if ( channelInfo.type == ASIOSTFloat32MSB || channelInfo.type == ASIOSTFloat32LSB ) { stream_.deviceFormat[mode] = RTAUDIO_FLOAT32; if ( channelInfo.type == ASIOSTFloat32MSB ) stream_.doByteSwap[mode] = true; } else if ( channelInfo.type == ASIOSTFloat64MSB || channelInfo.type == ASIOSTFloat64LSB ) { stream_.deviceFormat[mode] = RTAUDIO_FLOAT64; if ( channelInfo.type == ASIOSTFloat64MSB ) stream_.doByteSwap[mode] = true; } else if ( channelInfo.type == ASIOSTInt24MSB || channelInfo.type == ASIOSTInt24LSB ) { stream_.deviceFormat[mode] = RTAUDIO_SINT24; if ( channelInfo.type == ASIOSTInt24MSB ) stream_.doByteSwap[mode] = true; } if ( stream_.deviceFormat[mode] == 0 ) { errorStream_ << "RtApiAsio::probeDeviceOpen: driver (" << driverName << ") data format not supported by RtAudio."; errorText_ = errorStream_.str(); goto error; } // Set the buffer size. For a duplex stream, this will end up // setting the buffer size based on the input constraints, which // should be ok. long minSize, maxSize, preferSize, granularity; result = ASIOGetBufferSize( &minSize, &maxSize, &preferSize, &granularity ); if ( result != ASE_OK ) { errorStream_ << "RtApiAsio::probeDeviceOpen: driver (" << driverName << ") error (" << getAsioErrorString( result ) << ") getting buffer size."; errorText_ = errorStream_.str(); goto error; } if ( isDuplexInput ) { // When this is the duplex input (output was opened before), then we have to use the same // buffersize as the output, because it might use the preferred buffer size, which most // likely wasn't passed as input to this. The buffer sizes have to be identically anyway, // So instead of throwing an error, make them equal. The caller uses the reference // to the "bufferSize" param as usual to set up processing buffers. *bufferSize = stream_.bufferSize; } else { if ( *bufferSize == 0 ) *bufferSize = preferSize; else if ( *bufferSize < (unsigned int) minSize ) *bufferSize = (unsigned int) minSize; else if ( *bufferSize > (unsigned int) maxSize ) *bufferSize = (unsigned int) maxSize; else if ( granularity == -1 ) { // Make sure bufferSize is a power of two. int log2_of_min_size = 0; int log2_of_max_size = 0; for ( unsigned int i = 0; i < sizeof(long) * 8; i++ ) { if ( minSize & ((long)1 << i) ) log2_of_min_size = i; if ( maxSize & ((long)1 << i) ) log2_of_max_size = i; } long min_delta = std::abs( (long)*bufferSize - ((long)1 << log2_of_min_size) ); int min_delta_num = log2_of_min_size; for (int i = log2_of_min_size + 1; i <= log2_of_max_size; i++) { long current_delta = std::abs( (long)*bufferSize - ((long)1 << i) ); if (current_delta < min_delta) { min_delta = current_delta; min_delta_num = i; } } *bufferSize = ( (unsigned int)1 << min_delta_num ); if ( *bufferSize < (unsigned int) minSize ) *bufferSize = (unsigned int) minSize; else if ( *bufferSize > (unsigned int) maxSize ) *bufferSize = (unsigned int) maxSize; } else if ( granularity != 0 ) { // Set to an even multiple of granularity, rounding up. *bufferSize = (*bufferSize + granularity-1) / granularity * granularity; } } /* // we don't use it anymore, see above! // Just left it here for the case... if ( isDuplexInput && stream_.bufferSize != *bufferSize ) { errorText_ = "RtApiAsio::probeDeviceOpen: input/output buffersize discrepancy!"; goto error; } */ stream_.bufferSize = *bufferSize; stream_.nBuffers = 2; if ( options && options->flags & RTAUDIO_NONINTERLEAVED ) stream_.userInterleaved = false; else stream_.userInterleaved = true; // ASIO always uses non-interleaved buffers. stream_.deviceInterleaved[mode] = false; // Allocate, if necessary, our AsioHandle structure for the stream. if ( handle == 0 ) { try { handle = new AsioHandle; } catch ( std::bad_alloc& ) { errorText_ = "RtApiAsio::probeDeviceOpen: error allocating AsioHandle memory."; goto error; } handle->bufferInfos = 0; // Create a manual-reset event. handle->condition = CreateEvent( NULL, // no security TRUE, // manual-reset FALSE, // non-signaled initially NULL ); // unnamed stream_.apiHandle = (void *) handle; } // Create the ASIO internal buffers. Since RtAudio sets up input // and output separately, we'll have to dispose of previously // created output buffers for a duplex stream. if ( mode == INPUT && stream_.mode == OUTPUT ) { ASIODisposeBuffers(); if ( handle->bufferInfos ) free( handle->bufferInfos ); } // Allocate, initialize, and save the bufferInfos in our stream callbackInfo structure. unsigned int i; nChannels = stream_.nDeviceChannels[0] + stream_.nDeviceChannels[1]; handle->bufferInfos = (ASIOBufferInfo *) malloc( nChannels * sizeof(ASIOBufferInfo) ); if ( handle->bufferInfos == NULL ) { errorStream_ << "RtApiAsio::probeDeviceOpen: error allocating bufferInfo memory for driver (" << driverName << ")."; errorText_ = errorStream_.str(); goto error; } ASIOBufferInfo *infos; infos = handle->bufferInfos; for ( i=0; iisInput = ASIOFalse; infos->channelNum = i + stream_.channelOffset[0]; infos->buffers[0] = infos->buffers[1] = 0; } for ( i=0; iisInput = ASIOTrue; infos->channelNum = i + stream_.channelOffset[1]; infos->buffers[0] = infos->buffers[1] = 0; } // prepare for callbacks stream_.sampleRate = sampleRate; stream_.device[mode] = device; stream_.mode = isDuplexInput ? DUPLEX : mode; // store this class instance before registering callbacks, that are going to use it asioCallbackInfo = &stream_.callbackInfo; stream_.callbackInfo.object = (void *) this; // Set up the ASIO callback structure and create the ASIO data buffers. asioCallbacks.bufferSwitch = &bufferSwitch; asioCallbacks.sampleRateDidChange = &sampleRateChanged; asioCallbacks.asioMessage = &asioMessages; asioCallbacks.bufferSwitchTimeInfo = NULL; result = ASIOCreateBuffers( handle->bufferInfos, nChannels, stream_.bufferSize, &asioCallbacks ); if ( result != ASE_OK ) { // Standard method failed. This can happen with strict/misbehaving drivers that return valid buffer size ranges // but only accept the preferred buffer size as parameter for ASIOCreateBuffers. eg. Creatives ASIO driver // in that case, let's be naïve and try that instead *bufferSize = preferSize; stream_.bufferSize = *bufferSize; result = ASIOCreateBuffers( handle->bufferInfos, nChannels, stream_.bufferSize, &asioCallbacks ); } if ( result != ASE_OK ) { errorStream_ << "RtApiAsio::probeDeviceOpen: driver (" << driverName << ") error (" << getAsioErrorString( result ) << ") creating buffers."; errorText_ = errorStream_.str(); goto error; } buffersAllocated = true; stream_.state = STREAM_STOPPED; // Set flags for buffer conversion. stream_.doConvertBuffer[mode] = false; if ( stream_.userFormat != stream_.deviceFormat[mode] ) stream_.doConvertBuffer[mode] = true; if ( stream_.userInterleaved != stream_.deviceInterleaved[mode] && stream_.nUserChannels[mode] > 1 ) stream_.doConvertBuffer[mode] = true; // Allocate necessary internal buffers unsigned long bufferBytes; bufferBytes = stream_.nUserChannels[mode] * *bufferSize * formatBytes( stream_.userFormat ); stream_.userBuffer[mode] = (char *) calloc( bufferBytes, 1 ); if ( stream_.userBuffer[mode] == NULL ) { errorText_ = "RtApiAsio::probeDeviceOpen: error allocating user buffer memory."; goto error; } if ( stream_.doConvertBuffer[mode] ) { bool makeBuffer = true; bufferBytes = stream_.nDeviceChannels[mode] * formatBytes( stream_.deviceFormat[mode] ); if ( isDuplexInput && stream_.deviceBuffer ) { unsigned long bytesOut = stream_.nDeviceChannels[0] * formatBytes( stream_.deviceFormat[0] ); if ( bufferBytes <= bytesOut ) makeBuffer = false; } if ( makeBuffer ) { bufferBytes *= *bufferSize; if ( stream_.deviceBuffer ) free( stream_.deviceBuffer ); stream_.deviceBuffer = (char *) calloc( bufferBytes, 1 ); if ( stream_.deviceBuffer == NULL ) { errorText_ = "RtApiAsio::probeDeviceOpen: error allocating device buffer memory."; goto error; } } } // Determine device latencies long inputLatency, outputLatency; result = ASIOGetLatencies( &inputLatency, &outputLatency ); if ( result != ASE_OK ) { errorStream_ << "RtApiAsio::probeDeviceOpen: driver (" << driverName << ") error (" << getAsioErrorString( result ) << ") getting latency."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING); // warn but don't fail } else { stream_.latency[0] = outputLatency; stream_.latency[1] = inputLatency; } // Setup the buffer conversion information structure. We don't use // buffers to do channel offsets, so we override that parameter // here. if ( stream_.doConvertBuffer[mode] ) setConvertInfo( mode, 0 ); return SUCCESS; error: if ( !isDuplexInput ) { // the cleanup for error in the duplex input, is done by RtApi::openStream // So we clean up for single channel only if ( buffersAllocated ) ASIODisposeBuffers(); drivers.removeCurrentDriver(); if ( handle ) { CloseHandle( handle->condition ); if ( handle->bufferInfos ) free( handle->bufferInfos ); delete handle; stream_.apiHandle = 0; } if ( stream_.userBuffer[mode] ) { free( stream_.userBuffer[mode] ); stream_.userBuffer[mode] = 0; } if ( stream_.deviceBuffer ) { free( stream_.deviceBuffer ); stream_.deviceBuffer = 0; } } return FAILURE; }//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void RtApiAsio :: closeStream() { if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiAsio::closeStream(): no open stream to close!"; error( RtAudioError::WARNING ); return; } if ( stream_.state == STREAM_RUNNING ) { stream_.state = STREAM_STOPPED; ASIOStop(); } ASIODisposeBuffers(); drivers.removeCurrentDriver(); AsioHandle *handle = (AsioHandle *) stream_.apiHandle; if ( handle ) { CloseHandle( handle->condition ); if ( handle->bufferInfos ) free( handle->bufferInfos ); delete handle; stream_.apiHandle = 0; } for ( int i=0; i<2; i++ ) { if ( stream_.userBuffer[i] ) { free( stream_.userBuffer[i] ); stream_.userBuffer[i] = 0; } } if ( stream_.deviceBuffer ) { free( stream_.deviceBuffer ); stream_.deviceBuffer = 0; } stream_.mode = UNINITIALIZED; stream_.state = STREAM_CLOSED; } bool stopThreadCalled = false; void RtApiAsio :: startStream() { verifyStream(); if ( stream_.state == STREAM_RUNNING ) { errorText_ = "RtApiAsio::startStream(): the stream is already running!"; error( RtAudioError::WARNING ); return; } AsioHandle *handle = (AsioHandle *) stream_.apiHandle; ASIOError result = ASIOStart(); if ( result != ASE_OK ) { errorStream_ << "RtApiAsio::startStream: error (" << getAsioErrorString( result ) << ") starting device."; errorText_ = errorStream_.str(); goto unlock; } handle->drainCounter = 0; handle->internalDrain = false; ResetEvent( handle->condition ); stream_.state = STREAM_RUNNING; asioXRun = false; unlock: stopThreadCalled = false; if ( result == ASE_OK ) return; error( RtAudioError::SYSTEM_ERROR ); } void RtApiAsio :: stopStream() { verifyStream(); if ( stream_.state == STREAM_STOPPED ) { errorText_ = "RtApiAsio::stopStream(): the stream is already stopped!"; error( RtAudioError::WARNING ); return; } AsioHandle *handle = (AsioHandle *) stream_.apiHandle; if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { if ( handle->drainCounter == 0 ) { handle->drainCounter = 2; WaitForSingleObject( handle->condition, INFINITE ); // block until signaled } } stream_.state = STREAM_STOPPED; ASIOError result = ASIOStop(); if ( result != ASE_OK ) { errorStream_ << "RtApiAsio::stopStream: error (" << getAsioErrorString( result ) << ") stopping device."; errorText_ = errorStream_.str(); } if ( result == ASE_OK ) return; error( RtAudioError::SYSTEM_ERROR ); } void RtApiAsio :: abortStream() { verifyStream(); if ( stream_.state == STREAM_STOPPED ) { errorText_ = "RtApiAsio::abortStream(): the stream is already stopped!"; error( RtAudioError::WARNING ); return; } // The following lines were commented-out because some behavior was // noted where the device buffers need to be zeroed to avoid // continuing sound, even when the device buffers are completely // disposed. So now, calling abort is the same as calling stop. // AsioHandle *handle = (AsioHandle *) stream_.apiHandle; // handle->drainCounter = 2; stopStream(); } // This function will be called by a spawned thread when the user // callback function signals that the stream should be stopped or // aborted. It is necessary to handle it this way because the // callbackEvent() function must return before the ASIOStop() // function will return. static unsigned __stdcall asioStopStream( void *ptr ) { CallbackInfo *info = (CallbackInfo *) ptr; RtApiAsio *object = (RtApiAsio *) info->object; object->stopStream(); _endthreadex( 0 ); return 0; } bool RtApiAsio :: callbackEvent( long bufferIndex ) { if ( stream_.state == STREAM_STOPPED || stream_.state == STREAM_STOPPING ) return SUCCESS; if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiAsio::callbackEvent(): the stream is closed ... this shouldn't happen!"; error( RtAudioError::WARNING ); return FAILURE; } CallbackInfo *info = (CallbackInfo *) &stream_.callbackInfo; AsioHandle *handle = (AsioHandle *) stream_.apiHandle; // Check if we were draining the stream and signal if finished. if ( handle->drainCounter > 3 ) { stream_.state = STREAM_STOPPING; if ( handle->internalDrain == false ) SetEvent( handle->condition ); else { // spawn a thread to stop the stream unsigned threadId; stream_.callbackInfo.thread = _beginthreadex( NULL, 0, &asioStopStream, &stream_.callbackInfo, 0, &threadId ); } return SUCCESS; } // Invoke user callback to get fresh output data UNLESS we are // draining stream. if ( handle->drainCounter == 0 ) { RtAudioCallback callback = (RtAudioCallback) info->callback; double streamTime = getStreamTime(); RtAudioStreamStatus status = 0; if ( stream_.mode != INPUT && asioXRun == true ) { status |= RTAUDIO_OUTPUT_UNDERFLOW; asioXRun = false; } if ( stream_.mode != OUTPUT && asioXRun == true ) { status |= RTAUDIO_INPUT_OVERFLOW; asioXRun = false; } int cbReturnValue = callback( stream_.userBuffer[0], stream_.userBuffer[1], stream_.bufferSize, streamTime, status, info->userData ); if ( cbReturnValue == 2 ) { stream_.state = STREAM_STOPPING; handle->drainCounter = 2; unsigned threadId; stream_.callbackInfo.thread = _beginthreadex( NULL, 0, &asioStopStream, &stream_.callbackInfo, 0, &threadId ); return SUCCESS; } else if ( cbReturnValue == 1 ) { handle->drainCounter = 1; handle->internalDrain = true; } } unsigned int nChannels, bufferBytes, i, j; nChannels = stream_.nDeviceChannels[0] + stream_.nDeviceChannels[1]; if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { bufferBytes = stream_.bufferSize * formatBytes( stream_.deviceFormat[0] ); if ( handle->drainCounter > 1 ) { // write zeros to the output stream for ( i=0, j=0; ibufferInfos[i].isInput != ASIOTrue ) memset( handle->bufferInfos[i].buffers[bufferIndex], 0, bufferBytes ); } } else if ( stream_.doConvertBuffer[0] ) { convertBuffer( stream_.deviceBuffer, stream_.userBuffer[0], stream_.convertInfo[0] ); if ( stream_.doByteSwap[0] ) byteSwapBuffer( stream_.deviceBuffer, stream_.bufferSize * stream_.nDeviceChannels[0], stream_.deviceFormat[0] ); for ( i=0, j=0; ibufferInfos[i].isInput != ASIOTrue ) memcpy( handle->bufferInfos[i].buffers[bufferIndex], &stream_.deviceBuffer[j++*bufferBytes], bufferBytes ); } } else { if ( stream_.doByteSwap[0] ) byteSwapBuffer( stream_.userBuffer[0], stream_.bufferSize * stream_.nUserChannels[0], stream_.userFormat ); for ( i=0, j=0; ibufferInfos[i].isInput != ASIOTrue ) memcpy( handle->bufferInfos[i].buffers[bufferIndex], &stream_.userBuffer[0][bufferBytes*j++], bufferBytes ); } } } // Don't bother draining input if ( handle->drainCounter ) { handle->drainCounter++; goto unlock; } if ( stream_.mode == INPUT || stream_.mode == DUPLEX ) { bufferBytes = stream_.bufferSize * formatBytes(stream_.deviceFormat[1]); if (stream_.doConvertBuffer[1]) { // Always interleave ASIO input data. for ( i=0, j=0; ibufferInfos[i].isInput == ASIOTrue ) memcpy( &stream_.deviceBuffer[j++*bufferBytes], handle->bufferInfos[i].buffers[bufferIndex], bufferBytes ); } if ( stream_.doByteSwap[1] ) byteSwapBuffer( stream_.deviceBuffer, stream_.bufferSize * stream_.nDeviceChannels[1], stream_.deviceFormat[1] ); convertBuffer( stream_.userBuffer[1], stream_.deviceBuffer, stream_.convertInfo[1] ); } else { for ( i=0, j=0; ibufferInfos[i].isInput == ASIOTrue ) { memcpy( &stream_.userBuffer[1][bufferBytes*j++], handle->bufferInfos[i].buffers[bufferIndex], bufferBytes ); } } if ( stream_.doByteSwap[1] ) byteSwapBuffer( stream_.userBuffer[1], stream_.bufferSize * stream_.nUserChannels[1], stream_.userFormat ); } } unlock: // The following call was suggested by Malte Clasen. While the API // documentation indicates it should not be required, some device // drivers apparently do not function correctly without it. ASIOOutputReady(); RtApi::tickStreamTime(); return SUCCESS; } static void sampleRateChanged( ASIOSampleRate sRate ) { // The ASIO documentation says that this usually only happens during // external sync. Audio processing is not stopped by the driver, // actual sample rate might not have even changed, maybe only the // sample rate status of an AES/EBU or S/PDIF digital input at the // audio device. RtApi *object = (RtApi *) asioCallbackInfo->object; try { object->stopStream(); } catch ( RtAudioError &exception ) { std::cerr << "\nRtApiAsio: sampleRateChanged() error (" << exception.getMessage() << ")!\n" << std::endl; return; } std::cerr << "\nRtApiAsio: driver reports sample rate changed to " << sRate << " ... stream stopped!!!\n" << std::endl; } static long asioMessages( long selector, long value, void* /*message*/, double* /*opt*/ ) { long ret = 0; switch( selector ) { case kAsioSelectorSupported: if ( value == kAsioResetRequest || value == kAsioEngineVersion || value == kAsioResyncRequest || value == kAsioLatenciesChanged // The following three were added for ASIO 2.0, you don't // necessarily have to support them. || value == kAsioSupportsTimeInfo || value == kAsioSupportsTimeCode || value == kAsioSupportsInputMonitor) ret = 1L; break; case kAsioResetRequest: // Defer the task and perform the reset of the driver during the // next "safe" situation. You cannot reset the driver right now, // as this code is called from the driver. Reset the driver is // done by completely destruct is. I.e. ASIOStop(), // ASIODisposeBuffers(), Destruction Afterwards you initialize the // driver again. std::cerr << "\nRtApiAsio: driver reset requested!!!" << std::endl; ret = 1L; break; case kAsioResyncRequest: // This informs the application that the driver encountered some // non-fatal data loss. It is used for synchronization purposes // of different media. Added mainly to work around the Win16Mutex // problems in Windows 95/98 with the Windows Multimedia system, // which could lose data because the Mutex was held too long by // another thread. However a driver can issue it in other // situations, too. // std::cerr << "\nRtApiAsio: driver resync requested!!!" << std::endl; asioXRun = true; ret = 1L; break; case kAsioLatenciesChanged: // This will inform the host application that the drivers were // latencies changed. Beware, it this does not mean that the // buffer sizes have changed! You might need to update internal // delay data. std::cerr << "\nRtApiAsio: driver latency may have changed!!!" << std::endl; ret = 1L; break; case kAsioEngineVersion: // Return the supported ASIO version of the host application. If // a host application does not implement this selector, ASIO 1.0 // is assumed by the driver. ret = 2L; break; case kAsioSupportsTimeInfo: // Informs the driver whether the // asioCallbacks.bufferSwitchTimeInfo() callback is supported. // For compatibility with ASIO 1.0 drivers the host application // should always support the "old" bufferSwitch method, too. ret = 0; break; case kAsioSupportsTimeCode: // Informs the driver whether application is interested in time // code info. If an application does not need to know about time // code, the driver has less work to do. ret = 0; break; } return ret; } static const char* getAsioErrorString( ASIOError result ) { struct Messages { ASIOError value; const char*message; }; static const Messages m[] = { { ASE_NotPresent, "Hardware input or output is not present or available." }, { ASE_HWMalfunction, "Hardware is malfunctioning." }, { ASE_InvalidParameter, "Invalid input parameter." }, { ASE_InvalidMode, "Invalid mode." }, { ASE_SPNotAdvancing, "Sample position not advancing." }, { ASE_NoClock, "Sample clock or rate cannot be determined or is not present." }, { ASE_NoMemory, "Not enough memory to complete the request." } }; for ( unsigned int i = 0; i < sizeof(m)/sizeof(m[0]); ++i ) if ( m[i].value == result ) return m[i].message; return "Unknown error."; } //******************** End of __WINDOWS_ASIO__ *********************// #endif #if defined(__WINDOWS_WASAPI__) // Windows WASAPI API // Authored by Marcus Tomlinson , April 2014 // - Introduces support for the Windows WASAPI API // - Aims to deliver bit streams to and from hardware at the lowest possible latency, via the absolute minimum buffer sizes required // - Provides flexible stream configuration to an otherwise strict and inflexible WASAPI interface // - Includes automatic internal conversion of sample rate and buffer size between hardware and the user #ifndef INITGUID #define INITGUID #endif #include #include #include #include //============================================================================= #define SAFE_RELEASE( objectPtr )\ if ( objectPtr )\ {\ objectPtr->Release();\ objectPtr = NULL;\ } typedef HANDLE ( __stdcall *TAvSetMmThreadCharacteristicsPtr )( LPCWSTR TaskName, LPDWORD TaskIndex ); //----------------------------------------------------------------------------- // WASAPI dictates stream sample rate, format, channel count, and in some cases, buffer size. // Therefore we must perform all necessary conversions to user buffers in order to satisfy these // requirements. WasapiBuffer ring buffers are used between HwIn->UserIn and UserOut->HwOut to // provide intermediate storage for read / write synchronization. class WasapiBuffer { public: WasapiBuffer() : buffer_( NULL ), bufferSize_( 0 ), inIndex_( 0 ), outIndex_( 0 ) {} ~WasapiBuffer() { free( buffer_ ); } // sets the length of the internal ring buffer void setBufferSize( unsigned int bufferSize, unsigned int formatBytes ) { free( buffer_ ); buffer_ = ( char* ) calloc( bufferSize, formatBytes ); bufferSize_ = bufferSize; inIndex_ = 0; outIndex_ = 0; } // attempt to push a buffer into the ring buffer at the current "in" index bool pushBuffer( char* buffer, unsigned int bufferSize, RtAudioFormat format ) { if ( !buffer || // incoming buffer is NULL bufferSize == 0 || // incoming buffer has no data bufferSize > bufferSize_ ) // incoming buffer too large { return false; } unsigned int relOutIndex = outIndex_; unsigned int inIndexEnd = inIndex_ + bufferSize; if ( relOutIndex < inIndex_ && inIndexEnd >= bufferSize_ ) { relOutIndex += bufferSize_; } // "in" index can end on the "out" index but cannot begin at it if ( inIndex_ <= relOutIndex && inIndexEnd > relOutIndex ) { return false; // not enough space between "in" index and "out" index } // copy buffer from external to internal int fromZeroSize = inIndex_ + bufferSize - bufferSize_; fromZeroSize = fromZeroSize < 0 ? 0 : fromZeroSize; int fromInSize = bufferSize - fromZeroSize; switch( format ) { case RTAUDIO_SINT8: memcpy( &( ( char* ) buffer_ )[inIndex_], buffer, fromInSize * sizeof( char ) ); memcpy( buffer_, &( ( char* ) buffer )[fromInSize], fromZeroSize * sizeof( char ) ); break; case RTAUDIO_SINT16: memcpy( &( ( short* ) buffer_ )[inIndex_], buffer, fromInSize * sizeof( short ) ); memcpy( buffer_, &( ( short* ) buffer )[fromInSize], fromZeroSize * sizeof( short ) ); break; case RTAUDIO_SINT24: memcpy( &( ( S24* ) buffer_ )[inIndex_], buffer, fromInSize * sizeof( S24 ) ); memcpy( buffer_, &( ( S24* ) buffer )[fromInSize], fromZeroSize * sizeof( S24 ) ); break; case RTAUDIO_SINT32: memcpy( &( ( int* ) buffer_ )[inIndex_], buffer, fromInSize * sizeof( int ) ); memcpy( buffer_, &( ( int* ) buffer )[fromInSize], fromZeroSize * sizeof( int ) ); break; case RTAUDIO_FLOAT32: memcpy( &( ( float* ) buffer_ )[inIndex_], buffer, fromInSize * sizeof( float ) ); memcpy( buffer_, &( ( float* ) buffer )[fromInSize], fromZeroSize * sizeof( float ) ); break; case RTAUDIO_FLOAT64: memcpy( &( ( double* ) buffer_ )[inIndex_], buffer, fromInSize * sizeof( double ) ); memcpy( buffer_, &( ( double* ) buffer )[fromInSize], fromZeroSize * sizeof( double ) ); break; } // update "in" index inIndex_ += bufferSize; inIndex_ %= bufferSize_; return true; } // attempt to pull a buffer from the ring buffer from the current "out" index bool pullBuffer( char* buffer, unsigned int bufferSize, RtAudioFormat format ) { if ( !buffer || // incoming buffer is NULL bufferSize == 0 || // incoming buffer has no data bufferSize > bufferSize_ ) // incoming buffer too large { return false; } unsigned int relInIndex = inIndex_; unsigned int outIndexEnd = outIndex_ + bufferSize; if ( relInIndex < outIndex_ && outIndexEnd >= bufferSize_ ) { relInIndex += bufferSize_; } // "out" index can begin at and end on the "in" index if ( outIndex_ < relInIndex && outIndexEnd > relInIndex ) { return false; // not enough space between "out" index and "in" index } // copy buffer from internal to external int fromZeroSize = outIndex_ + bufferSize - bufferSize_; fromZeroSize = fromZeroSize < 0 ? 0 : fromZeroSize; int fromOutSize = bufferSize - fromZeroSize; switch( format ) { case RTAUDIO_SINT8: memcpy( buffer, &( ( char* ) buffer_ )[outIndex_], fromOutSize * sizeof( char ) ); memcpy( &( ( char* ) buffer )[fromOutSize], buffer_, fromZeroSize * sizeof( char ) ); break; case RTAUDIO_SINT16: memcpy( buffer, &( ( short* ) buffer_ )[outIndex_], fromOutSize * sizeof( short ) ); memcpy( &( ( short* ) buffer )[fromOutSize], buffer_, fromZeroSize * sizeof( short ) ); break; case RTAUDIO_SINT24: memcpy( buffer, &( ( S24* ) buffer_ )[outIndex_], fromOutSize * sizeof( S24 ) ); memcpy( &( ( S24* ) buffer )[fromOutSize], buffer_, fromZeroSize * sizeof( S24 ) ); break; case RTAUDIO_SINT32: memcpy( buffer, &( ( int* ) buffer_ )[outIndex_], fromOutSize * sizeof( int ) ); memcpy( &( ( int* ) buffer )[fromOutSize], buffer_, fromZeroSize * sizeof( int ) ); break; case RTAUDIO_FLOAT32: memcpy( buffer, &( ( float* ) buffer_ )[outIndex_], fromOutSize * sizeof( float ) ); memcpy( &( ( float* ) buffer )[fromOutSize], buffer_, fromZeroSize * sizeof( float ) ); break; case RTAUDIO_FLOAT64: memcpy( buffer, &( ( double* ) buffer_ )[outIndex_], fromOutSize * sizeof( double ) ); memcpy( &( ( double* ) buffer )[fromOutSize], buffer_, fromZeroSize * sizeof( double ) ); break; } // update "out" index outIndex_ += bufferSize; outIndex_ %= bufferSize_; return true; } private: char* buffer_; unsigned int bufferSize_; unsigned int inIndex_; unsigned int outIndex_; }; //----------------------------------------------------------------------------- // In order to satisfy WASAPI's buffer requirements, we need a means of converting sample rate // between HW and the user. The convertBufferWasapi function is used to perform this conversion // between HwIn->UserIn and UserOut->HwOut during the stream callback loop. // This sample rate converter favors speed over quality, and works best with conversions between // one rate and its multiple. void convertBufferWasapi( char* outBuffer, const char* inBuffer, const unsigned int& channelCount, const unsigned int& inSampleRate, const unsigned int& outSampleRate, const unsigned int& inSampleCount, unsigned int& outSampleCount, const RtAudioFormat& format ) { // calculate the new outSampleCount and relative sampleStep float sampleRatio = ( float ) outSampleRate / inSampleRate; float sampleStep = 1.0f / sampleRatio; float inSampleFraction = 0.0f; outSampleCount = ( unsigned int ) roundf( inSampleCount * sampleRatio ); // frame-by-frame, copy each relative input sample into it's corresponding output sample for ( unsigned int outSample = 0; outSample < outSampleCount; outSample++ ) { unsigned int inSample = ( unsigned int ) inSampleFraction; switch ( format ) { case RTAUDIO_SINT8: memcpy( &( ( char* ) outBuffer )[ outSample * channelCount ], &( ( char* ) inBuffer )[ inSample * channelCount ], channelCount * sizeof( char ) ); break; case RTAUDIO_SINT16: memcpy( &( ( short* ) outBuffer )[ outSample * channelCount ], &( ( short* ) inBuffer )[ inSample * channelCount ], channelCount * sizeof( short ) ); break; case RTAUDIO_SINT24: memcpy( &( ( S24* ) outBuffer )[ outSample * channelCount ], &( ( S24* ) inBuffer )[ inSample * channelCount ], channelCount * sizeof( S24 ) ); break; case RTAUDIO_SINT32: memcpy( &( ( int* ) outBuffer )[ outSample * channelCount ], &( ( int* ) inBuffer )[ inSample * channelCount ], channelCount * sizeof( int ) ); break; case RTAUDIO_FLOAT32: memcpy( &( ( float* ) outBuffer )[ outSample * channelCount ], &( ( float* ) inBuffer )[ inSample * channelCount ], channelCount * sizeof( float ) ); break; case RTAUDIO_FLOAT64: memcpy( &( ( double* ) outBuffer )[ outSample * channelCount ], &( ( double* ) inBuffer )[ inSample * channelCount ], channelCount * sizeof( double ) ); break; } // jump to next in sample inSampleFraction += sampleStep; } } //----------------------------------------------------------------------------- // A structure to hold various information related to the WASAPI implementation. struct WasapiHandle { IAudioClient* captureAudioClient; IAudioClient* renderAudioClient; IAudioCaptureClient* captureClient; IAudioRenderClient* renderClient; HANDLE captureEvent; HANDLE renderEvent; WasapiHandle() : captureAudioClient( NULL ), renderAudioClient( NULL ), captureClient( NULL ), renderClient( NULL ), captureEvent( NULL ), renderEvent( NULL ) {} }; //============================================================================= RtApiWasapi::RtApiWasapi() : coInitialized_( false ), deviceEnumerator_( NULL ) { // WASAPI can run either apartment or multi-threaded HRESULT hr = CoInitialize( NULL ); if ( !FAILED( hr ) ) coInitialized_ = true; // Instantiate device enumerator hr = CoCreateInstance( __uuidof( MMDeviceEnumerator ), NULL, CLSCTX_ALL, __uuidof( IMMDeviceEnumerator ), ( void** ) &deviceEnumerator_ ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::RtApiWasapi: Unable to instantiate device enumerator"; error( RtAudioError::DRIVER_ERROR ); } } //----------------------------------------------------------------------------- RtApiWasapi::~RtApiWasapi() { if ( stream_.state != STREAM_CLOSED ) closeStream(); SAFE_RELEASE( deviceEnumerator_ ); // If this object previously called CoInitialize() if ( coInitialized_ ) CoUninitialize(); } //============================================================================= unsigned int RtApiWasapi::getDeviceCount( void ) { unsigned int captureDeviceCount = 0; unsigned int renderDeviceCount = 0; IMMDeviceCollection* captureDevices = NULL; IMMDeviceCollection* renderDevices = NULL; // Count capture devices errorText_.clear(); HRESULT hr = deviceEnumerator_->EnumAudioEndpoints( eCapture, DEVICE_STATE_ACTIVE, &captureDevices ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::getDeviceCount: Unable to retrieve capture device collection."; goto Exit; } hr = captureDevices->GetCount( &captureDeviceCount ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::getDeviceCount: Unable to retrieve capture device count."; goto Exit; } // Count render devices hr = deviceEnumerator_->EnumAudioEndpoints( eRender, DEVICE_STATE_ACTIVE, &renderDevices ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::getDeviceCount: Unable to retrieve render device collection."; goto Exit; } hr = renderDevices->GetCount( &renderDeviceCount ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::getDeviceCount: Unable to retrieve render device count."; goto Exit; } Exit: // release all references SAFE_RELEASE( captureDevices ); SAFE_RELEASE( renderDevices ); if ( errorText_.empty() ) return captureDeviceCount + renderDeviceCount; error( RtAudioError::DRIVER_ERROR ); return 0; } //----------------------------------------------------------------------------- RtAudio::DeviceInfo RtApiWasapi::getDeviceInfo( unsigned int device ) { RtAudio::DeviceInfo info; unsigned int captureDeviceCount = 0; unsigned int renderDeviceCount = 0; std::string defaultDeviceName; bool isCaptureDevice = false; PROPVARIANT deviceNameProp; PROPVARIANT defaultDeviceNameProp; IMMDeviceCollection* captureDevices = NULL; IMMDeviceCollection* renderDevices = NULL; IMMDevice* devicePtr = NULL; IMMDevice* defaultDevicePtr = NULL; IAudioClient* audioClient = NULL; IPropertyStore* devicePropStore = NULL; IPropertyStore* defaultDevicePropStore = NULL; WAVEFORMATEX* deviceFormat = NULL; WAVEFORMATEX* closestMatchFormat = NULL; // probed info.probed = false; // Count capture devices errorText_.clear(); RtAudioError::Type errorType = RtAudioError::DRIVER_ERROR; HRESULT hr = deviceEnumerator_->EnumAudioEndpoints( eCapture, DEVICE_STATE_ACTIVE, &captureDevices ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve capture device collection."; goto Exit; } hr = captureDevices->GetCount( &captureDeviceCount ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve capture device count."; goto Exit; } // Count render devices hr = deviceEnumerator_->EnumAudioEndpoints( eRender, DEVICE_STATE_ACTIVE, &renderDevices ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve render device collection."; goto Exit; } hr = renderDevices->GetCount( &renderDeviceCount ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve render device count."; goto Exit; } // validate device index if ( device >= captureDeviceCount + renderDeviceCount ) { errorText_ = "RtApiWasapi::getDeviceInfo: Invalid device index."; errorType = RtAudioError::INVALID_USE; goto Exit; } // determine whether index falls within capture or render devices if ( device >= renderDeviceCount ) { hr = captureDevices->Item( device - renderDeviceCount, &devicePtr ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve capture device handle."; goto Exit; } isCaptureDevice = true; } else { hr = renderDevices->Item( device, &devicePtr ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve render device handle."; goto Exit; } isCaptureDevice = false; } // get default device name if ( isCaptureDevice ) { hr = deviceEnumerator_->GetDefaultAudioEndpoint( eCapture, eConsole, &defaultDevicePtr ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve default capture device handle."; goto Exit; } } else { hr = deviceEnumerator_->GetDefaultAudioEndpoint( eRender, eConsole, &defaultDevicePtr ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve default render device handle."; goto Exit; } } hr = defaultDevicePtr->OpenPropertyStore( STGM_READ, &defaultDevicePropStore ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::getDeviceInfo: Unable to open default device property store."; goto Exit; } PropVariantInit( &defaultDeviceNameProp ); hr = defaultDevicePropStore->GetValue( PKEY_Device_FriendlyName, &defaultDeviceNameProp ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve default device property: PKEY_Device_FriendlyName."; goto Exit; } defaultDeviceName = convertCharPointerToStdString(defaultDeviceNameProp.pwszVal); // name hr = devicePtr->OpenPropertyStore( STGM_READ, &devicePropStore ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::getDeviceInfo: Unable to open device property store."; goto Exit; } PropVariantInit( &deviceNameProp ); hr = devicePropStore->GetValue( PKEY_Device_FriendlyName, &deviceNameProp ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve device property: PKEY_Device_FriendlyName."; goto Exit; } info.name =convertCharPointerToStdString(deviceNameProp.pwszVal); // is default if ( isCaptureDevice ) { info.isDefaultInput = info.name == defaultDeviceName; info.isDefaultOutput = false; } else { info.isDefaultInput = false; info.isDefaultOutput = info.name == defaultDeviceName; } // channel count hr = devicePtr->Activate( __uuidof( IAudioClient ), CLSCTX_ALL, NULL, ( void** ) &audioClient ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve device audio client."; goto Exit; } hr = audioClient->GetMixFormat( &deviceFormat ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve device mix format."; goto Exit; } if ( isCaptureDevice ) { info.inputChannels = deviceFormat->nChannels; info.outputChannels = 0; info.duplexChannels = 0; } else { info.inputChannels = 0; info.outputChannels = deviceFormat->nChannels; info.duplexChannels = 0; } // sample rates info.sampleRates.clear(); // allow support for all sample rates as we have a built-in sample rate converter for ( unsigned int i = 0; i < MAX_SAMPLE_RATES; i++ ) { info.sampleRates.push_back( SAMPLE_RATES[i] ); } info.preferredSampleRate = deviceFormat->nSamplesPerSec; // native format info.nativeFormats = 0; if ( deviceFormat->wFormatTag == WAVE_FORMAT_IEEE_FLOAT || ( deviceFormat->wFormatTag == WAVE_FORMAT_EXTENSIBLE && ( ( WAVEFORMATEXTENSIBLE* ) deviceFormat )->SubFormat == KSDATAFORMAT_SUBTYPE_IEEE_FLOAT ) ) { if ( deviceFormat->wBitsPerSample == 32 ) { info.nativeFormats |= RTAUDIO_FLOAT32; } else if ( deviceFormat->wBitsPerSample == 64 ) { info.nativeFormats |= RTAUDIO_FLOAT64; } } else if ( deviceFormat->wFormatTag == WAVE_FORMAT_PCM || ( deviceFormat->wFormatTag == WAVE_FORMAT_EXTENSIBLE && ( ( WAVEFORMATEXTENSIBLE* ) deviceFormat )->SubFormat == KSDATAFORMAT_SUBTYPE_PCM ) ) { if ( deviceFormat->wBitsPerSample == 8 ) { info.nativeFormats |= RTAUDIO_SINT8; } else if ( deviceFormat->wBitsPerSample == 16 ) { info.nativeFormats |= RTAUDIO_SINT16; } else if ( deviceFormat->wBitsPerSample == 24 ) { info.nativeFormats |= RTAUDIO_SINT24; } else if ( deviceFormat->wBitsPerSample == 32 ) { info.nativeFormats |= RTAUDIO_SINT32; } } // probed info.probed = true; Exit: // release all references PropVariantClear( &deviceNameProp ); PropVariantClear( &defaultDeviceNameProp ); SAFE_RELEASE( captureDevices ); SAFE_RELEASE( renderDevices ); SAFE_RELEASE( devicePtr ); SAFE_RELEASE( defaultDevicePtr ); SAFE_RELEASE( audioClient ); SAFE_RELEASE( devicePropStore ); SAFE_RELEASE( defaultDevicePropStore ); CoTaskMemFree( deviceFormat ); CoTaskMemFree( closestMatchFormat ); if ( !errorText_.empty() ) error( errorType ); return info; } //----------------------------------------------------------------------------- unsigned int RtApiWasapi::getDefaultOutputDevice( void ) { for ( unsigned int i = 0; i < getDeviceCount(); i++ ) { if ( getDeviceInfo( i ).isDefaultOutput ) { return i; } } return 0; } //----------------------------------------------------------------------------- unsigned int RtApiWasapi::getDefaultInputDevice( void ) { for ( unsigned int i = 0; i < getDeviceCount(); i++ ) { if ( getDeviceInfo( i ).isDefaultInput ) { return i; } } return 0; } //----------------------------------------------------------------------------- void RtApiWasapi::closeStream( void ) { if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiWasapi::closeStream: No open stream to close."; error( RtAudioError::WARNING ); return; } if ( stream_.state != STREAM_STOPPED ) stopStream(); // clean up stream memory SAFE_RELEASE( ( ( WasapiHandle* ) stream_.apiHandle )->captureAudioClient ) SAFE_RELEASE( ( ( WasapiHandle* ) stream_.apiHandle )->renderAudioClient ) SAFE_RELEASE( ( ( WasapiHandle* ) stream_.apiHandle )->captureClient ) SAFE_RELEASE( ( ( WasapiHandle* ) stream_.apiHandle )->renderClient ) if ( ( ( WasapiHandle* ) stream_.apiHandle )->captureEvent ) CloseHandle( ( ( WasapiHandle* ) stream_.apiHandle )->captureEvent ); if ( ( ( WasapiHandle* ) stream_.apiHandle )->renderEvent ) CloseHandle( ( ( WasapiHandle* ) stream_.apiHandle )->renderEvent ); delete ( WasapiHandle* ) stream_.apiHandle; stream_.apiHandle = NULL; for ( int i = 0; i < 2; i++ ) { if ( stream_.userBuffer[i] ) { free( stream_.userBuffer[i] ); stream_.userBuffer[i] = 0; } } if ( stream_.deviceBuffer ) { free( stream_.deviceBuffer ); stream_.deviceBuffer = 0; } // update stream state stream_.state = STREAM_CLOSED; } //----------------------------------------------------------------------------- void RtApiWasapi::startStream( void ) { verifyStream(); if ( stream_.state == STREAM_RUNNING ) { errorText_ = "RtApiWasapi::startStream: The stream is already running."; error( RtAudioError::WARNING ); return; } // update stream state stream_.state = STREAM_RUNNING; // create WASAPI stream thread stream_.callbackInfo.thread = ( ThreadHandle ) CreateThread( NULL, 0, runWasapiThread, this, CREATE_SUSPENDED, NULL ); if ( !stream_.callbackInfo.thread ) { errorText_ = "RtApiWasapi::startStream: Unable to instantiate callback thread."; error( RtAudioError::THREAD_ERROR ); } else { SetThreadPriority( ( void* ) stream_.callbackInfo.thread, stream_.callbackInfo.priority ); ResumeThread( ( void* ) stream_.callbackInfo.thread ); } } //----------------------------------------------------------------------------- void RtApiWasapi::stopStream( void ) { verifyStream(); if ( stream_.state == STREAM_STOPPED ) { errorText_ = "RtApiWasapi::stopStream: The stream is already stopped."; error( RtAudioError::WARNING ); return; } // inform stream thread by setting stream state to STREAM_STOPPING stream_.state = STREAM_STOPPING; // wait until stream thread is stopped while( stream_.state != STREAM_STOPPED ) { Sleep( 1 ); } // Wait for the last buffer to play before stopping. Sleep( 1000 * stream_.bufferSize / stream_.sampleRate ); // stop capture client if applicable if ( ( ( WasapiHandle* ) stream_.apiHandle )->captureAudioClient ) { HRESULT hr = ( ( WasapiHandle* ) stream_.apiHandle )->captureAudioClient->Stop(); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::stopStream: Unable to stop capture stream."; error( RtAudioError::DRIVER_ERROR ); return; } } // stop render client if applicable if ( ( ( WasapiHandle* ) stream_.apiHandle )->renderAudioClient ) { HRESULT hr = ( ( WasapiHandle* ) stream_.apiHandle )->renderAudioClient->Stop(); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::stopStream: Unable to stop render stream."; error( RtAudioError::DRIVER_ERROR ); return; } } // close thread handle if ( stream_.callbackInfo.thread && !CloseHandle( ( void* ) stream_.callbackInfo.thread ) ) { errorText_ = "RtApiWasapi::stopStream: Unable to close callback thread."; error( RtAudioError::THREAD_ERROR ); return; } stream_.callbackInfo.thread = (ThreadHandle) NULL; } //----------------------------------------------------------------------------- void RtApiWasapi::abortStream( void ) { verifyStream(); if ( stream_.state == STREAM_STOPPED ) { errorText_ = "RtApiWasapi::abortStream: The stream is already stopped."; error( RtAudioError::WARNING ); return; } // inform stream thread by setting stream state to STREAM_STOPPING stream_.state = STREAM_STOPPING; // wait until stream thread is stopped while ( stream_.state != STREAM_STOPPED ) { Sleep( 1 ); } // stop capture client if applicable if ( ( ( WasapiHandle* ) stream_.apiHandle )->captureAudioClient ) { HRESULT hr = ( ( WasapiHandle* ) stream_.apiHandle )->captureAudioClient->Stop(); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::abortStream: Unable to stop capture stream."; error( RtAudioError::DRIVER_ERROR ); return; } } // stop render client if applicable if ( ( ( WasapiHandle* ) stream_.apiHandle )->renderAudioClient ) { HRESULT hr = ( ( WasapiHandle* ) stream_.apiHandle )->renderAudioClient->Stop(); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::abortStream: Unable to stop render stream."; error( RtAudioError::DRIVER_ERROR ); return; } } // close thread handle if ( stream_.callbackInfo.thread && !CloseHandle( ( void* ) stream_.callbackInfo.thread ) ) { errorText_ = "RtApiWasapi::abortStream: Unable to close callback thread."; error( RtAudioError::THREAD_ERROR ); return; } stream_.callbackInfo.thread = (ThreadHandle) NULL; } //----------------------------------------------------------------------------- bool RtApiWasapi::probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int* bufferSize, RtAudio::StreamOptions* options ) { bool methodResult = FAILURE; unsigned int captureDeviceCount = 0; unsigned int renderDeviceCount = 0; IMMDeviceCollection* captureDevices = NULL; IMMDeviceCollection* renderDevices = NULL; IMMDevice* devicePtr = NULL; WAVEFORMATEX* deviceFormat = NULL; unsigned int bufferBytes; stream_.state = STREAM_STOPPED; // create API Handle if not already created if ( !stream_.apiHandle ) stream_.apiHandle = ( void* ) new WasapiHandle(); // Count capture devices errorText_.clear(); RtAudioError::Type errorType = RtAudioError::DRIVER_ERROR; HRESULT hr = deviceEnumerator_->EnumAudioEndpoints( eCapture, DEVICE_STATE_ACTIVE, &captureDevices ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve capture device collection."; goto Exit; } hr = captureDevices->GetCount( &captureDeviceCount ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve capture device count."; goto Exit; } // Count render devices hr = deviceEnumerator_->EnumAudioEndpoints( eRender, DEVICE_STATE_ACTIVE, &renderDevices ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve render device collection."; goto Exit; } hr = renderDevices->GetCount( &renderDeviceCount ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve render device count."; goto Exit; } // validate device index if ( device >= captureDeviceCount + renderDeviceCount ) { errorType = RtAudioError::INVALID_USE; errorText_ = "RtApiWasapi::probeDeviceOpen: Invalid device index."; goto Exit; } // determine whether index falls within capture or render devices if ( device >= renderDeviceCount ) { if ( mode != INPUT ) { errorType = RtAudioError::INVALID_USE; errorText_ = "RtApiWasapi::probeDeviceOpen: Capture device selected as output device."; goto Exit; } // retrieve captureAudioClient from devicePtr IAudioClient*& captureAudioClient = ( ( WasapiHandle* ) stream_.apiHandle )->captureAudioClient; hr = captureDevices->Item( device - renderDeviceCount, &devicePtr ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve capture device handle."; goto Exit; } hr = devicePtr->Activate( __uuidof( IAudioClient ), CLSCTX_ALL, NULL, ( void** ) &captureAudioClient ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve device audio client."; goto Exit; } hr = captureAudioClient->GetMixFormat( &deviceFormat ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve device mix format."; goto Exit; } stream_.nDeviceChannels[mode] = deviceFormat->nChannels; captureAudioClient->GetStreamLatency( ( long long* ) &stream_.latency[mode] ); } else { if ( mode != OUTPUT ) { errorType = RtAudioError::INVALID_USE; errorText_ = "RtApiWasapi::probeDeviceOpen: Render device selected as input device."; goto Exit; } // retrieve renderAudioClient from devicePtr IAudioClient*& renderAudioClient = ( ( WasapiHandle* ) stream_.apiHandle )->renderAudioClient; hr = renderDevices->Item( device, &devicePtr ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve render device handle."; goto Exit; } hr = devicePtr->Activate( __uuidof( IAudioClient ), CLSCTX_ALL, NULL, ( void** ) &renderAudioClient ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve device audio client."; goto Exit; } hr = renderAudioClient->GetMixFormat( &deviceFormat ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve device mix format."; goto Exit; } stream_.nDeviceChannels[mode] = deviceFormat->nChannels; renderAudioClient->GetStreamLatency( ( long long* ) &stream_.latency[mode] ); } // fill stream data if ( ( stream_.mode == OUTPUT && mode == INPUT ) || ( stream_.mode == INPUT && mode == OUTPUT ) ) { stream_.mode = DUPLEX; } else { stream_.mode = mode; } stream_.device[mode] = device; stream_.doByteSwap[mode] = false; stream_.sampleRate = sampleRate; stream_.bufferSize = *bufferSize; stream_.nBuffers = 1; stream_.nUserChannels[mode] = channels; stream_.channelOffset[mode] = firstChannel; stream_.userFormat = format; stream_.deviceFormat[mode] = getDeviceInfo( device ).nativeFormats; if ( options && options->flags & RTAUDIO_NONINTERLEAVED ) stream_.userInterleaved = false; else stream_.userInterleaved = true; stream_.deviceInterleaved[mode] = true; // Set flags for buffer conversion. stream_.doConvertBuffer[mode] = false; if ( stream_.userFormat != stream_.deviceFormat[mode] || stream_.nUserChannels != stream_.nDeviceChannels ) stream_.doConvertBuffer[mode] = true; else if ( stream_.userInterleaved != stream_.deviceInterleaved[mode] && stream_.nUserChannels[mode] > 1 ) stream_.doConvertBuffer[mode] = true; if ( stream_.doConvertBuffer[mode] ) setConvertInfo( mode, 0 ); // Allocate necessary internal buffers bufferBytes = stream_.nUserChannels[mode] * stream_.bufferSize * formatBytes( stream_.userFormat ); stream_.userBuffer[mode] = ( char* ) calloc( bufferBytes, 1 ); if ( !stream_.userBuffer[mode] ) { errorType = RtAudioError::MEMORY_ERROR; errorText_ = "RtApiWasapi::probeDeviceOpen: Error allocating user buffer memory."; goto Exit; } if ( options && options->flags & RTAUDIO_SCHEDULE_REALTIME ) stream_.callbackInfo.priority = 15; else stream_.callbackInfo.priority = 0; ///! TODO: RTAUDIO_MINIMIZE_LATENCY // Provide stream buffers directly to callback ///! TODO: RTAUDIO_HOG_DEVICE // Exclusive mode methodResult = SUCCESS; Exit: //clean up SAFE_RELEASE( captureDevices ); SAFE_RELEASE( renderDevices ); SAFE_RELEASE( devicePtr ); CoTaskMemFree( deviceFormat ); // if method failed, close the stream if ( methodResult == FAILURE ) closeStream(); if ( !errorText_.empty() ) error( errorType ); return methodResult; } //============================================================================= DWORD WINAPI RtApiWasapi::runWasapiThread( void* wasapiPtr ) { if ( wasapiPtr ) ( ( RtApiWasapi* ) wasapiPtr )->wasapiThread(); return 0; } DWORD WINAPI RtApiWasapi::stopWasapiThread( void* wasapiPtr ) { if ( wasapiPtr ) ( ( RtApiWasapi* ) wasapiPtr )->stopStream(); return 0; } DWORD WINAPI RtApiWasapi::abortWasapiThread( void* wasapiPtr ) { if ( wasapiPtr ) ( ( RtApiWasapi* ) wasapiPtr )->abortStream(); return 0; } //----------------------------------------------------------------------------- void RtApiWasapi::wasapiThread() { // as this is a new thread, we must CoInitialize it CoInitialize( NULL ); HRESULT hr; IAudioClient* captureAudioClient = ( ( WasapiHandle* ) stream_.apiHandle )->captureAudioClient; IAudioClient* renderAudioClient = ( ( WasapiHandle* ) stream_.apiHandle )->renderAudioClient; IAudioCaptureClient* captureClient = ( ( WasapiHandle* ) stream_.apiHandle )->captureClient; IAudioRenderClient* renderClient = ( ( WasapiHandle* ) stream_.apiHandle )->renderClient; HANDLE captureEvent = ( ( WasapiHandle* ) stream_.apiHandle )->captureEvent; HANDLE renderEvent = ( ( WasapiHandle* ) stream_.apiHandle )->renderEvent; WAVEFORMATEX* captureFormat = NULL; WAVEFORMATEX* renderFormat = NULL; float captureSrRatio = 0.0f; float renderSrRatio = 0.0f; WasapiBuffer captureBuffer; WasapiBuffer renderBuffer; // declare local stream variables RtAudioCallback callback = ( RtAudioCallback ) stream_.callbackInfo.callback; BYTE* streamBuffer = NULL; unsigned long captureFlags = 0; unsigned int bufferFrameCount = 0; unsigned int numFramesPadding = 0; unsigned int convBufferSize = 0; bool callbackPushed = false; bool callbackPulled = false; bool callbackStopped = false; int callbackResult = 0; // convBuffer is used to store converted buffers between WASAPI and the user char* convBuffer = NULL; unsigned int convBuffSize = 0; unsigned int deviceBuffSize = 0; errorText_.clear(); RtAudioError::Type errorType = RtAudioError::DRIVER_ERROR; // Attempt to assign "Pro Audio" characteristic to thread HMODULE AvrtDll = LoadLibrary( (LPCTSTR) "AVRT.dll" ); if ( AvrtDll ) { DWORD taskIndex = 0; TAvSetMmThreadCharacteristicsPtr AvSetMmThreadCharacteristicsPtr = ( TAvSetMmThreadCharacteristicsPtr ) GetProcAddress( AvrtDll, "AvSetMmThreadCharacteristicsW" ); AvSetMmThreadCharacteristicsPtr( L"Pro Audio", &taskIndex ); FreeLibrary( AvrtDll ); } // start capture stream if applicable if ( captureAudioClient ) { hr = captureAudioClient->GetMixFormat( &captureFormat ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::wasapiThread: Unable to retrieve device mix format."; goto Exit; } captureSrRatio = ( ( float ) captureFormat->nSamplesPerSec / stream_.sampleRate ); // initialize capture stream according to desire buffer size float desiredBufferSize = stream_.bufferSize * captureSrRatio; REFERENCE_TIME desiredBufferPeriod = ( REFERENCE_TIME ) ( ( float ) desiredBufferSize * 10000000 / captureFormat->nSamplesPerSec ); if ( !captureClient ) { hr = captureAudioClient->Initialize( AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, desiredBufferPeriod, desiredBufferPeriod, captureFormat, NULL ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::wasapiThread: Unable to initialize capture audio client."; goto Exit; } hr = captureAudioClient->GetService( __uuidof( IAudioCaptureClient ), ( void** ) &captureClient ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::wasapiThread: Unable to retrieve capture client handle."; goto Exit; } // configure captureEvent to trigger on every available capture buffer captureEvent = CreateEvent( NULL, FALSE, FALSE, NULL ); if ( !captureEvent ) { errorType = RtAudioError::SYSTEM_ERROR; errorText_ = "RtApiWasapi::wasapiThread: Unable to create capture event."; goto Exit; } hr = captureAudioClient->SetEventHandle( captureEvent ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::wasapiThread: Unable to set capture event handle."; goto Exit; } ( ( WasapiHandle* ) stream_.apiHandle )->captureClient = captureClient; ( ( WasapiHandle* ) stream_.apiHandle )->captureEvent = captureEvent; } unsigned int inBufferSize = 0; hr = captureAudioClient->GetBufferSize( &inBufferSize ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::wasapiThread: Unable to get capture buffer size."; goto Exit; } // scale outBufferSize according to stream->user sample rate ratio unsigned int outBufferSize = ( unsigned int ) ( stream_.bufferSize * captureSrRatio ) * stream_.nDeviceChannels[INPUT]; inBufferSize *= stream_.nDeviceChannels[INPUT]; // set captureBuffer size captureBuffer.setBufferSize( inBufferSize + outBufferSize, formatBytes( stream_.deviceFormat[INPUT] ) ); // reset the capture stream hr = captureAudioClient->Reset(); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::wasapiThread: Unable to reset capture stream."; goto Exit; } // start the capture stream hr = captureAudioClient->Start(); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::wasapiThread: Unable to start capture stream."; goto Exit; } } // start render stream if applicable if ( renderAudioClient ) { hr = renderAudioClient->GetMixFormat( &renderFormat ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::wasapiThread: Unable to retrieve device mix format."; goto Exit; } renderSrRatio = ( ( float ) renderFormat->nSamplesPerSec / stream_.sampleRate ); // initialize render stream according to desire buffer size float desiredBufferSize = stream_.bufferSize * renderSrRatio; REFERENCE_TIME desiredBufferPeriod = ( REFERENCE_TIME ) ( ( float ) desiredBufferSize * 10000000 / renderFormat->nSamplesPerSec ); if ( !renderClient ) { hr = renderAudioClient->Initialize( AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, desiredBufferPeriod, desiredBufferPeriod, renderFormat, NULL ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::wasapiThread: Unable to initialize render audio client."; goto Exit; } hr = renderAudioClient->GetService( __uuidof( IAudioRenderClient ), ( void** ) &renderClient ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::wasapiThread: Unable to retrieve render client handle."; goto Exit; } // configure renderEvent to trigger on every available render buffer renderEvent = CreateEvent( NULL, FALSE, FALSE, NULL ); if ( !renderEvent ) { errorType = RtAudioError::SYSTEM_ERROR; errorText_ = "RtApiWasapi::wasapiThread: Unable to create render event."; goto Exit; } hr = renderAudioClient->SetEventHandle( renderEvent ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::wasapiThread: Unable to set render event handle."; goto Exit; } ( ( WasapiHandle* ) stream_.apiHandle )->renderClient = renderClient; ( ( WasapiHandle* ) stream_.apiHandle )->renderEvent = renderEvent; } unsigned int outBufferSize = 0; hr = renderAudioClient->GetBufferSize( &outBufferSize ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::wasapiThread: Unable to get render buffer size."; goto Exit; } // scale inBufferSize according to user->stream sample rate ratio unsigned int inBufferSize = ( unsigned int ) ( stream_.bufferSize * renderSrRatio ) * stream_.nDeviceChannels[OUTPUT]; outBufferSize *= stream_.nDeviceChannels[OUTPUT]; // set renderBuffer size renderBuffer.setBufferSize( inBufferSize + outBufferSize, formatBytes( stream_.deviceFormat[OUTPUT] ) ); // reset the render stream hr = renderAudioClient->Reset(); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::wasapiThread: Unable to reset render stream."; goto Exit; } // start the render stream hr = renderAudioClient->Start(); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::wasapiThread: Unable to start render stream."; goto Exit; } } if ( stream_.mode == INPUT ) { convBuffSize = ( size_t ) ( stream_.bufferSize * captureSrRatio ) * stream_.nDeviceChannels[INPUT] * formatBytes( stream_.deviceFormat[INPUT] ); deviceBuffSize = stream_.bufferSize * stream_.nDeviceChannels[INPUT] * formatBytes( stream_.deviceFormat[INPUT] ); } else if ( stream_.mode == OUTPUT ) { convBuffSize = ( size_t ) ( stream_.bufferSize * renderSrRatio ) * stream_.nDeviceChannels[OUTPUT] * formatBytes( stream_.deviceFormat[OUTPUT] ); deviceBuffSize = stream_.bufferSize * stream_.nDeviceChannels[OUTPUT] * formatBytes( stream_.deviceFormat[OUTPUT] ); } else if ( stream_.mode == DUPLEX ) { convBuffSize = std::max( ( size_t ) ( stream_.bufferSize * captureSrRatio ) * stream_.nDeviceChannels[INPUT] * formatBytes( stream_.deviceFormat[INPUT] ), ( size_t ) ( stream_.bufferSize * renderSrRatio ) * stream_.nDeviceChannels[OUTPUT] * formatBytes( stream_.deviceFormat[OUTPUT] ) ); deviceBuffSize = std::max( stream_.bufferSize * stream_.nDeviceChannels[INPUT] * formatBytes( stream_.deviceFormat[INPUT] ), stream_.bufferSize * stream_.nDeviceChannels[OUTPUT] * formatBytes( stream_.deviceFormat[OUTPUT] ) ); } convBuffer = ( char* ) malloc( convBuffSize ); stream_.deviceBuffer = ( char* ) malloc( deviceBuffSize ); if ( !convBuffer || !stream_.deviceBuffer ) { errorType = RtAudioError::MEMORY_ERROR; errorText_ = "RtApiWasapi::wasapiThread: Error allocating device buffer memory."; goto Exit; } // stream process loop while ( stream_.state != STREAM_STOPPING ) { if ( !callbackPulled ) { // Callback Input // ============== // 1. Pull callback buffer from inputBuffer // 2. If 1. was successful: Convert callback buffer to user sample rate and channel count // Convert callback buffer to user format if ( captureAudioClient ) { // Pull callback buffer from inputBuffer callbackPulled = captureBuffer.pullBuffer( convBuffer, ( unsigned int ) ( stream_.bufferSize * captureSrRatio ) * stream_.nDeviceChannels[INPUT], stream_.deviceFormat[INPUT] ); if ( callbackPulled ) { // Convert callback buffer to user sample rate convertBufferWasapi( stream_.deviceBuffer, convBuffer, stream_.nDeviceChannels[INPUT], captureFormat->nSamplesPerSec, stream_.sampleRate, ( unsigned int ) ( stream_.bufferSize * captureSrRatio ), convBufferSize, stream_.deviceFormat[INPUT] ); if ( stream_.doConvertBuffer[INPUT] ) { // Convert callback buffer to user format convertBuffer( stream_.userBuffer[INPUT], stream_.deviceBuffer, stream_.convertInfo[INPUT] ); } else { // no further conversion, simple copy deviceBuffer to userBuffer memcpy( stream_.userBuffer[INPUT], stream_.deviceBuffer, stream_.bufferSize * stream_.nUserChannels[INPUT] * formatBytes( stream_.userFormat ) ); } } } else { // if there is no capture stream, set callbackPulled flag callbackPulled = true; } // Execute Callback // ================ // 1. Execute user callback method // 2. Handle return value from callback // if callback has not requested the stream to stop if ( callbackPulled && !callbackStopped ) { // Execute user callback method callbackResult = callback( stream_.userBuffer[OUTPUT], stream_.userBuffer[INPUT], stream_.bufferSize, getStreamTime(), captureFlags & AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY ? RTAUDIO_INPUT_OVERFLOW : 0, stream_.callbackInfo.userData ); // Handle return value from callback if ( callbackResult == 1 ) { // instantiate a thread to stop this thread HANDLE threadHandle = CreateThread( NULL, 0, stopWasapiThread, this, 0, NULL ); if ( !threadHandle ) { errorType = RtAudioError::THREAD_ERROR; errorText_ = "RtApiWasapi::wasapiThread: Unable to instantiate stream stop thread."; goto Exit; } else if ( !CloseHandle( threadHandle ) ) { errorType = RtAudioError::THREAD_ERROR; errorText_ = "RtApiWasapi::wasapiThread: Unable to close stream stop thread handle."; goto Exit; } callbackStopped = true; } else if ( callbackResult == 2 ) { // instantiate a thread to stop this thread HANDLE threadHandle = CreateThread( NULL, 0, abortWasapiThread, this, 0, NULL ); if ( !threadHandle ) { errorType = RtAudioError::THREAD_ERROR; errorText_ = "RtApiWasapi::wasapiThread: Unable to instantiate stream abort thread."; goto Exit; } else if ( !CloseHandle( threadHandle ) ) { errorType = RtAudioError::THREAD_ERROR; errorText_ = "RtApiWasapi::wasapiThread: Unable to close stream abort thread handle."; goto Exit; } callbackStopped = true; } } } // Callback Output // =============== // 1. Convert callback buffer to stream format // 2. Convert callback buffer to stream sample rate and channel count // 3. Push callback buffer into outputBuffer if ( renderAudioClient && callbackPulled ) { if ( stream_.doConvertBuffer[OUTPUT] ) { // Convert callback buffer to stream format convertBuffer( stream_.deviceBuffer, stream_.userBuffer[OUTPUT], stream_.convertInfo[OUTPUT] ); } // Convert callback buffer to stream sample rate convertBufferWasapi( convBuffer, stream_.deviceBuffer, stream_.nDeviceChannels[OUTPUT], stream_.sampleRate, renderFormat->nSamplesPerSec, stream_.bufferSize, convBufferSize, stream_.deviceFormat[OUTPUT] ); // Push callback buffer into outputBuffer callbackPushed = renderBuffer.pushBuffer( convBuffer, convBufferSize * stream_.nDeviceChannels[OUTPUT], stream_.deviceFormat[OUTPUT] ); } else { // if there is no render stream, set callbackPushed flag callbackPushed = true; } // Stream Capture // ============== // 1. Get capture buffer from stream // 2. Push capture buffer into inputBuffer // 3. If 2. was successful: Release capture buffer if ( captureAudioClient ) { // if the callback input buffer was not pulled from captureBuffer, wait for next capture event if ( !callbackPulled ) { WaitForSingleObject( captureEvent, INFINITE ); } // Get capture buffer from stream hr = captureClient->GetBuffer( &streamBuffer, &bufferFrameCount, &captureFlags, NULL, NULL ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::wasapiThread: Unable to retrieve capture buffer."; goto Exit; } if ( bufferFrameCount != 0 ) { // Push capture buffer into inputBuffer if ( captureBuffer.pushBuffer( ( char* ) streamBuffer, bufferFrameCount * stream_.nDeviceChannels[INPUT], stream_.deviceFormat[INPUT] ) ) { // Release capture buffer hr = captureClient->ReleaseBuffer( bufferFrameCount ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::wasapiThread: Unable to release capture buffer."; goto Exit; } } else { // Inform WASAPI that capture was unsuccessful hr = captureClient->ReleaseBuffer( 0 ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::wasapiThread: Unable to release capture buffer."; goto Exit; } } } else { // Inform WASAPI that capture was unsuccessful hr = captureClient->ReleaseBuffer( 0 ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::wasapiThread: Unable to release capture buffer."; goto Exit; } } } // Stream Render // ============= // 1. Get render buffer from stream // 2. Pull next buffer from outputBuffer // 3. If 2. was successful: Fill render buffer with next buffer // Release render buffer if ( renderAudioClient ) { // if the callback output buffer was not pushed to renderBuffer, wait for next render event if ( callbackPulled && !callbackPushed ) { WaitForSingleObject( renderEvent, INFINITE ); } // Get render buffer from stream hr = renderAudioClient->GetBufferSize( &bufferFrameCount ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::wasapiThread: Unable to retrieve render buffer size."; goto Exit; } hr = renderAudioClient->GetCurrentPadding( &numFramesPadding ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::wasapiThread: Unable to retrieve render buffer padding."; goto Exit; } bufferFrameCount -= numFramesPadding; if ( bufferFrameCount != 0 ) { hr = renderClient->GetBuffer( bufferFrameCount, &streamBuffer ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::wasapiThread: Unable to retrieve render buffer."; goto Exit; } // Pull next buffer from outputBuffer // Fill render buffer with next buffer if ( renderBuffer.pullBuffer( ( char* ) streamBuffer, bufferFrameCount * stream_.nDeviceChannels[OUTPUT], stream_.deviceFormat[OUTPUT] ) ) { // Release render buffer hr = renderClient->ReleaseBuffer( bufferFrameCount, 0 ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::wasapiThread: Unable to release render buffer."; goto Exit; } } else { // Inform WASAPI that render was unsuccessful hr = renderClient->ReleaseBuffer( 0, 0 ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::wasapiThread: Unable to release render buffer."; goto Exit; } } } else { // Inform WASAPI that render was unsuccessful hr = renderClient->ReleaseBuffer( 0, 0 ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::wasapiThread: Unable to release render buffer."; goto Exit; } } } // if the callback buffer was pushed renderBuffer reset callbackPulled flag if ( callbackPushed ) { callbackPulled = false; // tick stream time RtApi::tickStreamTime(); } } Exit: // clean up CoTaskMemFree( captureFormat ); CoTaskMemFree( renderFormat ); free ( convBuffer ); CoUninitialize(); // update stream state stream_.state = STREAM_STOPPED; if ( errorText_.empty() ) return; else error( errorType ); } //******************** End of __WINDOWS_WASAPI__ *********************// #endif #if defined(__WINDOWS_DS__) // Windows DirectSound API // Modified by Robin Davies, October 2005 // - Improvements to DirectX pointer chasing. // - Bug fix for non-power-of-two Asio granularity used by Edirol PCR-A30. // - Auto-call CoInitialize for DSOUND and ASIO platforms. // Various revisions for RtAudio 4.0 by Gary Scavone, April 2007 // Changed device query structure for RtAudio 4.0.7, January 2010 #include #include #include #if defined(__MINGW32__) // missing from latest mingw winapi #define WAVE_FORMAT_96M08 0x00010000 /* 96 kHz, Mono, 8-bit */ #define WAVE_FORMAT_96S08 0x00020000 /* 96 kHz, Stereo, 8-bit */ #define WAVE_FORMAT_96M16 0x00040000 /* 96 kHz, Mono, 16-bit */ #define WAVE_FORMAT_96S16 0x00080000 /* 96 kHz, Stereo, 16-bit */ #endif #define MINIMUM_DEVICE_BUFFER_SIZE 32768 #ifdef _MSC_VER // if Microsoft Visual C++ #pragma comment( lib, "winmm.lib" ) // then, auto-link winmm.lib. Otherwise, it has to be added manually. #endif static inline DWORD dsPointerBetween( DWORD pointer, DWORD laterPointer, DWORD earlierPointer, DWORD bufferSize ) { if ( pointer > bufferSize ) pointer -= bufferSize; if ( laterPointer < earlierPointer ) laterPointer += bufferSize; if ( pointer < earlierPointer ) pointer += bufferSize; return pointer >= earlierPointer && pointer < laterPointer; } // A structure to hold various information related to the DirectSound // API implementation. struct DsHandle { unsigned int drainCounter; // Tracks callback counts when draining bool internalDrain; // Indicates if stop is initiated from callback or not. void *id[2]; void *buffer[2]; bool xrun[2]; UINT bufferPointer[2]; DWORD dsBufferSize[2]; DWORD dsPointerLeadTime[2]; // the number of bytes ahead of the safe pointer to lead by. HANDLE condition; DsHandle() :drainCounter(0), internalDrain(false) { id[0] = 0; id[1] = 0; buffer[0] = 0; buffer[1] = 0; xrun[0] = false; xrun[1] = false; bufferPointer[0] = 0; bufferPointer[1] = 0; } }; // Declarations for utility functions, callbacks, and structures // specific to the DirectSound implementation. static BOOL CALLBACK deviceQueryCallback( LPGUID lpguid, LPCTSTR description, LPCTSTR module, LPVOID lpContext ); static const char* getErrorString( int code ); static unsigned __stdcall callbackHandler( void *ptr ); struct DsDevice { LPGUID id[2]; bool validId[2]; bool found; std::string name; DsDevice() : found(false) { validId[0] = false; validId[1] = false; } }; struct DsProbeData { bool isInput; std::vector* dsDevices; }; RtApiDs :: RtApiDs() { // Dsound will run both-threaded. If CoInitialize fails, then just // accept whatever the mainline chose for a threading model. coInitialized_ = false; HRESULT hr = CoInitialize( NULL ); if ( !FAILED( hr ) ) coInitialized_ = true; } RtApiDs :: ~RtApiDs() { if ( coInitialized_ ) CoUninitialize(); // balanced call. if ( stream_.state != STREAM_CLOSED ) closeStream(); } // The DirectSound default output is always the first device. unsigned int RtApiDs :: getDefaultOutputDevice( void ) { return 0; } // The DirectSound default input is always the first input device, // which is the first capture device enumerated. unsigned int RtApiDs :: getDefaultInputDevice( void ) { return 0; } unsigned int RtApiDs :: getDeviceCount( void ) { // Set query flag for previously found devices to false, so that we // can check for any devices that have disappeared. for ( unsigned int i=0; i(dsDevices.size()); } RtAudio::DeviceInfo RtApiDs :: getDeviceInfo( unsigned int device ) { RtAudio::DeviceInfo info; info.probed = false; if ( dsDevices.size() == 0 ) { // Force a query of all devices getDeviceCount(); if ( dsDevices.size() == 0 ) { errorText_ = "RtApiDs::getDeviceInfo: no devices found!"; error( RtAudioError::INVALID_USE ); return info; } } if ( device >= dsDevices.size() ) { errorText_ = "RtApiDs::getDeviceInfo: device ID is invalid!"; error( RtAudioError::INVALID_USE ); return info; } HRESULT result; if ( dsDevices[ device ].validId[0] == false ) goto probeInput; LPDIRECTSOUND output; DSCAPS outCaps; result = DirectSoundCreate( dsDevices[ device ].id[0], &output, NULL ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::getDeviceInfo: error (" << getErrorString( result ) << ") opening output device (" << dsDevices[ device ].name << ")!"; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); goto probeInput; } outCaps.dwSize = sizeof( outCaps ); result = output->GetCaps( &outCaps ); if ( FAILED( result ) ) { output->Release(); errorStream_ << "RtApiDs::getDeviceInfo: error (" << getErrorString( result ) << ") getting capabilities!"; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); goto probeInput; } // Get output channel information. info.outputChannels = ( outCaps.dwFlags & DSCAPS_PRIMARYSTEREO ) ? 2 : 1; // Get sample rate information. info.sampleRates.clear(); for ( unsigned int k=0; k= (unsigned int) outCaps.dwMinSecondarySampleRate && SAMPLE_RATES[k] <= (unsigned int) outCaps.dwMaxSecondarySampleRate ) { info.sampleRates.push_back( SAMPLE_RATES[k] ); if ( !info.preferredSampleRate || ( SAMPLE_RATES[k] <= 48000 && SAMPLE_RATES[k] > info.preferredSampleRate ) ) info.preferredSampleRate = SAMPLE_RATES[k]; } } // Get format information. if ( outCaps.dwFlags & DSCAPS_PRIMARY16BIT ) info.nativeFormats |= RTAUDIO_SINT16; if ( outCaps.dwFlags & DSCAPS_PRIMARY8BIT ) info.nativeFormats |= RTAUDIO_SINT8; output->Release(); if ( getDefaultOutputDevice() == device ) info.isDefaultOutput = true; if ( dsDevices[ device ].validId[1] == false ) { info.name = dsDevices[ device ].name; info.probed = true; return info; } probeInput: LPDIRECTSOUNDCAPTURE input; result = DirectSoundCaptureCreate( dsDevices[ device ].id[1], &input, NULL ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::getDeviceInfo: error (" << getErrorString( result ) << ") opening input device (" << dsDevices[ device ].name << ")!"; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); return info; } DSCCAPS inCaps; inCaps.dwSize = sizeof( inCaps ); result = input->GetCaps( &inCaps ); if ( FAILED( result ) ) { input->Release(); errorStream_ << "RtApiDs::getDeviceInfo: error (" << getErrorString( result ) << ") getting object capabilities (" << dsDevices[ device ].name << ")!"; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); return info; } // Get input channel information. info.inputChannels = inCaps.dwChannels; // Get sample rate and format information. std::vector rates; if ( inCaps.dwChannels >= 2 ) { if ( inCaps.dwFormats & WAVE_FORMAT_1S16 ) info.nativeFormats |= RTAUDIO_SINT16; if ( inCaps.dwFormats & WAVE_FORMAT_2S16 ) info.nativeFormats |= RTAUDIO_SINT16; if ( inCaps.dwFormats & WAVE_FORMAT_4S16 ) info.nativeFormats |= RTAUDIO_SINT16; if ( inCaps.dwFormats & WAVE_FORMAT_96S16 ) info.nativeFormats |= RTAUDIO_SINT16; if ( inCaps.dwFormats & WAVE_FORMAT_1S08 ) info.nativeFormats |= RTAUDIO_SINT8; if ( inCaps.dwFormats & WAVE_FORMAT_2S08 ) info.nativeFormats |= RTAUDIO_SINT8; if ( inCaps.dwFormats & WAVE_FORMAT_4S08 ) info.nativeFormats |= RTAUDIO_SINT8; if ( inCaps.dwFormats & WAVE_FORMAT_96S08 ) info.nativeFormats |= RTAUDIO_SINT8; if ( info.nativeFormats & RTAUDIO_SINT16 ) { if ( inCaps.dwFormats & WAVE_FORMAT_1S16 ) rates.push_back( 11025 ); if ( inCaps.dwFormats & WAVE_FORMAT_2S16 ) rates.push_back( 22050 ); if ( inCaps.dwFormats & WAVE_FORMAT_4S16 ) rates.push_back( 44100 ); if ( inCaps.dwFormats & WAVE_FORMAT_96S16 ) rates.push_back( 96000 ); } else if ( info.nativeFormats & RTAUDIO_SINT8 ) { if ( inCaps.dwFormats & WAVE_FORMAT_1S08 ) rates.push_back( 11025 ); if ( inCaps.dwFormats & WAVE_FORMAT_2S08 ) rates.push_back( 22050 ); if ( inCaps.dwFormats & WAVE_FORMAT_4S08 ) rates.push_back( 44100 ); if ( inCaps.dwFormats & WAVE_FORMAT_96S08 ) rates.push_back( 96000 ); } } else if ( inCaps.dwChannels == 1 ) { if ( inCaps.dwFormats & WAVE_FORMAT_1M16 ) info.nativeFormats |= RTAUDIO_SINT16; if ( inCaps.dwFormats & WAVE_FORMAT_2M16 ) info.nativeFormats |= RTAUDIO_SINT16; if ( inCaps.dwFormats & WAVE_FORMAT_4M16 ) info.nativeFormats |= RTAUDIO_SINT16; if ( inCaps.dwFormats & WAVE_FORMAT_96M16 ) info.nativeFormats |= RTAUDIO_SINT16; if ( inCaps.dwFormats & WAVE_FORMAT_1M08 ) info.nativeFormats |= RTAUDIO_SINT8; if ( inCaps.dwFormats & WAVE_FORMAT_2M08 ) info.nativeFormats |= RTAUDIO_SINT8; if ( inCaps.dwFormats & WAVE_FORMAT_4M08 ) info.nativeFormats |= RTAUDIO_SINT8; if ( inCaps.dwFormats & WAVE_FORMAT_96M08 ) info.nativeFormats |= RTAUDIO_SINT8; if ( info.nativeFormats & RTAUDIO_SINT16 ) { if ( inCaps.dwFormats & WAVE_FORMAT_1M16 ) rates.push_back( 11025 ); if ( inCaps.dwFormats & WAVE_FORMAT_2M16 ) rates.push_back( 22050 ); if ( inCaps.dwFormats & WAVE_FORMAT_4M16 ) rates.push_back( 44100 ); if ( inCaps.dwFormats & WAVE_FORMAT_96M16 ) rates.push_back( 96000 ); } else if ( info.nativeFormats & RTAUDIO_SINT8 ) { if ( inCaps.dwFormats & WAVE_FORMAT_1M08 ) rates.push_back( 11025 ); if ( inCaps.dwFormats & WAVE_FORMAT_2M08 ) rates.push_back( 22050 ); if ( inCaps.dwFormats & WAVE_FORMAT_4M08 ) rates.push_back( 44100 ); if ( inCaps.dwFormats & WAVE_FORMAT_96M08 ) rates.push_back( 96000 ); } } else info.inputChannels = 0; // technically, this would be an error input->Release(); if ( info.inputChannels == 0 ) return info; // Copy the supported rates to the info structure but avoid duplication. bool found; for ( unsigned int i=0; i 0 && info.inputChannels > 0 ) info.duplexChannels = (info.outputChannels > info.inputChannels) ? info.inputChannels : info.outputChannels; if ( device == 0 ) info.isDefaultInput = true; // Copy name and return. info.name = dsDevices[ device ].name; info.probed = true; return info; } bool RtApiDs :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, RtAudio::StreamOptions *options ) { if ( channels + firstChannel > 2 ) { errorText_ = "RtApiDs::probeDeviceOpen: DirectSound does not support more than 2 channels per device."; return FAILURE; } size_t nDevices = dsDevices.size(); if ( nDevices == 0 ) { // This should not happen because a check is made before this function is called. errorText_ = "RtApiDs::probeDeviceOpen: no devices found!"; return FAILURE; } if ( device >= nDevices ) { // This should not happen because a check is made before this function is called. errorText_ = "RtApiDs::probeDeviceOpen: device ID is invalid!"; return FAILURE; } if ( mode == OUTPUT ) { if ( dsDevices[ device ].validId[0] == false ) { errorStream_ << "RtApiDs::probeDeviceOpen: device (" << device << ") does not support output!"; errorText_ = errorStream_.str(); return FAILURE; } } else { // mode == INPUT if ( dsDevices[ device ].validId[1] == false ) { errorStream_ << "RtApiDs::probeDeviceOpen: device (" << device << ") does not support input!"; errorText_ = errorStream_.str(); return FAILURE; } } // According to a note in PortAudio, using GetDesktopWindow() // instead of GetForegroundWindow() is supposed to avoid problems // that occur when the application's window is not the foreground // window. Also, if the application window closes before the // DirectSound buffer, DirectSound can crash. In the past, I had // problems when using GetDesktopWindow() but it seems fine now // (January 2010). I'll leave it commented here. // HWND hWnd = GetForegroundWindow(); HWND hWnd = GetDesktopWindow(); // Check the numberOfBuffers parameter and limit the lowest value to // two. This is a judgement call and a value of two is probably too // low for capture, but it should work for playback. int nBuffers = 0; if ( options ) nBuffers = options->numberOfBuffers; if ( options && options->flags & RTAUDIO_MINIMIZE_LATENCY ) nBuffers = 2; if ( nBuffers < 2 ) nBuffers = 3; // Check the lower range of the user-specified buffer size and set // (arbitrarily) to a lower bound of 32. if ( *bufferSize < 32 ) *bufferSize = 32; // Create the wave format structure. The data format setting will // be determined later. WAVEFORMATEX waveFormat; ZeroMemory( &waveFormat, sizeof(WAVEFORMATEX) ); waveFormat.wFormatTag = WAVE_FORMAT_PCM; waveFormat.nChannels = channels + firstChannel; waveFormat.nSamplesPerSec = (unsigned long) sampleRate; // Determine the device buffer size. By default, we'll use the value // defined above (32K), but we will grow it to make allowances for // very large software buffer sizes. DWORD dsBufferSize = MINIMUM_DEVICE_BUFFER_SIZE; DWORD dsPointerLeadTime = 0; void *ohandle = 0, *bhandle = 0; HRESULT result; if ( mode == OUTPUT ) { LPDIRECTSOUND output; result = DirectSoundCreate( dsDevices[ device ].id[0], &output, NULL ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") opening output device (" << dsDevices[ device ].name << ")!"; errorText_ = errorStream_.str(); return FAILURE; } DSCAPS outCaps; outCaps.dwSize = sizeof( outCaps ); result = output->GetCaps( &outCaps ); if ( FAILED( result ) ) { output->Release(); errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") getting capabilities (" << dsDevices[ device ].name << ")!"; errorText_ = errorStream_.str(); return FAILURE; } // Check channel information. if ( channels + firstChannel == 2 && !( outCaps.dwFlags & DSCAPS_PRIMARYSTEREO ) ) { errorStream_ << "RtApiDs::getDeviceInfo: the output device (" << dsDevices[ device ].name << ") does not support stereo playback."; errorText_ = errorStream_.str(); return FAILURE; } // Check format information. Use 16-bit format unless not // supported or user requests 8-bit. if ( outCaps.dwFlags & DSCAPS_PRIMARY16BIT && !( format == RTAUDIO_SINT8 && outCaps.dwFlags & DSCAPS_PRIMARY8BIT ) ) { waveFormat.wBitsPerSample = 16; stream_.deviceFormat[mode] = RTAUDIO_SINT16; } else { waveFormat.wBitsPerSample = 8; stream_.deviceFormat[mode] = RTAUDIO_SINT8; } stream_.userFormat = format; // Update wave format structure and buffer information. waveFormat.nBlockAlign = waveFormat.nChannels * waveFormat.wBitsPerSample / 8; waveFormat.nAvgBytesPerSec = waveFormat.nSamplesPerSec * waveFormat.nBlockAlign; dsPointerLeadTime = nBuffers * (*bufferSize) * (waveFormat.wBitsPerSample / 8) * channels; // If the user wants an even bigger buffer, increase the device buffer size accordingly. while ( dsPointerLeadTime * 2U > dsBufferSize ) dsBufferSize *= 2; // Set cooperative level to DSSCL_EXCLUSIVE ... sound stops when window focus changes. // result = output->SetCooperativeLevel( hWnd, DSSCL_EXCLUSIVE ); // Set cooperative level to DSSCL_PRIORITY ... sound remains when window focus changes. result = output->SetCooperativeLevel( hWnd, DSSCL_PRIORITY ); if ( FAILED( result ) ) { output->Release(); errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") setting cooperative level (" << dsDevices[ device ].name << ")!"; errorText_ = errorStream_.str(); return FAILURE; } // Even though we will write to the secondary buffer, we need to // access the primary buffer to set the correct output format // (since the default is 8-bit, 22 kHz!). Setup the DS primary // buffer description. DSBUFFERDESC bufferDescription; ZeroMemory( &bufferDescription, sizeof( DSBUFFERDESC ) ); bufferDescription.dwSize = sizeof( DSBUFFERDESC ); bufferDescription.dwFlags = DSBCAPS_PRIMARYBUFFER; // Obtain the primary buffer LPDIRECTSOUNDBUFFER buffer; result = output->CreateSoundBuffer( &bufferDescription, &buffer, NULL ); if ( FAILED( result ) ) { output->Release(); errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") accessing primary buffer (" << dsDevices[ device ].name << ")!"; errorText_ = errorStream_.str(); return FAILURE; } // Set the primary DS buffer sound format. result = buffer->SetFormat( &waveFormat ); if ( FAILED( result ) ) { output->Release(); errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") setting primary buffer format (" << dsDevices[ device ].name << ")!"; errorText_ = errorStream_.str(); return FAILURE; } // Setup the secondary DS buffer description. ZeroMemory( &bufferDescription, sizeof( DSBUFFERDESC ) ); bufferDescription.dwSize = sizeof( DSBUFFERDESC ); bufferDescription.dwFlags = ( DSBCAPS_STICKYFOCUS | DSBCAPS_GLOBALFOCUS | DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_LOCHARDWARE ); // Force hardware mixing bufferDescription.dwBufferBytes = dsBufferSize; bufferDescription.lpwfxFormat = &waveFormat; // Try to create the secondary DS buffer. If that doesn't work, // try to use software mixing. Otherwise, there's a problem. result = output->CreateSoundBuffer( &bufferDescription, &buffer, NULL ); if ( FAILED( result ) ) { bufferDescription.dwFlags = ( DSBCAPS_STICKYFOCUS | DSBCAPS_GLOBALFOCUS | DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_LOCSOFTWARE ); // Force software mixing result = output->CreateSoundBuffer( &bufferDescription, &buffer, NULL ); if ( FAILED( result ) ) { output->Release(); errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") creating secondary buffer (" << dsDevices[ device ].name << ")!"; errorText_ = errorStream_.str(); return FAILURE; } } // Get the buffer size ... might be different from what we specified. DSBCAPS dsbcaps; dsbcaps.dwSize = sizeof( DSBCAPS ); result = buffer->GetCaps( &dsbcaps ); if ( FAILED( result ) ) { output->Release(); buffer->Release(); errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") getting buffer settings (" << dsDevices[ device ].name << ")!"; errorText_ = errorStream_.str(); return FAILURE; } dsBufferSize = dsbcaps.dwBufferBytes; // Lock the DS buffer LPVOID audioPtr; DWORD dataLen; result = buffer->Lock( 0, dsBufferSize, &audioPtr, &dataLen, NULL, NULL, 0 ); if ( FAILED( result ) ) { output->Release(); buffer->Release(); errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") locking buffer (" << dsDevices[ device ].name << ")!"; errorText_ = errorStream_.str(); return FAILURE; } // Zero the DS buffer ZeroMemory( audioPtr, dataLen ); // Unlock the DS buffer result = buffer->Unlock( audioPtr, dataLen, NULL, 0 ); if ( FAILED( result ) ) { output->Release(); buffer->Release(); errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") unlocking buffer (" << dsDevices[ device ].name << ")!"; errorText_ = errorStream_.str(); return FAILURE; } ohandle = (void *) output; bhandle = (void *) buffer; } if ( mode == INPUT ) { LPDIRECTSOUNDCAPTURE input; result = DirectSoundCaptureCreate( dsDevices[ device ].id[1], &input, NULL ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") opening input device (" << dsDevices[ device ].name << ")!"; errorText_ = errorStream_.str(); return FAILURE; } DSCCAPS inCaps; inCaps.dwSize = sizeof( inCaps ); result = input->GetCaps( &inCaps ); if ( FAILED( result ) ) { input->Release(); errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") getting input capabilities (" << dsDevices[ device ].name << ")!"; errorText_ = errorStream_.str(); return FAILURE; } // Check channel information. if ( inCaps.dwChannels < channels + firstChannel ) { errorText_ = "RtApiDs::getDeviceInfo: the input device does not support requested input channels."; return FAILURE; } // Check format information. Use 16-bit format unless user // requests 8-bit. DWORD deviceFormats; if ( channels + firstChannel == 2 ) { deviceFormats = WAVE_FORMAT_1S08 | WAVE_FORMAT_2S08 | WAVE_FORMAT_4S08 | WAVE_FORMAT_96S08; if ( format == RTAUDIO_SINT8 && inCaps.dwFormats & deviceFormats ) { waveFormat.wBitsPerSample = 8; stream_.deviceFormat[mode] = RTAUDIO_SINT8; } else { // assume 16-bit is supported waveFormat.wBitsPerSample = 16; stream_.deviceFormat[mode] = RTAUDIO_SINT16; } } else { // channel == 1 deviceFormats = WAVE_FORMAT_1M08 | WAVE_FORMAT_2M08 | WAVE_FORMAT_4M08 | WAVE_FORMAT_96M08; if ( format == RTAUDIO_SINT8 && inCaps.dwFormats & deviceFormats ) { waveFormat.wBitsPerSample = 8; stream_.deviceFormat[mode] = RTAUDIO_SINT8; } else { // assume 16-bit is supported waveFormat.wBitsPerSample = 16; stream_.deviceFormat[mode] = RTAUDIO_SINT16; } } stream_.userFormat = format; // Update wave format structure and buffer information. waveFormat.nBlockAlign = waveFormat.nChannels * waveFormat.wBitsPerSample / 8; waveFormat.nAvgBytesPerSec = waveFormat.nSamplesPerSec * waveFormat.nBlockAlign; dsPointerLeadTime = nBuffers * (*bufferSize) * (waveFormat.wBitsPerSample / 8) * channels; // If the user wants an even bigger buffer, increase the device buffer size accordingly. while ( dsPointerLeadTime * 2U > dsBufferSize ) dsBufferSize *= 2; // Setup the secondary DS buffer description. DSCBUFFERDESC bufferDescription; ZeroMemory( &bufferDescription, sizeof( DSCBUFFERDESC ) ); bufferDescription.dwSize = sizeof( DSCBUFFERDESC ); bufferDescription.dwFlags = 0; bufferDescription.dwReserved = 0; bufferDescription.dwBufferBytes = dsBufferSize; bufferDescription.lpwfxFormat = &waveFormat; // Create the capture buffer. LPDIRECTSOUNDCAPTUREBUFFER buffer; result = input->CreateCaptureBuffer( &bufferDescription, &buffer, NULL ); if ( FAILED( result ) ) { input->Release(); errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") creating input buffer (" << dsDevices[ device ].name << ")!"; errorText_ = errorStream_.str(); return FAILURE; } // Get the buffer size ... might be different from what we specified. DSCBCAPS dscbcaps; dscbcaps.dwSize = sizeof( DSCBCAPS ); result = buffer->GetCaps( &dscbcaps ); if ( FAILED( result ) ) { input->Release(); buffer->Release(); errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") getting buffer settings (" << dsDevices[ device ].name << ")!"; errorText_ = errorStream_.str(); return FAILURE; } dsBufferSize = dscbcaps.dwBufferBytes; // NOTE: We could have a problem here if this is a duplex stream // and the play and capture hardware buffer sizes are different // (I'm actually not sure if that is a problem or not). // Currently, we are not verifying that. // Lock the capture buffer LPVOID audioPtr; DWORD dataLen; result = buffer->Lock( 0, dsBufferSize, &audioPtr, &dataLen, NULL, NULL, 0 ); if ( FAILED( result ) ) { input->Release(); buffer->Release(); errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") locking input buffer (" << dsDevices[ device ].name << ")!"; errorText_ = errorStream_.str(); return FAILURE; } // Zero the buffer ZeroMemory( audioPtr, dataLen ); // Unlock the buffer result = buffer->Unlock( audioPtr, dataLen, NULL, 0 ); if ( FAILED( result ) ) { input->Release(); buffer->Release(); errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") unlocking input buffer (" << dsDevices[ device ].name << ")!"; errorText_ = errorStream_.str(); return FAILURE; } ohandle = (void *) input; bhandle = (void *) buffer; } // Set various stream parameters DsHandle *handle = 0; stream_.nDeviceChannels[mode] = channels + firstChannel; stream_.nUserChannels[mode] = channels; stream_.bufferSize = *bufferSize; stream_.channelOffset[mode] = firstChannel; stream_.deviceInterleaved[mode] = true; if ( options && options->flags & RTAUDIO_NONINTERLEAVED ) stream_.userInterleaved = false; else stream_.userInterleaved = true; // Set flag for buffer conversion stream_.doConvertBuffer[mode] = false; if (stream_.nUserChannels[mode] != stream_.nDeviceChannels[mode]) stream_.doConvertBuffer[mode] = true; if (stream_.userFormat != stream_.deviceFormat[mode]) stream_.doConvertBuffer[mode] = true; if ( stream_.userInterleaved != stream_.deviceInterleaved[mode] && stream_.nUserChannels[mode] > 1 ) stream_.doConvertBuffer[mode] = true; // Allocate necessary internal buffers long bufferBytes = stream_.nUserChannels[mode] * *bufferSize * formatBytes( stream_.userFormat ); stream_.userBuffer[mode] = (char *) calloc( bufferBytes, 1 ); if ( stream_.userBuffer[mode] == NULL ) { errorText_ = "RtApiDs::probeDeviceOpen: error allocating user buffer memory."; goto error; } if ( stream_.doConvertBuffer[mode] ) { bool makeBuffer = true; bufferBytes = stream_.nDeviceChannels[mode] * formatBytes( stream_.deviceFormat[mode] ); if ( mode == INPUT ) { if ( stream_.mode == OUTPUT && stream_.deviceBuffer ) { unsigned long bytesOut = stream_.nDeviceChannels[0] * formatBytes( stream_.deviceFormat[0] ); if ( bufferBytes <= (long) bytesOut ) makeBuffer = false; } } if ( makeBuffer ) { bufferBytes *= *bufferSize; if ( stream_.deviceBuffer ) free( stream_.deviceBuffer ); stream_.deviceBuffer = (char *) calloc( bufferBytes, 1 ); if ( stream_.deviceBuffer == NULL ) { errorText_ = "RtApiDs::probeDeviceOpen: error allocating device buffer memory."; goto error; } } } // Allocate our DsHandle structures for the stream. if ( stream_.apiHandle == 0 ) { try { handle = new DsHandle; } catch ( std::bad_alloc& ) { errorText_ = "RtApiDs::probeDeviceOpen: error allocating AsioHandle memory."; goto error; } // Create a manual-reset event. handle->condition = CreateEvent( NULL, // no security TRUE, // manual-reset FALSE, // non-signaled initially NULL ); // unnamed stream_.apiHandle = (void *) handle; } else handle = (DsHandle *) stream_.apiHandle; handle->id[mode] = ohandle; handle->buffer[mode] = bhandle; handle->dsBufferSize[mode] = dsBufferSize; handle->dsPointerLeadTime[mode] = dsPointerLeadTime; stream_.device[mode] = device; stream_.state = STREAM_STOPPED; if ( stream_.mode == OUTPUT && mode == INPUT ) // We had already set up an output stream. stream_.mode = DUPLEX; else stream_.mode = mode; stream_.nBuffers = nBuffers; stream_.sampleRate = sampleRate; // Setup the buffer conversion information structure. if ( stream_.doConvertBuffer[mode] ) setConvertInfo( mode, firstChannel ); // Setup the callback thread. if ( stream_.callbackInfo.isRunning == false ) { unsigned threadId; stream_.callbackInfo.isRunning = true; stream_.callbackInfo.object = (void *) this; stream_.callbackInfo.thread = _beginthreadex( NULL, 0, &callbackHandler, &stream_.callbackInfo, 0, &threadId ); if ( stream_.callbackInfo.thread == 0 ) { errorText_ = "RtApiDs::probeDeviceOpen: error creating callback thread!"; goto error; } // Boost DS thread priority SetThreadPriority( (HANDLE) stream_.callbackInfo.thread, THREAD_PRIORITY_HIGHEST ); } return SUCCESS; error: if ( handle ) { if ( handle->buffer[0] ) { // the object pointer can be NULL and valid LPDIRECTSOUND object = (LPDIRECTSOUND) handle->id[0]; LPDIRECTSOUNDBUFFER buffer = (LPDIRECTSOUNDBUFFER) handle->buffer[0]; if ( buffer ) buffer->Release(); object->Release(); } if ( handle->buffer[1] ) { LPDIRECTSOUNDCAPTURE object = (LPDIRECTSOUNDCAPTURE) handle->id[1]; LPDIRECTSOUNDCAPTUREBUFFER buffer = (LPDIRECTSOUNDCAPTUREBUFFER) handle->buffer[1]; if ( buffer ) buffer->Release(); object->Release(); } CloseHandle( handle->condition ); delete handle; stream_.apiHandle = 0; } for ( int i=0; i<2; i++ ) { if ( stream_.userBuffer[i] ) { free( stream_.userBuffer[i] ); stream_.userBuffer[i] = 0; } } if ( stream_.deviceBuffer ) { free( stream_.deviceBuffer ); stream_.deviceBuffer = 0; } stream_.state = STREAM_CLOSED; return FAILURE; } void RtApiDs :: closeStream() { if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiDs::closeStream(): no open stream to close!"; error( RtAudioError::WARNING ); return; } // Stop the callback thread. stream_.callbackInfo.isRunning = false; WaitForSingleObject( (HANDLE) stream_.callbackInfo.thread, INFINITE ); CloseHandle( (HANDLE) stream_.callbackInfo.thread ); DsHandle *handle = (DsHandle *) stream_.apiHandle; if ( handle ) { if ( handle->buffer[0] ) { // the object pointer can be NULL and valid LPDIRECTSOUND object = (LPDIRECTSOUND) handle->id[0]; LPDIRECTSOUNDBUFFER buffer = (LPDIRECTSOUNDBUFFER) handle->buffer[0]; if ( buffer ) { buffer->Stop(); buffer->Release(); } object->Release(); } if ( handle->buffer[1] ) { LPDIRECTSOUNDCAPTURE object = (LPDIRECTSOUNDCAPTURE) handle->id[1]; LPDIRECTSOUNDCAPTUREBUFFER buffer = (LPDIRECTSOUNDCAPTUREBUFFER) handle->buffer[1]; if ( buffer ) { buffer->Stop(); buffer->Release(); } object->Release(); } CloseHandle( handle->condition ); delete handle; stream_.apiHandle = 0; } for ( int i=0; i<2; i++ ) { if ( stream_.userBuffer[i] ) { free( stream_.userBuffer[i] ); stream_.userBuffer[i] = 0; } } if ( stream_.deviceBuffer ) { free( stream_.deviceBuffer ); stream_.deviceBuffer = 0; } stream_.mode = UNINITIALIZED; stream_.state = STREAM_CLOSED; } void RtApiDs :: startStream() { verifyStream(); if ( stream_.state == STREAM_RUNNING ) { errorText_ = "RtApiDs::startStream(): the stream is already running!"; error( RtAudioError::WARNING ); return; } DsHandle *handle = (DsHandle *) stream_.apiHandle; // Increase scheduler frequency on lesser windows (a side-effect of // increasing timer accuracy). On greater windows (Win2K or later), // this is already in effect. timeBeginPeriod( 1 ); buffersRolling = false; duplexPrerollBytes = 0; if ( stream_.mode == DUPLEX ) { // 0.5 seconds of silence in DUPLEX mode while the devices spin up and synchronize. duplexPrerollBytes = (int) ( 0.5 * stream_.sampleRate * formatBytes( stream_.deviceFormat[1] ) * stream_.nDeviceChannels[1] ); } HRESULT result = 0; if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { LPDIRECTSOUNDBUFFER buffer = (LPDIRECTSOUNDBUFFER) handle->buffer[0]; result = buffer->Play( 0, 0, DSBPLAY_LOOPING ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::startStream: error (" << getErrorString( result ) << ") starting output buffer!"; errorText_ = errorStream_.str(); goto unlock; } } if ( stream_.mode == INPUT || stream_.mode == DUPLEX ) { LPDIRECTSOUNDCAPTUREBUFFER buffer = (LPDIRECTSOUNDCAPTUREBUFFER) handle->buffer[1]; result = buffer->Start( DSCBSTART_LOOPING ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::startStream: error (" << getErrorString( result ) << ") starting input buffer!"; errorText_ = errorStream_.str(); goto unlock; } } handle->drainCounter = 0; handle->internalDrain = false; ResetEvent( handle->condition ); stream_.state = STREAM_RUNNING; unlock: if ( FAILED( result ) ) error( RtAudioError::SYSTEM_ERROR ); } void RtApiDs :: stopStream() { verifyStream(); if ( stream_.state == STREAM_STOPPED ) { errorText_ = "RtApiDs::stopStream(): the stream is already stopped!"; error( RtAudioError::WARNING ); return; } HRESULT result = 0; LPVOID audioPtr; DWORD dataLen; DsHandle *handle = (DsHandle *) stream_.apiHandle; if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { if ( handle->drainCounter == 0 ) { handle->drainCounter = 2; WaitForSingleObject( handle->condition, INFINITE ); // block until signaled } stream_.state = STREAM_STOPPED; MUTEX_LOCK( &stream_.mutex ); // Stop the buffer and clear memory LPDIRECTSOUNDBUFFER buffer = (LPDIRECTSOUNDBUFFER) handle->buffer[0]; result = buffer->Stop(); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::stopStream: error (" << getErrorString( result ) << ") stopping output buffer!"; errorText_ = errorStream_.str(); goto unlock; } // Lock the buffer and clear it so that if we start to play again, // we won't have old data playing. result = buffer->Lock( 0, handle->dsBufferSize[0], &audioPtr, &dataLen, NULL, NULL, 0 ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::stopStream: error (" << getErrorString( result ) << ") locking output buffer!"; errorText_ = errorStream_.str(); goto unlock; } // Zero the DS buffer ZeroMemory( audioPtr, dataLen ); // Unlock the DS buffer result = buffer->Unlock( audioPtr, dataLen, NULL, 0 ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::stopStream: error (" << getErrorString( result ) << ") unlocking output buffer!"; errorText_ = errorStream_.str(); goto unlock; } // If we start playing again, we must begin at beginning of buffer. handle->bufferPointer[0] = 0; } if ( stream_.mode == INPUT || stream_.mode == DUPLEX ) { LPDIRECTSOUNDCAPTUREBUFFER buffer = (LPDIRECTSOUNDCAPTUREBUFFER) handle->buffer[1]; audioPtr = NULL; dataLen = 0; stream_.state = STREAM_STOPPED; if ( stream_.mode != DUPLEX ) MUTEX_LOCK( &stream_.mutex ); result = buffer->Stop(); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::stopStream: error (" << getErrorString( result ) << ") stopping input buffer!"; errorText_ = errorStream_.str(); goto unlock; } // Lock the buffer and clear it so that if we start to play again, // we won't have old data playing. result = buffer->Lock( 0, handle->dsBufferSize[1], &audioPtr, &dataLen, NULL, NULL, 0 ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::stopStream: error (" << getErrorString( result ) << ") locking input buffer!"; errorText_ = errorStream_.str(); goto unlock; } // Zero the DS buffer ZeroMemory( audioPtr, dataLen ); // Unlock the DS buffer result = buffer->Unlock( audioPtr, dataLen, NULL, 0 ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::stopStream: error (" << getErrorString( result ) << ") unlocking input buffer!"; errorText_ = errorStream_.str(); goto unlock; } // If we start recording again, we must begin at beginning of buffer. handle->bufferPointer[1] = 0; } unlock: timeEndPeriod( 1 ); // revert to normal scheduler frequency on lesser windows. MUTEX_UNLOCK( &stream_.mutex ); if ( FAILED( result ) ) error( RtAudioError::SYSTEM_ERROR ); } void RtApiDs :: abortStream() { verifyStream(); if ( stream_.state == STREAM_STOPPED ) { errorText_ = "RtApiDs::abortStream(): the stream is already stopped!"; error( RtAudioError::WARNING ); return; } DsHandle *handle = (DsHandle *) stream_.apiHandle; handle->drainCounter = 2; stopStream(); } void RtApiDs :: callbackEvent() { if ( stream_.state == STREAM_STOPPED || stream_.state == STREAM_STOPPING ) { Sleep( 50 ); // sleep 50 milliseconds return; } if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiDs::callbackEvent(): the stream is closed ... this shouldn't happen!"; error( RtAudioError::WARNING ); return; } CallbackInfo *info = (CallbackInfo *) &stream_.callbackInfo; DsHandle *handle = (DsHandle *) stream_.apiHandle; // Check if we were draining the stream and signal is finished. if ( handle->drainCounter > stream_.nBuffers + 2 ) { stream_.state = STREAM_STOPPING; if ( handle->internalDrain == false ) SetEvent( handle->condition ); else stopStream(); return; } // Invoke user callback to get fresh output data UNLESS we are // draining stream. if ( handle->drainCounter == 0 ) { RtAudioCallback callback = (RtAudioCallback) info->callback; double streamTime = getStreamTime(); RtAudioStreamStatus status = 0; if ( stream_.mode != INPUT && handle->xrun[0] == true ) { status |= RTAUDIO_OUTPUT_UNDERFLOW; handle->xrun[0] = false; } if ( stream_.mode != OUTPUT && handle->xrun[1] == true ) { status |= RTAUDIO_INPUT_OVERFLOW; handle->xrun[1] = false; } int cbReturnValue = callback( stream_.userBuffer[0], stream_.userBuffer[1], stream_.bufferSize, streamTime, status, info->userData ); if ( cbReturnValue == 2 ) { stream_.state = STREAM_STOPPING; handle->drainCounter = 2; abortStream(); return; } else if ( cbReturnValue == 1 ) { handle->drainCounter = 1; handle->internalDrain = true; } } HRESULT result; DWORD currentWritePointer, safeWritePointer; DWORD currentReadPointer, safeReadPointer; UINT nextWritePointer; LPVOID buffer1 = NULL; LPVOID buffer2 = NULL; DWORD bufferSize1 = 0; DWORD bufferSize2 = 0; char *buffer; long bufferBytes; MUTEX_LOCK( &stream_.mutex ); if ( stream_.state == STREAM_STOPPED ) { MUTEX_UNLOCK( &stream_.mutex ); return; } if ( buffersRolling == false ) { if ( stream_.mode == DUPLEX ) { //assert( handle->dsBufferSize[0] == handle->dsBufferSize[1] ); // It takes a while for the devices to get rolling. As a result, // there's no guarantee that the capture and write device pointers // will move in lockstep. Wait here for both devices to start // rolling, and then set our buffer pointers accordingly. // e.g. Crystal Drivers: the capture buffer starts up 5700 to 9600 // bytes later than the write buffer. // Stub: a serious risk of having a pre-emptive scheduling round // take place between the two GetCurrentPosition calls... but I'm // really not sure how to solve the problem. Temporarily boost to // Realtime priority, maybe; but I'm not sure what priority the // DirectSound service threads run at. We *should* be roughly // within a ms or so of correct. LPDIRECTSOUNDBUFFER dsWriteBuffer = (LPDIRECTSOUNDBUFFER) handle->buffer[0]; LPDIRECTSOUNDCAPTUREBUFFER dsCaptureBuffer = (LPDIRECTSOUNDCAPTUREBUFFER) handle->buffer[1]; DWORD startSafeWritePointer, startSafeReadPointer; result = dsWriteBuffer->GetCurrentPosition( NULL, &startSafeWritePointer ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current write position!"; errorText_ = errorStream_.str(); MUTEX_UNLOCK( &stream_.mutex ); error( RtAudioError::SYSTEM_ERROR ); return; } result = dsCaptureBuffer->GetCurrentPosition( NULL, &startSafeReadPointer ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current read position!"; errorText_ = errorStream_.str(); MUTEX_UNLOCK( &stream_.mutex ); error( RtAudioError::SYSTEM_ERROR ); return; } while ( true ) { result = dsWriteBuffer->GetCurrentPosition( NULL, &safeWritePointer ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current write position!"; errorText_ = errorStream_.str(); MUTEX_UNLOCK( &stream_.mutex ); error( RtAudioError::SYSTEM_ERROR ); return; } result = dsCaptureBuffer->GetCurrentPosition( NULL, &safeReadPointer ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current read position!"; errorText_ = errorStream_.str(); MUTEX_UNLOCK( &stream_.mutex ); error( RtAudioError::SYSTEM_ERROR ); return; } if ( safeWritePointer != startSafeWritePointer && safeReadPointer != startSafeReadPointer ) break; Sleep( 1 ); } //assert( handle->dsBufferSize[0] == handle->dsBufferSize[1] ); handle->bufferPointer[0] = safeWritePointer + handle->dsPointerLeadTime[0]; if ( handle->bufferPointer[0] >= handle->dsBufferSize[0] ) handle->bufferPointer[0] -= handle->dsBufferSize[0]; handle->bufferPointer[1] = safeReadPointer; } else if ( stream_.mode == OUTPUT ) { // Set the proper nextWritePosition after initial startup. LPDIRECTSOUNDBUFFER dsWriteBuffer = (LPDIRECTSOUNDBUFFER) handle->buffer[0]; result = dsWriteBuffer->GetCurrentPosition( ¤tWritePointer, &safeWritePointer ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current write position!"; errorText_ = errorStream_.str(); MUTEX_UNLOCK( &stream_.mutex ); error( RtAudioError::SYSTEM_ERROR ); return; } handle->bufferPointer[0] = safeWritePointer + handle->dsPointerLeadTime[0]; if ( handle->bufferPointer[0] >= handle->dsBufferSize[0] ) handle->bufferPointer[0] -= handle->dsBufferSize[0]; } buffersRolling = true; } if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { LPDIRECTSOUNDBUFFER dsBuffer = (LPDIRECTSOUNDBUFFER) handle->buffer[0]; if ( handle->drainCounter > 1 ) { // write zeros to the output stream bufferBytes = stream_.bufferSize * stream_.nUserChannels[0]; bufferBytes *= formatBytes( stream_.userFormat ); memset( stream_.userBuffer[0], 0, bufferBytes ); } // Setup parameters and do buffer conversion if necessary. if ( stream_.doConvertBuffer[0] ) { buffer = stream_.deviceBuffer; convertBuffer( buffer, stream_.userBuffer[0], stream_.convertInfo[0] ); bufferBytes = stream_.bufferSize * stream_.nDeviceChannels[0]; bufferBytes *= formatBytes( stream_.deviceFormat[0] ); } else { buffer = stream_.userBuffer[0]; bufferBytes = stream_.bufferSize * stream_.nUserChannels[0]; bufferBytes *= formatBytes( stream_.userFormat ); } // No byte swapping necessary in DirectSound implementation. // Ahhh ... windoze. 16-bit data is signed but 8-bit data is // unsigned. So, we need to convert our signed 8-bit data here to // unsigned. if ( stream_.deviceFormat[0] == RTAUDIO_SINT8 ) for ( int i=0; idsBufferSize[0]; nextWritePointer = handle->bufferPointer[0]; DWORD endWrite, leadPointer; while ( true ) { // Find out where the read and "safe write" pointers are. result = dsBuffer->GetCurrentPosition( ¤tWritePointer, &safeWritePointer ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current write position!"; errorText_ = errorStream_.str(); MUTEX_UNLOCK( &stream_.mutex ); error( RtAudioError::SYSTEM_ERROR ); return; } // We will copy our output buffer into the region between // safeWritePointer and leadPointer. If leadPointer is not // beyond the next endWrite position, wait until it is. leadPointer = safeWritePointer + handle->dsPointerLeadTime[0]; //std::cout << "safeWritePointer = " << safeWritePointer << ", leadPointer = " << leadPointer << ", nextWritePointer = " << nextWritePointer << std::endl; if ( leadPointer > dsBufferSize ) leadPointer -= dsBufferSize; if ( leadPointer < nextWritePointer ) leadPointer += dsBufferSize; // unwrap offset endWrite = nextWritePointer + bufferBytes; // Check whether the entire write region is behind the play pointer. if ( leadPointer >= endWrite ) break; // If we are here, then we must wait until the leadPointer advances // beyond the end of our next write region. We use the // Sleep() function to suspend operation until that happens. double millis = ( endWrite - leadPointer ) * 1000.0; millis /= ( formatBytes( stream_.deviceFormat[0]) * stream_.nDeviceChannels[0] * stream_.sampleRate); if ( millis < 1.0 ) millis = 1.0; Sleep( (DWORD) millis ); } if ( dsPointerBetween( nextWritePointer, safeWritePointer, currentWritePointer, dsBufferSize ) || dsPointerBetween( endWrite, safeWritePointer, currentWritePointer, dsBufferSize ) ) { // We've strayed into the forbidden zone ... resync the read pointer. handle->xrun[0] = true; nextWritePointer = safeWritePointer + handle->dsPointerLeadTime[0] - bufferBytes; if ( nextWritePointer >= dsBufferSize ) nextWritePointer -= dsBufferSize; handle->bufferPointer[0] = nextWritePointer; endWrite = nextWritePointer + bufferBytes; } // Lock free space in the buffer result = dsBuffer->Lock( nextWritePointer, bufferBytes, &buffer1, &bufferSize1, &buffer2, &bufferSize2, 0 ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") locking buffer during playback!"; errorText_ = errorStream_.str(); MUTEX_UNLOCK( &stream_.mutex ); error( RtAudioError::SYSTEM_ERROR ); return; } // Copy our buffer into the DS buffer CopyMemory( buffer1, buffer, bufferSize1 ); if ( buffer2 != NULL ) CopyMemory( buffer2, buffer+bufferSize1, bufferSize2 ); // Update our buffer offset and unlock sound buffer dsBuffer->Unlock( buffer1, bufferSize1, buffer2, bufferSize2 ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") unlocking buffer during playback!"; errorText_ = errorStream_.str(); MUTEX_UNLOCK( &stream_.mutex ); error( RtAudioError::SYSTEM_ERROR ); return; } nextWritePointer = ( nextWritePointer + bufferSize1 + bufferSize2 ) % dsBufferSize; handle->bufferPointer[0] = nextWritePointer; } // Don't bother draining input if ( handle->drainCounter ) { handle->drainCounter++; goto unlock; } if ( stream_.mode == INPUT || stream_.mode == DUPLEX ) { // Setup parameters. if ( stream_.doConvertBuffer[1] ) { buffer = stream_.deviceBuffer; bufferBytes = stream_.bufferSize * stream_.nDeviceChannels[1]; bufferBytes *= formatBytes( stream_.deviceFormat[1] ); } else { buffer = stream_.userBuffer[1]; bufferBytes = stream_.bufferSize * stream_.nUserChannels[1]; bufferBytes *= formatBytes( stream_.userFormat ); } LPDIRECTSOUNDCAPTUREBUFFER dsBuffer = (LPDIRECTSOUNDCAPTUREBUFFER) handle->buffer[1]; long nextReadPointer = handle->bufferPointer[1]; DWORD dsBufferSize = handle->dsBufferSize[1]; // Find out where the write and "safe read" pointers are. result = dsBuffer->GetCurrentPosition( ¤tReadPointer, &safeReadPointer ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current read position!"; errorText_ = errorStream_.str(); MUTEX_UNLOCK( &stream_.mutex ); error( RtAudioError::SYSTEM_ERROR ); return; } if ( safeReadPointer < (DWORD)nextReadPointer ) safeReadPointer += dsBufferSize; // unwrap offset DWORD endRead = nextReadPointer + bufferBytes; // Handling depends on whether we are INPUT or DUPLEX. // If we're in INPUT mode then waiting is a good thing. If we're in DUPLEX mode, // then a wait here will drag the write pointers into the forbidden zone. // // In DUPLEX mode, rather than wait, we will back off the read pointer until // it's in a safe position. This causes dropouts, but it seems to be the only // practical way to sync up the read and write pointers reliably, given the // the very complex relationship between phase and increment of the read and write // pointers. // // In order to minimize audible dropouts in DUPLEX mode, we will // provide a pre-roll period of 0.5 seconds in which we return // zeros from the read buffer while the pointers sync up. if ( stream_.mode == DUPLEX ) { if ( safeReadPointer < endRead ) { if ( duplexPrerollBytes <= 0 ) { // Pre-roll time over. Be more agressive. int adjustment = endRead-safeReadPointer; handle->xrun[1] = true; // Two cases: // - large adjustments: we've probably run out of CPU cycles, so just resync exactly, // and perform fine adjustments later. // - small adjustments: back off by twice as much. if ( adjustment >= 2*bufferBytes ) nextReadPointer = safeReadPointer-2*bufferBytes; else nextReadPointer = safeReadPointer-bufferBytes-adjustment; if ( nextReadPointer < 0 ) nextReadPointer += dsBufferSize; } else { // In pre=roll time. Just do it. nextReadPointer = safeReadPointer - bufferBytes; while ( nextReadPointer < 0 ) nextReadPointer += dsBufferSize; } endRead = nextReadPointer + bufferBytes; } } else { // mode == INPUT while ( safeReadPointer < endRead && stream_.callbackInfo.isRunning ) { // See comments for playback. double millis = (endRead - safeReadPointer) * 1000.0; millis /= ( formatBytes(stream_.deviceFormat[1]) * stream_.nDeviceChannels[1] * stream_.sampleRate); if ( millis < 1.0 ) millis = 1.0; Sleep( (DWORD) millis ); // Wake up and find out where we are now. result = dsBuffer->GetCurrentPosition( ¤tReadPointer, &safeReadPointer ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current read position!"; errorText_ = errorStream_.str(); MUTEX_UNLOCK( &stream_.mutex ); error( RtAudioError::SYSTEM_ERROR ); return; } if ( safeReadPointer < (DWORD)nextReadPointer ) safeReadPointer += dsBufferSize; // unwrap offset } } // Lock free space in the buffer result = dsBuffer->Lock( nextReadPointer, bufferBytes, &buffer1, &bufferSize1, &buffer2, &bufferSize2, 0 ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") locking capture buffer!"; errorText_ = errorStream_.str(); MUTEX_UNLOCK( &stream_.mutex ); error( RtAudioError::SYSTEM_ERROR ); return; } if ( duplexPrerollBytes <= 0 ) { // Copy our buffer into the DS buffer CopyMemory( buffer, buffer1, bufferSize1 ); if ( buffer2 != NULL ) CopyMemory( buffer+bufferSize1, buffer2, bufferSize2 ); } else { memset( buffer, 0, bufferSize1 ); if ( buffer2 != NULL ) memset( buffer + bufferSize1, 0, bufferSize2 ); duplexPrerollBytes -= bufferSize1 + bufferSize2; } // Update our buffer offset and unlock sound buffer nextReadPointer = ( nextReadPointer + bufferSize1 + bufferSize2 ) % dsBufferSize; dsBuffer->Unlock( buffer1, bufferSize1, buffer2, bufferSize2 ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") unlocking capture buffer!"; errorText_ = errorStream_.str(); MUTEX_UNLOCK( &stream_.mutex ); error( RtAudioError::SYSTEM_ERROR ); return; } handle->bufferPointer[1] = nextReadPointer; // No byte swapping necessary in DirectSound implementation. // If necessary, convert 8-bit data from unsigned to signed. if ( stream_.deviceFormat[1] == RTAUDIO_SINT8 ) for ( int j=0; jobject; bool* isRunning = &info->isRunning; while ( *isRunning == true ) { object->callbackEvent(); } _endthreadex( 0 ); return 0; } static BOOL CALLBACK deviceQueryCallback( LPGUID lpguid, LPCTSTR description, LPCTSTR /*module*/, LPVOID lpContext ) { struct DsProbeData& probeInfo = *(struct DsProbeData*) lpContext; std::vector& dsDevices = *probeInfo.dsDevices; HRESULT hr; bool validDevice = false; if ( probeInfo.isInput == true ) { DSCCAPS caps; LPDIRECTSOUNDCAPTURE object; hr = DirectSoundCaptureCreate( lpguid, &object, NULL ); if ( hr != DS_OK ) return TRUE; caps.dwSize = sizeof(caps); hr = object->GetCaps( &caps ); if ( hr == DS_OK ) { if ( caps.dwChannels > 0 && caps.dwFormats > 0 ) validDevice = true; } object->Release(); } else { DSCAPS caps; LPDIRECTSOUND object; hr = DirectSoundCreate( lpguid, &object, NULL ); if ( hr != DS_OK ) return TRUE; caps.dwSize = sizeof(caps); hr = object->GetCaps( &caps ); if ( hr == DS_OK ) { if ( caps.dwFlags & DSCAPS_PRIMARYMONO || caps.dwFlags & DSCAPS_PRIMARYSTEREO ) validDevice = true; } object->Release(); } // If good device, then save its name and guid. std::string name = convertCharPointerToStdString( description ); //if ( name == "Primary Sound Driver" || name == "Primary Sound Capture Driver" ) if ( lpguid == NULL ) name = "Default Device"; if ( validDevice ) { for ( unsigned int i=0; i #include // A structure to hold various information related to the ALSA API // implementation. struct AlsaHandle { snd_pcm_t *handles[2]; bool synchronized; bool xrun[2]; pthread_cond_t runnable_cv; bool runnable; AlsaHandle() :synchronized(false), runnable(false) { xrun[0] = false; xrun[1] = false; } }; static void *alsaCallbackHandler( void * ptr ); RtApiAlsa :: RtApiAlsa() { // Nothing to do here. } RtApiAlsa :: ~RtApiAlsa() { if ( stream_.state != STREAM_CLOSED ) closeStream(); } unsigned int RtApiAlsa :: getDeviceCount( void ) { unsigned nDevices = 0; int result, subdevice, card; char name[64]; snd_ctl_t *handle; // Count cards and devices card = -1; snd_card_next( &card ); while ( card >= 0 ) { sprintf( name, "hw:%d", card ); result = snd_ctl_open( &handle, name, 0 ); if ( result < 0 ) { errorStream_ << "RtApiAlsa::getDeviceCount: control open, card = " << card << ", " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); goto nextcard; } subdevice = -1; while( 1 ) { result = snd_ctl_pcm_next_device( handle, &subdevice ); if ( result < 0 ) { errorStream_ << "RtApiAlsa::getDeviceCount: control next device, card = " << card << ", " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); break; } if ( subdevice < 0 ) break; nDevices++; } nextcard: snd_ctl_close( handle ); snd_card_next( &card ); } result = snd_ctl_open( &handle, "default", 0 ); if (result == 0) { nDevices++; snd_ctl_close( handle ); } return nDevices; } RtAudio::DeviceInfo RtApiAlsa :: getDeviceInfo( unsigned int device ) { RtAudio::DeviceInfo info; info.probed = false; unsigned nDevices = 0; int result, subdevice, card; char name[64]; snd_ctl_t *chandle; // Count cards and devices card = -1; subdevice = -1; snd_card_next( &card ); while ( card >= 0 ) { sprintf( name, "hw:%d", card ); result = snd_ctl_open( &chandle, name, SND_CTL_NONBLOCK ); if ( result < 0 ) { errorStream_ << "RtApiAlsa::getDeviceInfo: control open, card = " << card << ", " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); goto nextcard; } subdevice = -1; while( 1 ) { result = snd_ctl_pcm_next_device( chandle, &subdevice ); if ( result < 0 ) { errorStream_ << "RtApiAlsa::getDeviceInfo: control next device, card = " << card << ", " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); break; } if ( subdevice < 0 ) break; if ( nDevices == device ) { sprintf( name, "hw:%d,%d", card, subdevice ); goto foundDevice; } nDevices++; } nextcard: snd_ctl_close( chandle ); snd_card_next( &card ); } result = snd_ctl_open( &chandle, "default", SND_CTL_NONBLOCK ); if ( result == 0 ) { if ( nDevices == device ) { strcpy( name, "default" ); goto foundDevice; } nDevices++; } if ( nDevices == 0 ) { errorText_ = "RtApiAlsa::getDeviceInfo: no devices found!"; error( RtAudioError::INVALID_USE ); return info; } if ( device >= nDevices ) { errorText_ = "RtApiAlsa::getDeviceInfo: device ID is invalid!"; error( RtAudioError::INVALID_USE ); return info; } foundDevice: // If a stream is already open, we cannot probe the stream devices. // Thus, use the saved results. if ( stream_.state != STREAM_CLOSED && ( stream_.device[0] == device || stream_.device[1] == device ) ) { snd_ctl_close( chandle ); if ( device >= devices_.size() ) { errorText_ = "RtApiAlsa::getDeviceInfo: device ID was not present before stream was opened."; error( RtAudioError::WARNING ); return info; } return devices_[ device ]; } int openMode = SND_PCM_ASYNC; snd_pcm_stream_t stream; snd_pcm_info_t *pcminfo; snd_pcm_info_alloca( &pcminfo ); snd_pcm_t *phandle; snd_pcm_hw_params_t *params; snd_pcm_hw_params_alloca( ¶ms ); // First try for playback unless default device (which has subdev -1) stream = SND_PCM_STREAM_PLAYBACK; snd_pcm_info_set_stream( pcminfo, stream ); if ( subdevice != -1 ) { snd_pcm_info_set_device( pcminfo, subdevice ); snd_pcm_info_set_subdevice( pcminfo, 0 ); result = snd_ctl_pcm_info( chandle, pcminfo ); if ( result < 0 ) { // Device probably doesn't support playback. goto captureProbe; } } result = snd_pcm_open( &phandle, name, stream, openMode | SND_PCM_NONBLOCK ); if ( result < 0 ) { errorStream_ << "RtApiAlsa::getDeviceInfo: snd_pcm_open error for device (" << name << "), " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); goto captureProbe; } // The device is open ... fill the parameter structure. result = snd_pcm_hw_params_any( phandle, params ); if ( result < 0 ) { snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::getDeviceInfo: snd_pcm_hw_params error for device (" << name << "), " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); goto captureProbe; } // Get output channel information. unsigned int value; result = snd_pcm_hw_params_get_channels_max( params, &value ); if ( result < 0 ) { snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::getDeviceInfo: error getting device (" << name << ") output channels, " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); goto captureProbe; } info.outputChannels = value; snd_pcm_close( phandle ); captureProbe: stream = SND_PCM_STREAM_CAPTURE; snd_pcm_info_set_stream( pcminfo, stream ); // Now try for capture unless default device (with subdev = -1) if ( subdevice != -1 ) { result = snd_ctl_pcm_info( chandle, pcminfo ); snd_ctl_close( chandle ); if ( result < 0 ) { // Device probably doesn't support capture. if ( info.outputChannels == 0 ) return info; goto probeParameters; } } else snd_ctl_close( chandle ); result = snd_pcm_open( &phandle, name, stream, openMode | SND_PCM_NONBLOCK); if ( result < 0 ) { errorStream_ << "RtApiAlsa::getDeviceInfo: snd_pcm_open error for device (" << name << "), " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); if ( info.outputChannels == 0 ) return info; goto probeParameters; } // The device is open ... fill the parameter structure. result = snd_pcm_hw_params_any( phandle, params ); if ( result < 0 ) { snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::getDeviceInfo: snd_pcm_hw_params error for device (" << name << "), " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); if ( info.outputChannels == 0 ) return info; goto probeParameters; } result = snd_pcm_hw_params_get_channels_max( params, &value ); if ( result < 0 ) { snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::getDeviceInfo: error getting device (" << name << ") input channels, " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); if ( info.outputChannels == 0 ) return info; goto probeParameters; } info.inputChannels = value; snd_pcm_close( phandle ); // If device opens for both playback and capture, we determine the channels. if ( info.outputChannels > 0 && info.inputChannels > 0 ) info.duplexChannels = (info.outputChannels > info.inputChannels) ? info.inputChannels : info.outputChannels; // ALSA doesn't provide default devices so we'll use the first available one. if ( device == 0 && info.outputChannels > 0 ) info.isDefaultOutput = true; if ( device == 0 && info.inputChannels > 0 ) info.isDefaultInput = true; probeParameters: // At this point, we just need to figure out the supported data // formats and sample rates. We'll proceed by opening the device in // the direction with the maximum number of channels, or playback if // they are equal. This might limit our sample rate options, but so // be it. if ( info.outputChannels >= info.inputChannels ) stream = SND_PCM_STREAM_PLAYBACK; else stream = SND_PCM_STREAM_CAPTURE; snd_pcm_info_set_stream( pcminfo, stream ); result = snd_pcm_open( &phandle, name, stream, openMode | SND_PCM_NONBLOCK); if ( result < 0 ) { errorStream_ << "RtApiAlsa::getDeviceInfo: snd_pcm_open error for device (" << name << "), " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); return info; } // The device is open ... fill the parameter structure. result = snd_pcm_hw_params_any( phandle, params ); if ( result < 0 ) { snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::getDeviceInfo: snd_pcm_hw_params error for device (" << name << "), " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); return info; } // Test our discrete set of sample rate values. info.sampleRates.clear(); for ( unsigned int i=0; i info.preferredSampleRate ) ) info.preferredSampleRate = SAMPLE_RATES[i]; } } if ( info.sampleRates.size() == 0 ) { snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::getDeviceInfo: no supported sample rates found for device (" << name << ")."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); return info; } // Probe the supported data formats ... we don't care about endian-ness just yet snd_pcm_format_t format; info.nativeFormats = 0; format = SND_PCM_FORMAT_S8; if ( snd_pcm_hw_params_test_format( phandle, params, format ) == 0 ) info.nativeFormats |= RTAUDIO_SINT8; format = SND_PCM_FORMAT_S16; if ( snd_pcm_hw_params_test_format( phandle, params, format ) == 0 ) info.nativeFormats |= RTAUDIO_SINT16; format = SND_PCM_FORMAT_S24; if ( snd_pcm_hw_params_test_format( phandle, params, format ) == 0 ) info.nativeFormats |= RTAUDIO_SINT24; format = SND_PCM_FORMAT_S32; if ( snd_pcm_hw_params_test_format( phandle, params, format ) == 0 ) info.nativeFormats |= RTAUDIO_SINT32; format = SND_PCM_FORMAT_FLOAT; if ( snd_pcm_hw_params_test_format( phandle, params, format ) == 0 ) info.nativeFormats |= RTAUDIO_FLOAT32; format = SND_PCM_FORMAT_FLOAT64; if ( snd_pcm_hw_params_test_format( phandle, params, format ) == 0 ) info.nativeFormats |= RTAUDIO_FLOAT64; // Check that we have at least one supported format if ( info.nativeFormats == 0 ) { snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::getDeviceInfo: pcm device (" << name << ") data format not supported by RtAudio."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); return info; } // Get the device name char *cardname; result = snd_card_get_name( card, &cardname ); if ( result >= 0 ) { sprintf( name, "hw:%s,%d", cardname, subdevice ); free( cardname ); } info.name = name; // That's all ... close the device and return snd_pcm_close( phandle ); info.probed = true; return info; } void RtApiAlsa :: saveDeviceInfo( void ) { devices_.clear(); unsigned int nDevices = getDeviceCount(); devices_.resize( nDevices ); for ( unsigned int i=0; iflags & RTAUDIO_ALSA_USE_DEFAULT ) snprintf(name, sizeof(name), "%s", "default"); else { // Count cards and devices card = -1; snd_card_next( &card ); while ( card >= 0 ) { sprintf( name, "hw:%d", card ); result = snd_ctl_open( &chandle, name, SND_CTL_NONBLOCK ); if ( result < 0 ) { errorStream_ << "RtApiAlsa::probeDeviceOpen: control open, card = " << card << ", " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); return FAILURE; } subdevice = -1; while( 1 ) { result = snd_ctl_pcm_next_device( chandle, &subdevice ); if ( result < 0 ) break; if ( subdevice < 0 ) break; if ( nDevices == device ) { sprintf( name, "hw:%d,%d", card, subdevice ); snd_ctl_close( chandle ); goto foundDevice; } nDevices++; } snd_ctl_close( chandle ); snd_card_next( &card ); } result = snd_ctl_open( &chandle, "default", SND_CTL_NONBLOCK ); if ( result == 0 ) { if ( nDevices == device ) { strcpy( name, "default" ); goto foundDevice; } nDevices++; } if ( nDevices == 0 ) { // This should not happen because a check is made before this function is called. errorText_ = "RtApiAlsa::probeDeviceOpen: no devices found!"; return FAILURE; } if ( device >= nDevices ) { // This should not happen because a check is made before this function is called. errorText_ = "RtApiAlsa::probeDeviceOpen: device ID is invalid!"; return FAILURE; } } foundDevice: // The getDeviceInfo() function will not work for a device that is // already open. Thus, we'll probe the system before opening a // stream and save the results for use by getDeviceInfo(). if ( mode == OUTPUT || ( mode == INPUT && stream_.mode != OUTPUT ) ) // only do once this->saveDeviceInfo(); snd_pcm_stream_t stream; if ( mode == OUTPUT ) stream = SND_PCM_STREAM_PLAYBACK; else stream = SND_PCM_STREAM_CAPTURE; snd_pcm_t *phandle; int openMode = SND_PCM_ASYNC; result = snd_pcm_open( &phandle, name, stream, openMode ); if ( result < 0 ) { if ( mode == OUTPUT ) errorStream_ << "RtApiAlsa::probeDeviceOpen: pcm device (" << name << ") won't open for output."; else errorStream_ << "RtApiAlsa::probeDeviceOpen: pcm device (" << name << ") won't open for input."; errorText_ = errorStream_.str(); return FAILURE; } // Fill the parameter structure. snd_pcm_hw_params_t *hw_params; snd_pcm_hw_params_alloca( &hw_params ); result = snd_pcm_hw_params_any( phandle, hw_params ); if ( result < 0 ) { snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::probeDeviceOpen: error getting pcm device (" << name << ") parameters, " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); return FAILURE; } #if defined(__RTAUDIO_DEBUG__) fprintf( stderr, "\nRtApiAlsa: dump hardware params just after device open:\n\n" ); snd_pcm_hw_params_dump( hw_params, out ); #endif // Set access ... check user preference. if ( options && options->flags & RTAUDIO_NONINTERLEAVED ) { stream_.userInterleaved = false; result = snd_pcm_hw_params_set_access( phandle, hw_params, SND_PCM_ACCESS_RW_NONINTERLEAVED ); if ( result < 0 ) { result = snd_pcm_hw_params_set_access( phandle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED ); stream_.deviceInterleaved[mode] = true; } else stream_.deviceInterleaved[mode] = false; } else { stream_.userInterleaved = true; result = snd_pcm_hw_params_set_access( phandle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED ); if ( result < 0 ) { result = snd_pcm_hw_params_set_access( phandle, hw_params, SND_PCM_ACCESS_RW_NONINTERLEAVED ); stream_.deviceInterleaved[mode] = false; } else stream_.deviceInterleaved[mode] = true; } if ( result < 0 ) { snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::probeDeviceOpen: error setting pcm device (" << name << ") access, " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); return FAILURE; } // Determine how to set the device format. stream_.userFormat = format; snd_pcm_format_t deviceFormat = SND_PCM_FORMAT_UNKNOWN; if ( format == RTAUDIO_SINT8 ) deviceFormat = SND_PCM_FORMAT_S8; else if ( format == RTAUDIO_SINT16 ) deviceFormat = SND_PCM_FORMAT_S16; else if ( format == RTAUDIO_SINT24 ) deviceFormat = SND_PCM_FORMAT_S24; else if ( format == RTAUDIO_SINT32 ) deviceFormat = SND_PCM_FORMAT_S32; else if ( format == RTAUDIO_FLOAT32 ) deviceFormat = SND_PCM_FORMAT_FLOAT; else if ( format == RTAUDIO_FLOAT64 ) deviceFormat = SND_PCM_FORMAT_FLOAT64; if ( snd_pcm_hw_params_test_format(phandle, hw_params, deviceFormat) == 0) { stream_.deviceFormat[mode] = format; goto setFormat; } // The user requested format is not natively supported by the device. deviceFormat = SND_PCM_FORMAT_FLOAT64; if ( snd_pcm_hw_params_test_format( phandle, hw_params, deviceFormat ) == 0 ) { stream_.deviceFormat[mode] = RTAUDIO_FLOAT64; goto setFormat; } deviceFormat = SND_PCM_FORMAT_FLOAT; if ( snd_pcm_hw_params_test_format(phandle, hw_params, deviceFormat ) == 0 ) { stream_.deviceFormat[mode] = RTAUDIO_FLOAT32; goto setFormat; } deviceFormat = SND_PCM_FORMAT_S32; if ( snd_pcm_hw_params_test_format(phandle, hw_params, deviceFormat ) == 0 ) { stream_.deviceFormat[mode] = RTAUDIO_SINT32; goto setFormat; } deviceFormat = SND_PCM_FORMAT_S24; if ( snd_pcm_hw_params_test_format(phandle, hw_params, deviceFormat ) == 0 ) { stream_.deviceFormat[mode] = RTAUDIO_SINT24; goto setFormat; } deviceFormat = SND_PCM_FORMAT_S16; if ( snd_pcm_hw_params_test_format(phandle, hw_params, deviceFormat ) == 0 ) { stream_.deviceFormat[mode] = RTAUDIO_SINT16; goto setFormat; } deviceFormat = SND_PCM_FORMAT_S8; if ( snd_pcm_hw_params_test_format(phandle, hw_params, deviceFormat ) == 0 ) { stream_.deviceFormat[mode] = RTAUDIO_SINT8; goto setFormat; } // If we get here, no supported format was found. snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::probeDeviceOpen: pcm device " << device << " data format not supported by RtAudio."; errorText_ = errorStream_.str(); return FAILURE; setFormat: result = snd_pcm_hw_params_set_format( phandle, hw_params, deviceFormat ); if ( result < 0 ) { snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::probeDeviceOpen: error setting pcm device (" << name << ") data format, " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); return FAILURE; } // Determine whether byte-swaping is necessary. stream_.doByteSwap[mode] = false; if ( deviceFormat != SND_PCM_FORMAT_S8 ) { result = snd_pcm_format_cpu_endian( deviceFormat ); if ( result == 0 ) stream_.doByteSwap[mode] = true; else if (result < 0) { snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::probeDeviceOpen: error getting pcm device (" << name << ") endian-ness, " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); return FAILURE; } } // Set the sample rate. result = snd_pcm_hw_params_set_rate_near( phandle, hw_params, (unsigned int*) &sampleRate, 0 ); if ( result < 0 ) { snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::probeDeviceOpen: error setting sample rate on device (" << name << "), " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); return FAILURE; } // Determine the number of channels for this device. We support a possible // minimum device channel number > than the value requested by the user. stream_.nUserChannels[mode] = channels; unsigned int value; result = snd_pcm_hw_params_get_channels_max( hw_params, &value ); unsigned int deviceChannels = value; if ( result < 0 || deviceChannels < channels + firstChannel ) { snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::probeDeviceOpen: requested channel parameters not supported by device (" << name << "), " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); return FAILURE; } result = snd_pcm_hw_params_get_channels_min( hw_params, &value ); if ( result < 0 ) { snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::probeDeviceOpen: error getting minimum channels for device (" << name << "), " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); return FAILURE; } deviceChannels = value; if ( deviceChannels < channels + firstChannel ) deviceChannels = channels + firstChannel; stream_.nDeviceChannels[mode] = deviceChannels; // Set the device channels. result = snd_pcm_hw_params_set_channels( phandle, hw_params, deviceChannels ); if ( result < 0 ) { snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::probeDeviceOpen: error setting channels for device (" << name << "), " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); return FAILURE; } // Set the buffer (or period) size. int dir = 0; snd_pcm_uframes_t periodSize = *bufferSize; result = snd_pcm_hw_params_set_period_size_near( phandle, hw_params, &periodSize, &dir ); if ( result < 0 ) { snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::probeDeviceOpen: error setting period size for device (" << name << "), " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); return FAILURE; } *bufferSize = periodSize; // Set the buffer number, which in ALSA is referred to as the "period". unsigned int periods = 0; if ( options && options->flags & RTAUDIO_MINIMIZE_LATENCY ) periods = 2; if ( options && options->numberOfBuffers > 0 ) periods = options->numberOfBuffers; if ( periods < 2 ) periods = 4; // a fairly safe default value result = snd_pcm_hw_params_set_periods_near( phandle, hw_params, &periods, &dir ); if ( result < 0 ) { snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::probeDeviceOpen: error setting periods for device (" << name << "), " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); return FAILURE; } // If attempting to setup a duplex stream, the bufferSize parameter // MUST be the same in both directions! if ( stream_.mode == OUTPUT && mode == INPUT && *bufferSize != stream_.bufferSize ) { snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::probeDeviceOpen: system error setting buffer size for duplex stream on device (" << name << ")."; errorText_ = errorStream_.str(); return FAILURE; } stream_.bufferSize = *bufferSize; // Install the hardware configuration result = snd_pcm_hw_params( phandle, hw_params ); if ( result < 0 ) { snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::probeDeviceOpen: error installing hardware configuration on device (" << name << "), " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); return FAILURE; } #if defined(__RTAUDIO_DEBUG__) fprintf(stderr, "\nRtApiAlsa: dump hardware params after installation:\n\n"); snd_pcm_hw_params_dump( hw_params, out ); #endif // Set the software configuration to fill buffers with zeros and prevent device stopping on xruns. snd_pcm_sw_params_t *sw_params = NULL; snd_pcm_sw_params_alloca( &sw_params ); snd_pcm_sw_params_current( phandle, sw_params ); snd_pcm_sw_params_set_start_threshold( phandle, sw_params, *bufferSize ); snd_pcm_sw_params_set_stop_threshold( phandle, sw_params, ULONG_MAX ); snd_pcm_sw_params_set_silence_threshold( phandle, sw_params, 0 ); // The following two settings were suggested by Theo Veenker //snd_pcm_sw_params_set_avail_min( phandle, sw_params, *bufferSize ); //snd_pcm_sw_params_set_xfer_align( phandle, sw_params, 1 ); // here are two options for a fix //snd_pcm_sw_params_set_silence_size( phandle, sw_params, ULONG_MAX ); snd_pcm_uframes_t val; snd_pcm_sw_params_get_boundary( sw_params, &val ); snd_pcm_sw_params_set_silence_size( phandle, sw_params, val ); result = snd_pcm_sw_params( phandle, sw_params ); if ( result < 0 ) { snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::probeDeviceOpen: error installing software configuration on device (" << name << "), " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); return FAILURE; } #if defined(__RTAUDIO_DEBUG__) fprintf(stderr, "\nRtApiAlsa: dump software params after installation:\n\n"); snd_pcm_sw_params_dump( sw_params, out ); #endif // Set flags for buffer conversion stream_.doConvertBuffer[mode] = false; if ( stream_.userFormat != stream_.deviceFormat[mode] ) stream_.doConvertBuffer[mode] = true; if ( stream_.nUserChannels[mode] < stream_.nDeviceChannels[mode] ) stream_.doConvertBuffer[mode] = true; if ( stream_.userInterleaved != stream_.deviceInterleaved[mode] && stream_.nUserChannels[mode] > 1 ) stream_.doConvertBuffer[mode] = true; // Allocate the ApiHandle if necessary and then save. AlsaHandle *apiInfo = 0; if ( stream_.apiHandle == 0 ) { try { apiInfo = (AlsaHandle *) new AlsaHandle; } catch ( std::bad_alloc& ) { errorText_ = "RtApiAlsa::probeDeviceOpen: error allocating AlsaHandle memory."; goto error; } if ( pthread_cond_init( &apiInfo->runnable_cv, NULL ) ) { errorText_ = "RtApiAlsa::probeDeviceOpen: error initializing pthread condition variable."; goto error; } stream_.apiHandle = (void *) apiInfo; apiInfo->handles[0] = 0; apiInfo->handles[1] = 0; } else { apiInfo = (AlsaHandle *) stream_.apiHandle; } apiInfo->handles[mode] = phandle; phandle = 0; // Allocate necessary internal buffers. unsigned long bufferBytes; bufferBytes = stream_.nUserChannels[mode] * *bufferSize * formatBytes( stream_.userFormat ); stream_.userBuffer[mode] = (char *) calloc( bufferBytes, 1 ); if ( stream_.userBuffer[mode] == NULL ) { errorText_ = "RtApiAlsa::probeDeviceOpen: error allocating user buffer memory."; goto error; } if ( stream_.doConvertBuffer[mode] ) { bool makeBuffer = true; bufferBytes = stream_.nDeviceChannels[mode] * formatBytes( stream_.deviceFormat[mode] ); if ( mode == INPUT ) { if ( stream_.mode == OUTPUT && stream_.deviceBuffer ) { unsigned long bytesOut = stream_.nDeviceChannels[0] * formatBytes( stream_.deviceFormat[0] ); if ( bufferBytes <= bytesOut ) makeBuffer = false; } } if ( makeBuffer ) { bufferBytes *= *bufferSize; if ( stream_.deviceBuffer ) free( stream_.deviceBuffer ); stream_.deviceBuffer = (char *) calloc( bufferBytes, 1 ); if ( stream_.deviceBuffer == NULL ) { errorText_ = "RtApiAlsa::probeDeviceOpen: error allocating device buffer memory."; goto error; } } } stream_.sampleRate = sampleRate; stream_.nBuffers = periods; stream_.device[mode] = device; stream_.state = STREAM_STOPPED; // Setup the buffer conversion information structure. if ( stream_.doConvertBuffer[mode] ) setConvertInfo( mode, firstChannel ); // Setup thread if necessary. if ( stream_.mode == OUTPUT && mode == INPUT ) { // We had already set up an output stream. stream_.mode = DUPLEX; // Link the streams if possible. apiInfo->synchronized = false; if ( snd_pcm_link( apiInfo->handles[0], apiInfo->handles[1] ) == 0 ) apiInfo->synchronized = true; else { errorText_ = "RtApiAlsa::probeDeviceOpen: unable to synchronize input and output devices."; error( RtAudioError::WARNING ); } } else { stream_.mode = mode; // Setup callback thread. stream_.callbackInfo.object = (void *) this; // Set the thread attributes for joinable and realtime scheduling // priority (optional). The higher priority will only take affect // if the program is run as root or suid. Note, under Linux // processes with CAP_SYS_NICE privilege, a user can change // scheduling policy and priority (thus need not be root). See // POSIX "capabilities". pthread_attr_t attr; pthread_attr_init( &attr ); pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_JOINABLE ); #ifdef SCHED_RR // Undefined with some OSes (eg: NetBSD 1.6.x with GNU Pthread) if ( options && options->flags & RTAUDIO_SCHEDULE_REALTIME ) { // We previously attempted to increase the audio callback priority // to SCHED_RR here via the attributes. However, while no errors // were reported in doing so, it did not work. So, now this is // done in the alsaCallbackHandler function. stream_.callbackInfo.doRealtime = true; int priority = options->priority; int min = sched_get_priority_min( SCHED_RR ); int max = sched_get_priority_max( SCHED_RR ); if ( priority < min ) priority = min; else if ( priority > max ) priority = max; stream_.callbackInfo.priority = priority; } #endif stream_.callbackInfo.isRunning = true; result = pthread_create( &stream_.callbackInfo.thread, &attr, alsaCallbackHandler, &stream_.callbackInfo ); pthread_attr_destroy( &attr ); if ( result ) { stream_.callbackInfo.isRunning = false; errorText_ = "RtApiAlsa::error creating callback thread!"; goto error; } } return SUCCESS; error: if ( apiInfo ) { pthread_cond_destroy( &apiInfo->runnable_cv ); if ( apiInfo->handles[0] ) snd_pcm_close( apiInfo->handles[0] ); if ( apiInfo->handles[1] ) snd_pcm_close( apiInfo->handles[1] ); delete apiInfo; stream_.apiHandle = 0; } if ( phandle) snd_pcm_close( phandle ); for ( int i=0; i<2; i++ ) { if ( stream_.userBuffer[i] ) { free( stream_.userBuffer[i] ); stream_.userBuffer[i] = 0; } } if ( stream_.deviceBuffer ) { free( stream_.deviceBuffer ); stream_.deviceBuffer = 0; } stream_.state = STREAM_CLOSED; return FAILURE; } void RtApiAlsa :: closeStream() { if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiAlsa::closeStream(): no open stream to close!"; error( RtAudioError::WARNING ); return; } AlsaHandle *apiInfo = (AlsaHandle *) stream_.apiHandle; stream_.callbackInfo.isRunning = false; MUTEX_LOCK( &stream_.mutex ); if ( stream_.state == STREAM_STOPPED ) { apiInfo->runnable = true; pthread_cond_signal( &apiInfo->runnable_cv ); } MUTEX_UNLOCK( &stream_.mutex ); pthread_join( stream_.callbackInfo.thread, NULL ); if ( stream_.state == STREAM_RUNNING ) { stream_.state = STREAM_STOPPED; if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) snd_pcm_drop( apiInfo->handles[0] ); if ( stream_.mode == INPUT || stream_.mode == DUPLEX ) snd_pcm_drop( apiInfo->handles[1] ); } if ( apiInfo ) { pthread_cond_destroy( &apiInfo->runnable_cv ); if ( apiInfo->handles[0] ) snd_pcm_close( apiInfo->handles[0] ); if ( apiInfo->handles[1] ) snd_pcm_close( apiInfo->handles[1] ); delete apiInfo; stream_.apiHandle = 0; } for ( int i=0; i<2; i++ ) { if ( stream_.userBuffer[i] ) { free( stream_.userBuffer[i] ); stream_.userBuffer[i] = 0; } } if ( stream_.deviceBuffer ) { free( stream_.deviceBuffer ); stream_.deviceBuffer = 0; } stream_.mode = UNINITIALIZED; stream_.state = STREAM_CLOSED; } void RtApiAlsa :: startStream() { // This method calls snd_pcm_prepare if the device isn't already in that state. verifyStream(); if ( stream_.state == STREAM_RUNNING ) { errorText_ = "RtApiAlsa::startStream(): the stream is already running!"; error( RtAudioError::WARNING ); return; } MUTEX_LOCK( &stream_.mutex ); int result = 0; snd_pcm_state_t state; AlsaHandle *apiInfo = (AlsaHandle *) stream_.apiHandle; snd_pcm_t **handle = (snd_pcm_t **) apiInfo->handles; if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { state = snd_pcm_state( handle[0] ); if ( state != SND_PCM_STATE_PREPARED ) { result = snd_pcm_prepare( handle[0] ); if ( result < 0 ) { errorStream_ << "RtApiAlsa::startStream: error preparing output pcm device, " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); goto unlock; } } } if ( ( stream_.mode == INPUT || stream_.mode == DUPLEX ) && !apiInfo->synchronized ) { result = snd_pcm_drop(handle[1]); // fix to remove stale data received since device has been open state = snd_pcm_state( handle[1] ); if ( state != SND_PCM_STATE_PREPARED ) { result = snd_pcm_prepare( handle[1] ); if ( result < 0 ) { errorStream_ << "RtApiAlsa::startStream: error preparing input pcm device, " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); goto unlock; } } } stream_.state = STREAM_RUNNING; unlock: apiInfo->runnable = true; pthread_cond_signal( &apiInfo->runnable_cv ); MUTEX_UNLOCK( &stream_.mutex ); if ( result >= 0 ) return; error( RtAudioError::SYSTEM_ERROR ); } void RtApiAlsa :: stopStream() { verifyStream(); if ( stream_.state == STREAM_STOPPED ) { errorText_ = "RtApiAlsa::stopStream(): the stream is already stopped!"; error( RtAudioError::WARNING ); return; } stream_.state = STREAM_STOPPED; MUTEX_LOCK( &stream_.mutex ); int result = 0; AlsaHandle *apiInfo = (AlsaHandle *) stream_.apiHandle; snd_pcm_t **handle = (snd_pcm_t **) apiInfo->handles; if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { if ( apiInfo->synchronized ) result = snd_pcm_drop( handle[0] ); else result = snd_pcm_drain( handle[0] ); if ( result < 0 ) { errorStream_ << "RtApiAlsa::stopStream: error draining output pcm device, " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); goto unlock; } } if ( ( stream_.mode == INPUT || stream_.mode == DUPLEX ) && !apiInfo->synchronized ) { result = snd_pcm_drop( handle[1] ); if ( result < 0 ) { errorStream_ << "RtApiAlsa::stopStream: error stopping input pcm device, " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); goto unlock; } } unlock: apiInfo->runnable = false; // fixes high CPU usage when stopped MUTEX_UNLOCK( &stream_.mutex ); if ( result >= 0 ) return; error( RtAudioError::SYSTEM_ERROR ); } void RtApiAlsa :: abortStream() { verifyStream(); if ( stream_.state == STREAM_STOPPED ) { errorText_ = "RtApiAlsa::abortStream(): the stream is already stopped!"; error( RtAudioError::WARNING ); return; } stream_.state = STREAM_STOPPED; MUTEX_LOCK( &stream_.mutex ); int result = 0; AlsaHandle *apiInfo = (AlsaHandle *) stream_.apiHandle; snd_pcm_t **handle = (snd_pcm_t **) apiInfo->handles; if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { result = snd_pcm_drop( handle[0] ); if ( result < 0 ) { errorStream_ << "RtApiAlsa::abortStream: error aborting output pcm device, " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); goto unlock; } } if ( ( stream_.mode == INPUT || stream_.mode == DUPLEX ) && !apiInfo->synchronized ) { result = snd_pcm_drop( handle[1] ); if ( result < 0 ) { errorStream_ << "RtApiAlsa::abortStream: error aborting input pcm device, " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); goto unlock; } } unlock: apiInfo->runnable = false; // fixes high CPU usage when stopped MUTEX_UNLOCK( &stream_.mutex ); if ( result >= 0 ) return; error( RtAudioError::SYSTEM_ERROR ); } void RtApiAlsa :: callbackEvent() { AlsaHandle *apiInfo = (AlsaHandle *) stream_.apiHandle; if ( stream_.state == STREAM_STOPPED ) { MUTEX_LOCK( &stream_.mutex ); while ( !apiInfo->runnable ) pthread_cond_wait( &apiInfo->runnable_cv, &stream_.mutex ); if ( stream_.state != STREAM_RUNNING ) { MUTEX_UNLOCK( &stream_.mutex ); return; } MUTEX_UNLOCK( &stream_.mutex ); } if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiAlsa::callbackEvent(): the stream is closed ... this shouldn't happen!"; error( RtAudioError::WARNING ); return; } int doStopStream = 0; RtAudioCallback callback = (RtAudioCallback) stream_.callbackInfo.callback; double streamTime = getStreamTime(); RtAudioStreamStatus status = 0; if ( stream_.mode != INPUT && apiInfo->xrun[0] == true ) { status |= RTAUDIO_OUTPUT_UNDERFLOW; apiInfo->xrun[0] = false; } if ( stream_.mode != OUTPUT && apiInfo->xrun[1] == true ) { status |= RTAUDIO_INPUT_OVERFLOW; apiInfo->xrun[1] = false; } doStopStream = callback( stream_.userBuffer[0], stream_.userBuffer[1], stream_.bufferSize, streamTime, status, stream_.callbackInfo.userData ); if ( doStopStream == 2 ) { abortStream(); return; } MUTEX_LOCK( &stream_.mutex ); // The state might change while waiting on a mutex. if ( stream_.state == STREAM_STOPPED ) goto unlock; int result; char *buffer; int channels; snd_pcm_t **handle; snd_pcm_sframes_t frames; RtAudioFormat format; handle = (snd_pcm_t **) apiInfo->handles; if ( stream_.mode == INPUT || stream_.mode == DUPLEX ) { // Setup parameters. if ( stream_.doConvertBuffer[1] ) { buffer = stream_.deviceBuffer; channels = stream_.nDeviceChannels[1]; format = stream_.deviceFormat[1]; } else { buffer = stream_.userBuffer[1]; channels = stream_.nUserChannels[1]; format = stream_.userFormat; } // Read samples from device in interleaved/non-interleaved format. if ( stream_.deviceInterleaved[1] ) result = snd_pcm_readi( handle[1], buffer, stream_.bufferSize ); else { void *bufs[channels]; size_t offset = stream_.bufferSize * formatBytes( format ); for ( int i=0; ixrun[1] = true; result = snd_pcm_prepare( handle[1] ); if ( result < 0 ) { errorStream_ << "RtApiAlsa::callbackEvent: error preparing device after overrun, " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); } } else { errorStream_ << "RtApiAlsa::callbackEvent: error, current state is " << snd_pcm_state_name( state ) << ", " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); } } else { errorStream_ << "RtApiAlsa::callbackEvent: audio read error, " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); } error( RtAudioError::WARNING ); goto tryOutput; } // Do byte swapping if necessary. if ( stream_.doByteSwap[1] ) byteSwapBuffer( buffer, stream_.bufferSize * channels, format ); // Do buffer conversion if necessary. if ( stream_.doConvertBuffer[1] ) convertBuffer( stream_.userBuffer[1], stream_.deviceBuffer, stream_.convertInfo[1] ); // Check stream latency result = snd_pcm_delay( handle[1], &frames ); if ( result == 0 && frames > 0 ) stream_.latency[1] = frames; } tryOutput: if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { // Setup parameters and do buffer conversion if necessary. if ( stream_.doConvertBuffer[0] ) { buffer = stream_.deviceBuffer; convertBuffer( buffer, stream_.userBuffer[0], stream_.convertInfo[0] ); channels = stream_.nDeviceChannels[0]; format = stream_.deviceFormat[0]; } else { buffer = stream_.userBuffer[0]; channels = stream_.nUserChannels[0]; format = stream_.userFormat; } // Do byte swapping if necessary. if ( stream_.doByteSwap[0] ) byteSwapBuffer(buffer, stream_.bufferSize * channels, format); // Write samples to device in interleaved/non-interleaved format. if ( stream_.deviceInterleaved[0] ) result = snd_pcm_writei( handle[0], buffer, stream_.bufferSize ); else { void *bufs[channels]; size_t offset = stream_.bufferSize * formatBytes( format ); for ( int i=0; ixrun[0] = true; result = snd_pcm_prepare( handle[0] ); if ( result < 0 ) { errorStream_ << "RtApiAlsa::callbackEvent: error preparing device after underrun, " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); } else errorText_ = "RtApiAlsa::callbackEvent: audio write error, underrun."; } else { errorStream_ << "RtApiAlsa::callbackEvent: error, current state is " << snd_pcm_state_name( state ) << ", " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); } } else { errorStream_ << "RtApiAlsa::callbackEvent: audio write error, " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); } error( RtAudioError::WARNING ); goto unlock; } // Check stream latency result = snd_pcm_delay( handle[0], &frames ); if ( result == 0 && frames > 0 ) stream_.latency[0] = frames; } unlock: MUTEX_UNLOCK( &stream_.mutex ); RtApi::tickStreamTime(); if ( doStopStream == 1 ) this->stopStream(); } static void *alsaCallbackHandler( void *ptr ) { CallbackInfo *info = (CallbackInfo *) ptr; RtApiAlsa *object = (RtApiAlsa *) info->object; bool *isRunning = &info->isRunning; #ifdef SCHED_RR // Undefined with some OSes (eg: NetBSD 1.6.x with GNU Pthread) if ( info->doRealtime ) { pthread_t tID = pthread_self(); // ID of this thread sched_param prio = { info->priority }; // scheduling priority of thread pthread_setschedparam( tID, SCHED_RR, &prio ); } #endif while ( *isRunning == true ) { pthread_testcancel(); object->callbackEvent(); } pthread_exit( NULL ); } //******************** End of __LINUX_ALSA__ *********************// #endif #if defined(__LINUX_PULSE__) // Code written by Peter Meerwald, pmeerw@pmeerw.net // and Tristan Matthews. #include #include #include static const unsigned int SUPPORTED_SAMPLERATES[] = { 8000, 16000, 22050, 32000, 44100, 48000, 96000, 0}; struct rtaudio_pa_format_mapping_t { RtAudioFormat rtaudio_format; pa_sample_format_t pa_format; }; static const rtaudio_pa_format_mapping_t supported_sampleformats[] = { {RTAUDIO_SINT16, PA_SAMPLE_S16LE}, {RTAUDIO_SINT32, PA_SAMPLE_S32LE}, {RTAUDIO_FLOAT32, PA_SAMPLE_FLOAT32LE}, {0, PA_SAMPLE_INVALID}}; struct PulseAudioHandle { pa_simple *s_play; pa_simple *s_rec; pthread_t thread; pthread_cond_t runnable_cv; bool runnable; PulseAudioHandle() : s_play(0), s_rec(0), runnable(false) { } }; RtApiPulse::~RtApiPulse() { if ( stream_.state != STREAM_CLOSED ) closeStream(); } unsigned int RtApiPulse::getDeviceCount( void ) { return 1; } RtAudio::DeviceInfo RtApiPulse::getDeviceInfo( unsigned int /*device*/ ) { RtAudio::DeviceInfo info; info.probed = true; info.name = "PulseAudio"; info.outputChannels = 2; info.inputChannels = 2; info.duplexChannels = 2; info.isDefaultOutput = true; info.isDefaultInput = true; for ( const unsigned int *sr = SUPPORTED_SAMPLERATES; *sr; ++sr ) info.sampleRates.push_back( *sr ); info.preferredSampleRate = 48000; info.nativeFormats = RTAUDIO_SINT16 | RTAUDIO_SINT32 | RTAUDIO_FLOAT32; return info; } static void *pulseaudio_callback( void * user ) { CallbackInfo *cbi = static_cast( user ); RtApiPulse *context = static_cast( cbi->object ); volatile bool *isRunning = &cbi->isRunning; while ( *isRunning ) { pthread_testcancel(); context->callbackEvent(); } pthread_exit( NULL ); } void RtApiPulse::closeStream( void ) { PulseAudioHandle *pah = static_cast( stream_.apiHandle ); stream_.callbackInfo.isRunning = false; if ( pah ) { MUTEX_LOCK( &stream_.mutex ); if ( stream_.state == STREAM_STOPPED ) { pah->runnable = true; pthread_cond_signal( &pah->runnable_cv ); } MUTEX_UNLOCK( &stream_.mutex ); pthread_join( pah->thread, 0 ); if ( pah->s_play ) { pa_simple_flush( pah->s_play, NULL ); pa_simple_free( pah->s_play ); } if ( pah->s_rec ) pa_simple_free( pah->s_rec ); pthread_cond_destroy( &pah->runnable_cv ); delete pah; stream_.apiHandle = 0; } if ( stream_.userBuffer[0] ) { free( stream_.userBuffer[0] ); stream_.userBuffer[0] = 0; } if ( stream_.userBuffer[1] ) { free( stream_.userBuffer[1] ); stream_.userBuffer[1] = 0; } stream_.state = STREAM_CLOSED; stream_.mode = UNINITIALIZED; } void RtApiPulse::callbackEvent( void ) { PulseAudioHandle *pah = static_cast( stream_.apiHandle ); if ( stream_.state == STREAM_STOPPED ) { MUTEX_LOCK( &stream_.mutex ); while ( !pah->runnable ) pthread_cond_wait( &pah->runnable_cv, &stream_.mutex ); if ( stream_.state != STREAM_RUNNING ) { MUTEX_UNLOCK( &stream_.mutex ); return; } MUTEX_UNLOCK( &stream_.mutex ); } if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiPulse::callbackEvent(): the stream is closed ... " "this shouldn't happen!"; error( RtAudioError::WARNING ); return; } RtAudioCallback callback = (RtAudioCallback) stream_.callbackInfo.callback; double streamTime = getStreamTime(); RtAudioStreamStatus status = 0; int doStopStream = callback( stream_.userBuffer[OUTPUT], stream_.userBuffer[INPUT], stream_.bufferSize, streamTime, status, stream_.callbackInfo.userData ); if ( doStopStream == 2 ) { abortStream(); return; } MUTEX_LOCK( &stream_.mutex ); void *pulse_in = stream_.doConvertBuffer[INPUT] ? stream_.deviceBuffer : stream_.userBuffer[INPUT]; void *pulse_out = stream_.doConvertBuffer[OUTPUT] ? stream_.deviceBuffer : stream_.userBuffer[OUTPUT]; if ( stream_.state != STREAM_RUNNING ) goto unlock; int pa_error; size_t bytes; if (stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { if ( stream_.doConvertBuffer[OUTPUT] ) { convertBuffer( stream_.deviceBuffer, stream_.userBuffer[OUTPUT], stream_.convertInfo[OUTPUT] ); bytes = stream_.nDeviceChannels[OUTPUT] * stream_.bufferSize * formatBytes( stream_.deviceFormat[OUTPUT] ); } else bytes = stream_.nUserChannels[OUTPUT] * stream_.bufferSize * formatBytes( stream_.userFormat ); if ( pa_simple_write( pah->s_play, pulse_out, bytes, &pa_error ) < 0 ) { errorStream_ << "RtApiPulse::callbackEvent: audio write error, " << pa_strerror( pa_error ) << "."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); } } if ( stream_.mode == INPUT || stream_.mode == DUPLEX) { if ( stream_.doConvertBuffer[INPUT] ) bytes = stream_.nDeviceChannels[INPUT] * stream_.bufferSize * formatBytes( stream_.deviceFormat[INPUT] ); else bytes = stream_.nUserChannels[INPUT] * stream_.bufferSize * formatBytes( stream_.userFormat ); if ( pa_simple_read( pah->s_rec, pulse_in, bytes, &pa_error ) < 0 ) { errorStream_ << "RtApiPulse::callbackEvent: audio read error, " << pa_strerror( pa_error ) << "."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); } if ( stream_.doConvertBuffer[INPUT] ) { convertBuffer( stream_.userBuffer[INPUT], stream_.deviceBuffer, stream_.convertInfo[INPUT] ); } } unlock: MUTEX_UNLOCK( &stream_.mutex ); RtApi::tickStreamTime(); if ( doStopStream == 1 ) stopStream(); } void RtApiPulse::startStream( void ) { PulseAudioHandle *pah = static_cast( stream_.apiHandle ); if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiPulse::startStream(): the stream is not open!"; error( RtAudioError::INVALID_USE ); return; } if ( stream_.state == STREAM_RUNNING ) { errorText_ = "RtApiPulse::startStream(): the stream is already running!"; error( RtAudioError::WARNING ); return; } MUTEX_LOCK( &stream_.mutex ); stream_.state = STREAM_RUNNING; pah->runnable = true; pthread_cond_signal( &pah->runnable_cv ); MUTEX_UNLOCK( &stream_.mutex ); } void RtApiPulse::stopStream( void ) { PulseAudioHandle *pah = static_cast( stream_.apiHandle ); if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiPulse::stopStream(): the stream is not open!"; error( RtAudioError::INVALID_USE ); return; } if ( stream_.state == STREAM_STOPPED ) { errorText_ = "RtApiPulse::stopStream(): the stream is already stopped!"; error( RtAudioError::WARNING ); return; } stream_.state = STREAM_STOPPED; MUTEX_LOCK( &stream_.mutex ); if ( pah && pah->s_play ) { int pa_error; if ( pa_simple_drain( pah->s_play, &pa_error ) < 0 ) { errorStream_ << "RtApiPulse::stopStream: error draining output device, " << pa_strerror( pa_error ) << "."; errorText_ = errorStream_.str(); MUTEX_UNLOCK( &stream_.mutex ); error( RtAudioError::SYSTEM_ERROR ); return; } } stream_.state = STREAM_STOPPED; MUTEX_UNLOCK( &stream_.mutex ); } void RtApiPulse::abortStream( void ) { PulseAudioHandle *pah = static_cast( stream_.apiHandle ); if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiPulse::abortStream(): the stream is not open!"; error( RtAudioError::INVALID_USE ); return; } if ( stream_.state == STREAM_STOPPED ) { errorText_ = "RtApiPulse::abortStream(): the stream is already stopped!"; error( RtAudioError::WARNING ); return; } stream_.state = STREAM_STOPPED; MUTEX_LOCK( &stream_.mutex ); if ( pah && pah->s_play ) { int pa_error; if ( pa_simple_flush( pah->s_play, &pa_error ) < 0 ) { errorStream_ << "RtApiPulse::abortStream: error flushing output device, " << pa_strerror( pa_error ) << "."; errorText_ = errorStream_.str(); MUTEX_UNLOCK( &stream_.mutex ); error( RtAudioError::SYSTEM_ERROR ); return; } } stream_.state = STREAM_STOPPED; MUTEX_UNLOCK( &stream_.mutex ); } bool RtApiPulse::probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, RtAudio::StreamOptions *options ) { PulseAudioHandle *pah = 0; unsigned long bufferBytes = 0; pa_sample_spec ss; if ( device != 0 ) return false; if ( mode != INPUT && mode != OUTPUT ) return false; if ( channels != 1 && channels != 2 ) { errorText_ = "RtApiPulse::probeDeviceOpen: unsupported number of channels."; return false; } ss.channels = channels; if ( firstChannel != 0 ) return false; bool sr_found = false; for ( const unsigned int *sr = SUPPORTED_SAMPLERATES; *sr; ++sr ) { if ( sampleRate == *sr ) { sr_found = true; stream_.sampleRate = sampleRate; ss.rate = sampleRate; break; } } if ( !sr_found ) { errorText_ = "RtApiPulse::probeDeviceOpen: unsupported sample rate."; return false; } bool sf_found = 0; for ( const rtaudio_pa_format_mapping_t *sf = supported_sampleformats; sf->rtaudio_format && sf->pa_format != PA_SAMPLE_INVALID; ++sf ) { if ( format == sf->rtaudio_format ) { sf_found = true; stream_.userFormat = sf->rtaudio_format; stream_.deviceFormat[mode] = stream_.userFormat; ss.format = sf->pa_format; break; } } if ( !sf_found ) { // Use internal data format conversion. stream_.userFormat = format; stream_.deviceFormat[mode] = RTAUDIO_FLOAT32; ss.format = PA_SAMPLE_FLOAT32LE; } // Set other stream parameters. if ( options && options->flags & RTAUDIO_NONINTERLEAVED ) stream_.userInterleaved = false; else stream_.userInterleaved = true; stream_.deviceInterleaved[mode] = true; stream_.nBuffers = 1; stream_.doByteSwap[mode] = false; stream_.nUserChannels[mode] = channels; stream_.nDeviceChannels[mode] = channels + firstChannel; stream_.channelOffset[mode] = 0; std::string streamName = "RtAudio"; // Set flags for buffer conversion. stream_.doConvertBuffer[mode] = false; if ( stream_.userFormat != stream_.deviceFormat[mode] ) stream_.doConvertBuffer[mode] = true; if ( stream_.nUserChannels[mode] < stream_.nDeviceChannels[mode] ) stream_.doConvertBuffer[mode] = true; // Allocate necessary internal buffers. bufferBytes = stream_.nUserChannels[mode] * *bufferSize * formatBytes( stream_.userFormat ); stream_.userBuffer[mode] = (char *) calloc( bufferBytes, 1 ); if ( stream_.userBuffer[mode] == NULL ) { errorText_ = "RtApiPulse::probeDeviceOpen: error allocating user buffer memory."; goto error; } stream_.bufferSize = *bufferSize; if ( stream_.doConvertBuffer[mode] ) { bool makeBuffer = true; bufferBytes = stream_.nDeviceChannels[mode] * formatBytes( stream_.deviceFormat[mode] ); if ( mode == INPUT ) { if ( stream_.mode == OUTPUT && stream_.deviceBuffer ) { unsigned long bytesOut = stream_.nDeviceChannels[0] * formatBytes( stream_.deviceFormat[0] ); if ( bufferBytes <= bytesOut ) makeBuffer = false; } } if ( makeBuffer ) { bufferBytes *= *bufferSize; if ( stream_.deviceBuffer ) free( stream_.deviceBuffer ); stream_.deviceBuffer = (char *) calloc( bufferBytes, 1 ); if ( stream_.deviceBuffer == NULL ) { errorText_ = "RtApiPulse::probeDeviceOpen: error allocating device buffer memory."; goto error; } } } stream_.device[mode] = device; // Setup the buffer conversion information structure. if ( stream_.doConvertBuffer[mode] ) setConvertInfo( mode, firstChannel ); if ( !stream_.apiHandle ) { PulseAudioHandle *pah = new PulseAudioHandle; if ( !pah ) { errorText_ = "RtApiPulse::probeDeviceOpen: error allocating memory for handle."; goto error; } stream_.apiHandle = pah; if ( pthread_cond_init( &pah->runnable_cv, NULL ) != 0 ) { errorText_ = "RtApiPulse::probeDeviceOpen: error creating condition variable."; goto error; } } pah = static_cast( stream_.apiHandle ); int error; if ( options && !options->streamName.empty() ) streamName = options->streamName; switch ( mode ) { case INPUT: pa_buffer_attr buffer_attr; buffer_attr.fragsize = bufferBytes; buffer_attr.maxlength = -1; pah->s_rec = pa_simple_new( NULL, streamName.c_str(), PA_STREAM_RECORD, NULL, "Record", &ss, NULL, &buffer_attr, &error ); if ( !pah->s_rec ) { errorText_ = "RtApiPulse::probeDeviceOpen: error connecting input to PulseAudio server."; goto error; } break; case OUTPUT: pah->s_play = pa_simple_new( NULL, streamName.c_str(), PA_STREAM_PLAYBACK, NULL, "Playback", &ss, NULL, NULL, &error ); if ( !pah->s_play ) { errorText_ = "RtApiPulse::probeDeviceOpen: error connecting output to PulseAudio server."; goto error; } break; default: goto error; } if ( stream_.mode == UNINITIALIZED ) stream_.mode = mode; else if ( stream_.mode == mode ) goto error; else stream_.mode = DUPLEX; if ( !stream_.callbackInfo.isRunning ) { stream_.callbackInfo.object = this; stream_.callbackInfo.isRunning = true; if ( pthread_create( &pah->thread, NULL, pulseaudio_callback, (void *)&stream_.callbackInfo) != 0 ) { errorText_ = "RtApiPulse::probeDeviceOpen: error creating thread."; goto error; } } stream_.state = STREAM_STOPPED; return true; error: if ( pah && stream_.callbackInfo.isRunning ) { pthread_cond_destroy( &pah->runnable_cv ); delete pah; stream_.apiHandle = 0; } for ( int i=0; i<2; i++ ) { if ( stream_.userBuffer[i] ) { free( stream_.userBuffer[i] ); stream_.userBuffer[i] = 0; } } if ( stream_.deviceBuffer ) { free( stream_.deviceBuffer ); stream_.deviceBuffer = 0; } return FAILURE; } //******************** End of __LINUX_PULSE__ *********************// #endif #if defined(__LINUX_OSS__) #include #include #include #include #include #include #include static void *ossCallbackHandler(void * ptr); // A structure to hold various information related to the OSS API // implementation. struct OssHandle { int id[2]; // device ids bool xrun[2]; bool triggered; pthread_cond_t runnable; OssHandle() :triggered(false) { id[0] = 0; id[1] = 0; xrun[0] = false; xrun[1] = false; } }; RtApiOss :: RtApiOss() { // Nothing to do here. } RtApiOss :: ~RtApiOss() { if ( stream_.state != STREAM_CLOSED ) closeStream(); } unsigned int RtApiOss :: getDeviceCount( void ) { int mixerfd = open( "/dev/mixer", O_RDWR, 0 ); if ( mixerfd == -1 ) { errorText_ = "RtApiOss::getDeviceCount: error opening '/dev/mixer'."; error( RtAudioError::WARNING ); return 0; } oss_sysinfo sysinfo; if ( ioctl( mixerfd, SNDCTL_SYSINFO, &sysinfo ) == -1 ) { close( mixerfd ); errorText_ = "RtApiOss::getDeviceCount: error getting sysinfo, OSS version >= 4.0 is required."; error( RtAudioError::WARNING ); return 0; } close( mixerfd ); return sysinfo.numaudios; } RtAudio::DeviceInfo RtApiOss :: getDeviceInfo( unsigned int device ) { RtAudio::DeviceInfo info; info.probed = false; int mixerfd = open( "/dev/mixer", O_RDWR, 0 ); if ( mixerfd == -1 ) { errorText_ = "RtApiOss::getDeviceInfo: error opening '/dev/mixer'."; error( RtAudioError::WARNING ); return info; } oss_sysinfo sysinfo; int result = ioctl( mixerfd, SNDCTL_SYSINFO, &sysinfo ); if ( result == -1 ) { close( mixerfd ); errorText_ = "RtApiOss::getDeviceInfo: error getting sysinfo, OSS version >= 4.0 is required."; error( RtAudioError::WARNING ); return info; } unsigned nDevices = sysinfo.numaudios; if ( nDevices == 0 ) { close( mixerfd ); errorText_ = "RtApiOss::getDeviceInfo: no devices found!"; error( RtAudioError::INVALID_USE ); return info; } if ( device >= nDevices ) { close( mixerfd ); errorText_ = "RtApiOss::getDeviceInfo: device ID is invalid!"; error( RtAudioError::INVALID_USE ); return info; } oss_audioinfo ainfo; ainfo.dev = device; result = ioctl( mixerfd, SNDCTL_AUDIOINFO, &ainfo ); close( mixerfd ); if ( result == -1 ) { errorStream_ << "RtApiOss::getDeviceInfo: error getting device (" << ainfo.name << ") info."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); return info; } // Probe channels if ( ainfo.caps & PCM_CAP_OUTPUT ) info.outputChannels = ainfo.max_channels; if ( ainfo.caps & PCM_CAP_INPUT ) info.inputChannels = ainfo.max_channels; if ( ainfo.caps & PCM_CAP_DUPLEX ) { if ( info.outputChannels > 0 && info.inputChannels > 0 && ainfo.caps & PCM_CAP_DUPLEX ) info.duplexChannels = (info.outputChannels > info.inputChannels) ? info.inputChannels : info.outputChannels; } // Probe data formats ... do for input unsigned long mask = ainfo.iformats; if ( mask & AFMT_S16_LE || mask & AFMT_S16_BE ) info.nativeFormats |= RTAUDIO_SINT16; if ( mask & AFMT_S8 ) info.nativeFormats |= RTAUDIO_SINT8; if ( mask & AFMT_S32_LE || mask & AFMT_S32_BE ) info.nativeFormats |= RTAUDIO_SINT32; if ( mask & AFMT_FLOAT ) info.nativeFormats |= RTAUDIO_FLOAT32; if ( mask & AFMT_S24_LE || mask & AFMT_S24_BE ) info.nativeFormats |= RTAUDIO_SINT24; // Check that we have at least one supported format if ( info.nativeFormats == 0 ) { errorStream_ << "RtApiOss::getDeviceInfo: device (" << ainfo.name << ") data format not supported by RtAudio."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); return info; } // Probe the supported sample rates. info.sampleRates.clear(); if ( ainfo.nrates ) { for ( unsigned int i=0; i info.preferredSampleRate ) ) info.preferredSampleRate = SAMPLE_RATES[k]; break; } } } } else { // Check min and max rate values; for ( unsigned int k=0; k= (int) SAMPLE_RATES[k] ) { info.sampleRates.push_back( SAMPLE_RATES[k] ); if ( !info.preferredSampleRate || ( SAMPLE_RATES[k] <= 48000 && SAMPLE_RATES[k] > info.preferredSampleRate ) ) info.preferredSampleRate = SAMPLE_RATES[k]; } } } if ( info.sampleRates.size() == 0 ) { errorStream_ << "RtApiOss::getDeviceInfo: no supported sample rates found for device (" << ainfo.name << ")."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); } else { info.probed = true; info.name = ainfo.name; } return info; } bool RtApiOss :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, RtAudio::StreamOptions *options ) { int mixerfd = open( "/dev/mixer", O_RDWR, 0 ); if ( mixerfd == -1 ) { errorText_ = "RtApiOss::probeDeviceOpen: error opening '/dev/mixer'."; return FAILURE; } oss_sysinfo sysinfo; int result = ioctl( mixerfd, SNDCTL_SYSINFO, &sysinfo ); if ( result == -1 ) { close( mixerfd ); errorText_ = "RtApiOss::probeDeviceOpen: error getting sysinfo, OSS version >= 4.0 is required."; return FAILURE; } unsigned nDevices = sysinfo.numaudios; if ( nDevices == 0 ) { // This should not happen because a check is made before this function is called. close( mixerfd ); errorText_ = "RtApiOss::probeDeviceOpen: no devices found!"; return FAILURE; } if ( device >= nDevices ) { // This should not happen because a check is made before this function is called. close( mixerfd ); errorText_ = "RtApiOss::probeDeviceOpen: device ID is invalid!"; return FAILURE; } oss_audioinfo ainfo; ainfo.dev = device; result = ioctl( mixerfd, SNDCTL_AUDIOINFO, &ainfo ); close( mixerfd ); if ( result == -1 ) { errorStream_ << "RtApiOss::getDeviceInfo: error getting device (" << ainfo.name << ") info."; errorText_ = errorStream_.str(); return FAILURE; } // Check if device supports input or output if ( ( mode == OUTPUT && !( ainfo.caps & PCM_CAP_OUTPUT ) ) || ( mode == INPUT && !( ainfo.caps & PCM_CAP_INPUT ) ) ) { if ( mode == OUTPUT ) errorStream_ << "RtApiOss::probeDeviceOpen: device (" << ainfo.name << ") does not support output."; else errorStream_ << "RtApiOss::probeDeviceOpen: device (" << ainfo.name << ") does not support input."; errorText_ = errorStream_.str(); return FAILURE; } int flags = 0; OssHandle *handle = (OssHandle *) stream_.apiHandle; if ( mode == OUTPUT ) flags |= O_WRONLY; else { // mode == INPUT if (stream_.mode == OUTPUT && stream_.device[0] == device) { // We just set the same device for playback ... close and reopen for duplex (OSS only). close( handle->id[0] ); handle->id[0] = 0; if ( !( ainfo.caps & PCM_CAP_DUPLEX ) ) { errorStream_ << "RtApiOss::probeDeviceOpen: device (" << ainfo.name << ") does not support duplex mode."; errorText_ = errorStream_.str(); return FAILURE; } // Check that the number previously set channels is the same. if ( stream_.nUserChannels[0] != channels ) { errorStream_ << "RtApiOss::probeDeviceOpen: input/output channels must be equal for OSS duplex device (" << ainfo.name << ")."; errorText_ = errorStream_.str(); return FAILURE; } flags |= O_RDWR; } else flags |= O_RDONLY; } // Set exclusive access if specified. if ( options && options->flags & RTAUDIO_HOG_DEVICE ) flags |= O_EXCL; // Try to open the device. int fd; fd = open( ainfo.devnode, flags, 0 ); if ( fd == -1 ) { if ( errno == EBUSY ) errorStream_ << "RtApiOss::probeDeviceOpen: device (" << ainfo.name << ") is busy."; else errorStream_ << "RtApiOss::probeDeviceOpen: error opening device (" << ainfo.name << ")."; errorText_ = errorStream_.str(); return FAILURE; } // For duplex operation, specifically set this mode (this doesn't seem to work). /* if ( flags | O_RDWR ) { result = ioctl( fd, SNDCTL_DSP_SETDUPLEX, NULL ); if ( result == -1) { errorStream_ << "RtApiOss::probeDeviceOpen: error setting duplex mode for device (" << ainfo.name << ")."; errorText_ = errorStream_.str(); return FAILURE; } } */ // Check the device channel support. stream_.nUserChannels[mode] = channels; if ( ainfo.max_channels < (int)(channels + firstChannel) ) { close( fd ); errorStream_ << "RtApiOss::probeDeviceOpen: the device (" << ainfo.name << ") does not support requested channel parameters."; errorText_ = errorStream_.str(); return FAILURE; } // Set the number of channels. int deviceChannels = channels + firstChannel; result = ioctl( fd, SNDCTL_DSP_CHANNELS, &deviceChannels ); if ( result == -1 || deviceChannels < (int)(channels + firstChannel) ) { close( fd ); errorStream_ << "RtApiOss::probeDeviceOpen: error setting channel parameters on device (" << ainfo.name << ")."; errorText_ = errorStream_.str(); return FAILURE; } stream_.nDeviceChannels[mode] = deviceChannels; // Get the data format mask int mask; result = ioctl( fd, SNDCTL_DSP_GETFMTS, &mask ); if ( result == -1 ) { close( fd ); errorStream_ << "RtApiOss::probeDeviceOpen: error getting device (" << ainfo.name << ") data formats."; errorText_ = errorStream_.str(); return FAILURE; } // Determine how to set the device format. stream_.userFormat = format; int deviceFormat = -1; stream_.doByteSwap[mode] = false; if ( format == RTAUDIO_SINT8 ) { if ( mask & AFMT_S8 ) { deviceFormat = AFMT_S8; stream_.deviceFormat[mode] = RTAUDIO_SINT8; } } else if ( format == RTAUDIO_SINT16 ) { if ( mask & AFMT_S16_NE ) { deviceFormat = AFMT_S16_NE; stream_.deviceFormat[mode] = RTAUDIO_SINT16; } else if ( mask & AFMT_S16_OE ) { deviceFormat = AFMT_S16_OE; stream_.deviceFormat[mode] = RTAUDIO_SINT16; stream_.doByteSwap[mode] = true; } } else if ( format == RTAUDIO_SINT24 ) { if ( mask & AFMT_S24_NE ) { deviceFormat = AFMT_S24_NE; stream_.deviceFormat[mode] = RTAUDIO_SINT24; } else if ( mask & AFMT_S24_OE ) { deviceFormat = AFMT_S24_OE; stream_.deviceFormat[mode] = RTAUDIO_SINT24; stream_.doByteSwap[mode] = true; } } else if ( format == RTAUDIO_SINT32 ) { if ( mask & AFMT_S32_NE ) { deviceFormat = AFMT_S32_NE; stream_.deviceFormat[mode] = RTAUDIO_SINT32; } else if ( mask & AFMT_S32_OE ) { deviceFormat = AFMT_S32_OE; stream_.deviceFormat[mode] = RTAUDIO_SINT32; stream_.doByteSwap[mode] = true; } } if ( deviceFormat == -1 ) { // The user requested format is not natively supported by the device. if ( mask & AFMT_S16_NE ) { deviceFormat = AFMT_S16_NE; stream_.deviceFormat[mode] = RTAUDIO_SINT16; } else if ( mask & AFMT_S32_NE ) { deviceFormat = AFMT_S32_NE; stream_.deviceFormat[mode] = RTAUDIO_SINT32; } else if ( mask & AFMT_S24_NE ) { deviceFormat = AFMT_S24_NE; stream_.deviceFormat[mode] = RTAUDIO_SINT24; } else if ( mask & AFMT_S16_OE ) { deviceFormat = AFMT_S16_OE; stream_.deviceFormat[mode] = RTAUDIO_SINT16; stream_.doByteSwap[mode] = true; } else if ( mask & AFMT_S32_OE ) { deviceFormat = AFMT_S32_OE; stream_.deviceFormat[mode] = RTAUDIO_SINT32; stream_.doByteSwap[mode] = true; } else if ( mask & AFMT_S24_OE ) { deviceFormat = AFMT_S24_OE; stream_.deviceFormat[mode] = RTAUDIO_SINT24; stream_.doByteSwap[mode] = true; } else if ( mask & AFMT_S8) { deviceFormat = AFMT_S8; stream_.deviceFormat[mode] = RTAUDIO_SINT8; } } if ( stream_.deviceFormat[mode] == 0 ) { // This really shouldn't happen ... close( fd ); errorStream_ << "RtApiOss::probeDeviceOpen: device (" << ainfo.name << ") data format not supported by RtAudio."; errorText_ = errorStream_.str(); return FAILURE; } // Set the data format. int temp = deviceFormat; result = ioctl( fd, SNDCTL_DSP_SETFMT, &deviceFormat ); if ( result == -1 || deviceFormat != temp ) { close( fd ); errorStream_ << "RtApiOss::probeDeviceOpen: error setting data format on device (" << ainfo.name << ")."; errorText_ = errorStream_.str(); return FAILURE; } // Attempt to set the buffer size. According to OSS, the minimum // number of buffers is two. The supposed minimum buffer size is 16 // bytes, so that will be our lower bound. The argument to this // call is in the form 0xMMMMSSSS (hex), where the buffer size (in // bytes) is given as 2^SSSS and the number of buffers as 2^MMMM. // We'll check the actual value used near the end of the setup // procedure. int ossBufferBytes = *bufferSize * formatBytes( stream_.deviceFormat[mode] ) * deviceChannels; if ( ossBufferBytes < 16 ) ossBufferBytes = 16; int buffers = 0; if ( options ) buffers = options->numberOfBuffers; if ( options && options->flags & RTAUDIO_MINIMIZE_LATENCY ) buffers = 2; if ( buffers < 2 ) buffers = 3; temp = ((int) buffers << 16) + (int)( log10( (double)ossBufferBytes ) / log10( 2.0 ) ); result = ioctl( fd, SNDCTL_DSP_SETFRAGMENT, &temp ); if ( result == -1 ) { close( fd ); errorStream_ << "RtApiOss::probeDeviceOpen: error setting buffer size on device (" << ainfo.name << ")."; errorText_ = errorStream_.str(); return FAILURE; } stream_.nBuffers = buffers; // Save buffer size (in sample frames). *bufferSize = ossBufferBytes / ( formatBytes(stream_.deviceFormat[mode]) * deviceChannels ); stream_.bufferSize = *bufferSize; // Set the sample rate. int srate = sampleRate; result = ioctl( fd, SNDCTL_DSP_SPEED, &srate ); if ( result == -1 ) { close( fd ); errorStream_ << "RtApiOss::probeDeviceOpen: error setting sample rate (" << sampleRate << ") on device (" << ainfo.name << ")."; errorText_ = errorStream_.str(); return FAILURE; } // Verify the sample rate setup worked. if ( abs( srate - sampleRate ) > 100 ) { close( fd ); errorStream_ << "RtApiOss::probeDeviceOpen: device (" << ainfo.name << ") does not support sample rate (" << sampleRate << ")."; errorText_ = errorStream_.str(); return FAILURE; } stream_.sampleRate = sampleRate; if ( mode == INPUT && stream_.mode == OUTPUT && stream_.device[0] == device) { // We're doing duplex setup here. stream_.deviceFormat[0] = stream_.deviceFormat[1]; stream_.nDeviceChannels[0] = deviceChannels; } // Set interleaving parameters. stream_.userInterleaved = true; stream_.deviceInterleaved[mode] = true; if ( options && options->flags & RTAUDIO_NONINTERLEAVED ) stream_.userInterleaved = false; // Set flags for buffer conversion stream_.doConvertBuffer[mode] = false; if ( stream_.userFormat != stream_.deviceFormat[mode] ) stream_.doConvertBuffer[mode] = true; if ( stream_.nUserChannels[mode] < stream_.nDeviceChannels[mode] ) stream_.doConvertBuffer[mode] = true; if ( stream_.userInterleaved != stream_.deviceInterleaved[mode] && stream_.nUserChannels[mode] > 1 ) stream_.doConvertBuffer[mode] = true; // Allocate the stream handles if necessary and then save. if ( stream_.apiHandle == 0 ) { try { handle = new OssHandle; } catch ( std::bad_alloc& ) { errorText_ = "RtApiOss::probeDeviceOpen: error allocating OssHandle memory."; goto error; } if ( pthread_cond_init( &handle->runnable, NULL ) ) { errorText_ = "RtApiOss::probeDeviceOpen: error initializing pthread condition variable."; goto error; } stream_.apiHandle = (void *) handle; } else { handle = (OssHandle *) stream_.apiHandle; } handle->id[mode] = fd; // Allocate necessary internal buffers. unsigned long bufferBytes; bufferBytes = stream_.nUserChannels[mode] * *bufferSize * formatBytes( stream_.userFormat ); stream_.userBuffer[mode] = (char *) calloc( bufferBytes, 1 ); if ( stream_.userBuffer[mode] == NULL ) { errorText_ = "RtApiOss::probeDeviceOpen: error allocating user buffer memory."; goto error; } if ( stream_.doConvertBuffer[mode] ) { bool makeBuffer = true; bufferBytes = stream_.nDeviceChannels[mode] * formatBytes( stream_.deviceFormat[mode] ); if ( mode == INPUT ) { if ( stream_.mode == OUTPUT && stream_.deviceBuffer ) { unsigned long bytesOut = stream_.nDeviceChannels[0] * formatBytes( stream_.deviceFormat[0] ); if ( bufferBytes <= bytesOut ) makeBuffer = false; } } if ( makeBuffer ) { bufferBytes *= *bufferSize; if ( stream_.deviceBuffer ) free( stream_.deviceBuffer ); stream_.deviceBuffer = (char *) calloc( bufferBytes, 1 ); if ( stream_.deviceBuffer == NULL ) { errorText_ = "RtApiOss::probeDeviceOpen: error allocating device buffer memory."; goto error; } } } stream_.device[mode] = device; stream_.state = STREAM_STOPPED; // Setup the buffer conversion information structure. if ( stream_.doConvertBuffer[mode] ) setConvertInfo( mode, firstChannel ); // Setup thread if necessary. if ( stream_.mode == OUTPUT && mode == INPUT ) { // We had already set up an output stream. stream_.mode = DUPLEX; if ( stream_.device[0] == device ) handle->id[0] = fd; } else { stream_.mode = mode; // Setup callback thread. stream_.callbackInfo.object = (void *) this; // Set the thread attributes for joinable and realtime scheduling // priority. The higher priority will only take affect if the // program is run as root or suid. pthread_attr_t attr; pthread_attr_init( &attr ); pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_JOINABLE ); #ifdef SCHED_RR // Undefined with some OSes (eg: NetBSD 1.6.x with GNU Pthread) if ( options && options->flags & RTAUDIO_SCHEDULE_REALTIME ) { struct sched_param param; int priority = options->priority; int min = sched_get_priority_min( SCHED_RR ); int max = sched_get_priority_max( SCHED_RR ); if ( priority < min ) priority = min; else if ( priority > max ) priority = max; param.sched_priority = priority; pthread_attr_setschedparam( &attr, ¶m ); pthread_attr_setschedpolicy( &attr, SCHED_RR ); } else pthread_attr_setschedpolicy( &attr, SCHED_OTHER ); #else pthread_attr_setschedpolicy( &attr, SCHED_OTHER ); #endif stream_.callbackInfo.isRunning = true; result = pthread_create( &stream_.callbackInfo.thread, &attr, ossCallbackHandler, &stream_.callbackInfo ); pthread_attr_destroy( &attr ); if ( result ) { stream_.callbackInfo.isRunning = false; errorText_ = "RtApiOss::error creating callback thread!"; goto error; } } return SUCCESS; error: if ( handle ) { pthread_cond_destroy( &handle->runnable ); if ( handle->id[0] ) close( handle->id[0] ); if ( handle->id[1] ) close( handle->id[1] ); delete handle; stream_.apiHandle = 0; } for ( int i=0; i<2; i++ ) { if ( stream_.userBuffer[i] ) { free( stream_.userBuffer[i] ); stream_.userBuffer[i] = 0; } } if ( stream_.deviceBuffer ) { free( stream_.deviceBuffer ); stream_.deviceBuffer = 0; } return FAILURE; } void RtApiOss :: closeStream() { if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiOss::closeStream(): no open stream to close!"; error( RtAudioError::WARNING ); return; } OssHandle *handle = (OssHandle *) stream_.apiHandle; stream_.callbackInfo.isRunning = false; MUTEX_LOCK( &stream_.mutex ); if ( stream_.state == STREAM_STOPPED ) pthread_cond_signal( &handle->runnable ); MUTEX_UNLOCK( &stream_.mutex ); pthread_join( stream_.callbackInfo.thread, NULL ); if ( stream_.state == STREAM_RUNNING ) { if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) ioctl( handle->id[0], SNDCTL_DSP_HALT, 0 ); else ioctl( handle->id[1], SNDCTL_DSP_HALT, 0 ); stream_.state = STREAM_STOPPED; } if ( handle ) { pthread_cond_destroy( &handle->runnable ); if ( handle->id[0] ) close( handle->id[0] ); if ( handle->id[1] ) close( handle->id[1] ); delete handle; stream_.apiHandle = 0; } for ( int i=0; i<2; i++ ) { if ( stream_.userBuffer[i] ) { free( stream_.userBuffer[i] ); stream_.userBuffer[i] = 0; } } if ( stream_.deviceBuffer ) { free( stream_.deviceBuffer ); stream_.deviceBuffer = 0; } stream_.mode = UNINITIALIZED; stream_.state = STREAM_CLOSED; } void RtApiOss :: startStream() { verifyStream(); if ( stream_.state == STREAM_RUNNING ) { errorText_ = "RtApiOss::startStream(): the stream is already running!"; error( RtAudioError::WARNING ); return; } MUTEX_LOCK( &stream_.mutex ); stream_.state = STREAM_RUNNING; // No need to do anything else here ... OSS automatically starts // when fed samples. MUTEX_UNLOCK( &stream_.mutex ); OssHandle *handle = (OssHandle *) stream_.apiHandle; pthread_cond_signal( &handle->runnable ); } void RtApiOss :: stopStream() { verifyStream(); if ( stream_.state == STREAM_STOPPED ) { errorText_ = "RtApiOss::stopStream(): the stream is already stopped!"; error( RtAudioError::WARNING ); return; } MUTEX_LOCK( &stream_.mutex ); // The state might change while waiting on a mutex. if ( stream_.state == STREAM_STOPPED ) { MUTEX_UNLOCK( &stream_.mutex ); return; } int result = 0; OssHandle *handle = (OssHandle *) stream_.apiHandle; if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { // Flush the output with zeros a few times. char *buffer; int samples; RtAudioFormat format; if ( stream_.doConvertBuffer[0] ) { buffer = stream_.deviceBuffer; samples = stream_.bufferSize * stream_.nDeviceChannels[0]; format = stream_.deviceFormat[0]; } else { buffer = stream_.userBuffer[0]; samples = stream_.bufferSize * stream_.nUserChannels[0]; format = stream_.userFormat; } memset( buffer, 0, samples * formatBytes(format) ); for ( unsigned int i=0; iid[0], buffer, samples * formatBytes(format) ); if ( result == -1 ) { errorText_ = "RtApiOss::stopStream: audio write error."; error( RtAudioError::WARNING ); } } result = ioctl( handle->id[0], SNDCTL_DSP_HALT, 0 ); if ( result == -1 ) { errorStream_ << "RtApiOss::stopStream: system error stopping callback procedure on device (" << stream_.device[0] << ")."; errorText_ = errorStream_.str(); goto unlock; } handle->triggered = false; } if ( stream_.mode == INPUT || ( stream_.mode == DUPLEX && handle->id[0] != handle->id[1] ) ) { result = ioctl( handle->id[1], SNDCTL_DSP_HALT, 0 ); if ( result == -1 ) { errorStream_ << "RtApiOss::stopStream: system error stopping input callback procedure on device (" << stream_.device[0] << ")."; errorText_ = errorStream_.str(); goto unlock; } } unlock: stream_.state = STREAM_STOPPED; MUTEX_UNLOCK( &stream_.mutex ); if ( result != -1 ) return; error( RtAudioError::SYSTEM_ERROR ); } void RtApiOss :: abortStream() { verifyStream(); if ( stream_.state == STREAM_STOPPED ) { errorText_ = "RtApiOss::abortStream(): the stream is already stopped!"; error( RtAudioError::WARNING ); return; } MUTEX_LOCK( &stream_.mutex ); // The state might change while waiting on a mutex. if ( stream_.state == STREAM_STOPPED ) { MUTEX_UNLOCK( &stream_.mutex ); return; } int result = 0; OssHandle *handle = (OssHandle *) stream_.apiHandle; if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { result = ioctl( handle->id[0], SNDCTL_DSP_HALT, 0 ); if ( result == -1 ) { errorStream_ << "RtApiOss::abortStream: system error stopping callback procedure on device (" << stream_.device[0] << ")."; errorText_ = errorStream_.str(); goto unlock; } handle->triggered = false; } if ( stream_.mode == INPUT || ( stream_.mode == DUPLEX && handle->id[0] != handle->id[1] ) ) { result = ioctl( handle->id[1], SNDCTL_DSP_HALT, 0 ); if ( result == -1 ) { errorStream_ << "RtApiOss::abortStream: system error stopping input callback procedure on device (" << stream_.device[0] << ")."; errorText_ = errorStream_.str(); goto unlock; } } unlock: stream_.state = STREAM_STOPPED; MUTEX_UNLOCK( &stream_.mutex ); if ( result != -1 ) return; error( RtAudioError::SYSTEM_ERROR ); } void RtApiOss :: callbackEvent() { OssHandle *handle = (OssHandle *) stream_.apiHandle; if ( stream_.state == STREAM_STOPPED ) { MUTEX_LOCK( &stream_.mutex ); pthread_cond_wait( &handle->runnable, &stream_.mutex ); if ( stream_.state != STREAM_RUNNING ) { MUTEX_UNLOCK( &stream_.mutex ); return; } MUTEX_UNLOCK( &stream_.mutex ); } if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiOss::callbackEvent(): the stream is closed ... this shouldn't happen!"; error( RtAudioError::WARNING ); return; } // Invoke user callback to get fresh output data. int doStopStream = 0; RtAudioCallback callback = (RtAudioCallback) stream_.callbackInfo.callback; double streamTime = getStreamTime(); RtAudioStreamStatus status = 0; if ( stream_.mode != INPUT && handle->xrun[0] == true ) { status |= RTAUDIO_OUTPUT_UNDERFLOW; handle->xrun[0] = false; } if ( stream_.mode != OUTPUT && handle->xrun[1] == true ) { status |= RTAUDIO_INPUT_OVERFLOW; handle->xrun[1] = false; } doStopStream = callback( stream_.userBuffer[0], stream_.userBuffer[1], stream_.bufferSize, streamTime, status, stream_.callbackInfo.userData ); if ( doStopStream == 2 ) { this->abortStream(); return; } MUTEX_LOCK( &stream_.mutex ); // The state might change while waiting on a mutex. if ( stream_.state == STREAM_STOPPED ) goto unlock; int result; char *buffer; int samples; RtAudioFormat format; if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { // Setup parameters and do buffer conversion if necessary. if ( stream_.doConvertBuffer[0] ) { buffer = stream_.deviceBuffer; convertBuffer( buffer, stream_.userBuffer[0], stream_.convertInfo[0] ); samples = stream_.bufferSize * stream_.nDeviceChannels[0]; format = stream_.deviceFormat[0]; } else { buffer = stream_.userBuffer[0]; samples = stream_.bufferSize * stream_.nUserChannels[0]; format = stream_.userFormat; } // Do byte swapping if necessary. if ( stream_.doByteSwap[0] ) byteSwapBuffer( buffer, samples, format ); if ( stream_.mode == DUPLEX && handle->triggered == false ) { int trig = 0; ioctl( handle->id[0], SNDCTL_DSP_SETTRIGGER, &trig ); result = write( handle->id[0], buffer, samples * formatBytes(format) ); trig = PCM_ENABLE_INPUT|PCM_ENABLE_OUTPUT; ioctl( handle->id[0], SNDCTL_DSP_SETTRIGGER, &trig ); handle->triggered = true; } else // Write samples to device. result = write( handle->id[0], buffer, samples * formatBytes(format) ); if ( result == -1 ) { // We'll assume this is an underrun, though there isn't a // specific means for determining that. handle->xrun[0] = true; errorText_ = "RtApiOss::callbackEvent: audio write error."; error( RtAudioError::WARNING ); // Continue on to input section. } } if ( stream_.mode == INPUT || stream_.mode == DUPLEX ) { // Setup parameters. if ( stream_.doConvertBuffer[1] ) { buffer = stream_.deviceBuffer; samples = stream_.bufferSize * stream_.nDeviceChannels[1]; format = stream_.deviceFormat[1]; } else { buffer = stream_.userBuffer[1]; samples = stream_.bufferSize * stream_.nUserChannels[1]; format = stream_.userFormat; } // Read samples from device. result = read( handle->id[1], buffer, samples * formatBytes(format) ); if ( result == -1 ) { // We'll assume this is an overrun, though there isn't a // specific means for determining that. handle->xrun[1] = true; errorText_ = "RtApiOss::callbackEvent: audio read error."; error( RtAudioError::WARNING ); goto unlock; } // Do byte swapping if necessary. if ( stream_.doByteSwap[1] ) byteSwapBuffer( buffer, samples, format ); // Do buffer conversion if necessary. if ( stream_.doConvertBuffer[1] ) convertBuffer( stream_.userBuffer[1], stream_.deviceBuffer, stream_.convertInfo[1] ); } unlock: MUTEX_UNLOCK( &stream_.mutex ); RtApi::tickStreamTime(); if ( doStopStream == 1 ) this->stopStream(); } static void *ossCallbackHandler( void *ptr ) { CallbackInfo *info = (CallbackInfo *) ptr; RtApiOss *object = (RtApiOss *) info->object; bool *isRunning = &info->isRunning; while ( *isRunning == true ) { pthread_testcancel(); object->callbackEvent(); } pthread_exit( NULL ); } //******************** End of __LINUX_OSS__ *********************// #endif // *************************************************** // // // Protected common (OS-independent) RtAudio methods. // // *************************************************** // // This method can be modified to control the behavior of error // message printing. void RtApi :: error( RtAudioError::Type type ) { errorStream_.str(""); // clear the ostringstream RtAudioErrorCallback errorCallback = (RtAudioErrorCallback) stream_.callbackInfo.errorCallback; if ( errorCallback ) { // abortStream() can generate new error messages. Ignore them. Just keep original one. if ( firstErrorOccurred_ ) return; firstErrorOccurred_ = true; const std::string errorMessage = errorText_; if ( type != RtAudioError::WARNING && stream_.state != STREAM_STOPPED) { stream_.callbackInfo.isRunning = false; // exit from the thread abortStream(); } errorCallback( type, errorMessage ); firstErrorOccurred_ = false; return; } if ( type == RtAudioError::WARNING && showWarnings_ == true ) std::cerr << '\n' << errorText_ << "\n\n"; else if ( type != RtAudioError::WARNING ) throw( RtAudioError( errorText_, type ) ); } void RtApi :: verifyStream() { if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApi:: a stream is not open!"; error( RtAudioError::INVALID_USE ); } } void RtApi :: clearStreamInfo() { stream_.mode = UNINITIALIZED; stream_.state = STREAM_CLOSED; stream_.sampleRate = 0; stream_.bufferSize = 0; stream_.nBuffers = 0; stream_.userFormat = 0; stream_.userInterleaved = true; stream_.streamTime = 0.0; stream_.apiHandle = 0; stream_.deviceBuffer = 0; stream_.callbackInfo.callback = 0; stream_.callbackInfo.userData = 0; stream_.callbackInfo.isRunning = false; stream_.callbackInfo.errorCallback = 0; for ( int i=0; i<2; i++ ) { stream_.device[i] = 11111; stream_.doConvertBuffer[i] = false; stream_.deviceInterleaved[i] = true; stream_.doByteSwap[i] = false; stream_.nUserChannels[i] = 0; stream_.nDeviceChannels[i] = 0; stream_.channelOffset[i] = 0; stream_.deviceFormat[i] = 0; stream_.latency[i] = 0; stream_.userBuffer[i] = 0; stream_.convertInfo[i].channels = 0; stream_.convertInfo[i].inJump = 0; stream_.convertInfo[i].outJump = 0; stream_.convertInfo[i].inFormat = 0; stream_.convertInfo[i].outFormat = 0; stream_.convertInfo[i].inOffset.clear(); stream_.convertInfo[i].outOffset.clear(); } } unsigned int RtApi :: formatBytes( RtAudioFormat format ) { if ( format == RTAUDIO_SINT16 ) return 2; else if ( format == RTAUDIO_SINT32 || format == RTAUDIO_FLOAT32 ) return 4; else if ( format == RTAUDIO_FLOAT64 ) return 8; else if ( format == RTAUDIO_SINT24 ) return 3; else if ( format == RTAUDIO_SINT8 ) return 1; errorText_ = "RtApi::formatBytes: undefined format."; error( RtAudioError::WARNING ); return 0; } void RtApi :: setConvertInfo( StreamMode mode, unsigned int firstChannel ) { if ( mode == INPUT ) { // convert device to user buffer stream_.convertInfo[mode].inJump = stream_.nDeviceChannels[1]; stream_.convertInfo[mode].outJump = stream_.nUserChannels[1]; stream_.convertInfo[mode].inFormat = stream_.deviceFormat[1]; stream_.convertInfo[mode].outFormat = stream_.userFormat; } else { // convert user to device buffer stream_.convertInfo[mode].inJump = stream_.nUserChannels[0]; stream_.convertInfo[mode].outJump = stream_.nDeviceChannels[0]; stream_.convertInfo[mode].inFormat = stream_.userFormat; stream_.convertInfo[mode].outFormat = stream_.deviceFormat[0]; } if ( stream_.convertInfo[mode].inJump < stream_.convertInfo[mode].outJump ) stream_.convertInfo[mode].channels = stream_.convertInfo[mode].inJump; else stream_.convertInfo[mode].channels = stream_.convertInfo[mode].outJump; // Set up the interleave/deinterleave offsets. if ( stream_.deviceInterleaved[mode] != stream_.userInterleaved ) { if ( ( mode == OUTPUT && stream_.deviceInterleaved[mode] ) || ( mode == INPUT && stream_.userInterleaved ) ) { for ( int k=0; k 0 ) { if ( stream_.deviceInterleaved[mode] ) { if ( mode == OUTPUT ) { for ( int k=0; k> 8); //out[info.outOffset[j]] >>= 8; } in += info.inJump; out += info.outJump; } } else if (info.inFormat == RTAUDIO_FLOAT32) { Float32 *in = (Float32 *)inBuffer; for (unsigned int i=0; i> 8); } in += info.inJump; out += info.outJump; } } else if (info.inFormat == RTAUDIO_SINT32) { Int32 *in = (Int32 *)inBuffer; for (unsigned int i=0; i> 16) & 0x0000ffff); } in += info.inJump; out += info.outJump; } } else if (info.inFormat == RTAUDIO_FLOAT32) { Float32 *in = (Float32 *)inBuffer; for (unsigned int i=0; i> 8) & 0x00ff); } in += info.inJump; out += info.outJump; } } else if (info.inFormat == RTAUDIO_SINT24) { Int24 *in = (Int24 *)inBuffer; for (unsigned int i=0; i> 16); } in += info.inJump; out += info.outJump; } } else if (info.inFormat == RTAUDIO_SINT32) { Int32 *in = (Int32 *)inBuffer; for (unsigned int i=0; i> 24) & 0x000000ff); } in += info.inJump; out += info.outJump; } } else if (info.inFormat == RTAUDIO_FLOAT32) { Float32 *in = (Float32 *)inBuffer; for (unsigned int i=0; i>8) | (x<<8); } //static inline uint32_t bswap_32(uint32_t x) { return (bswap_16(x&0xffff)<<16) | (bswap_16(x>>16)); } //static inline uint64_t bswap_64(uint64_t x) { return (((unsigned long long)bswap_32(x&0xffffffffull))<<32) | (bswap_32(x>>32)); } void RtApi :: byteSwapBuffer( char *buffer, unsigned int samples, RtAudioFormat format ) { char val; char *ptr; ptr = buffer; if ( format == RTAUDIO_SINT16 ) { for ( unsigned int i=0; i #include #include #include /*! \typedef typedef unsigned long RtAudioFormat; \brief RtAudio data format type. Support for signed integers and floats. Audio data fed to/from an RtAudio stream is assumed to ALWAYS be in host byte order. The internal routines will automatically take care of any necessary byte-swapping between the host format and the soundcard. Thus, endian-ness is not a concern in the following format definitions. - \e RTAUDIO_SINT8: 8-bit signed integer. - \e RTAUDIO_SINT16: 16-bit signed integer. - \e RTAUDIO_SINT24: 24-bit signed integer. - \e RTAUDIO_SINT32: 32-bit signed integer. - \e RTAUDIO_FLOAT32: Normalized between plus/minus 1.0. - \e RTAUDIO_FLOAT64: Normalized between plus/minus 1.0. */ typedef unsigned long RtAudioFormat; static const RtAudioFormat RTAUDIO_SINT8 = 0x1; // 8-bit signed integer. static const RtAudioFormat RTAUDIO_SINT16 = 0x2; // 16-bit signed integer. static const RtAudioFormat RTAUDIO_SINT24 = 0x4; // 24-bit signed integer. static const RtAudioFormat RTAUDIO_SINT32 = 0x8; // 32-bit signed integer. static const RtAudioFormat RTAUDIO_FLOAT32 = 0x10; // Normalized between plus/minus 1.0. static const RtAudioFormat RTAUDIO_FLOAT64 = 0x20; // Normalized between plus/minus 1.0. /*! \typedef typedef unsigned long RtAudioStreamFlags; \brief RtAudio stream option flags. The following flags can be OR'ed together to allow a client to make changes to the default stream behavior: - \e RTAUDIO_NONINTERLEAVED: Use non-interleaved buffers (default = interleaved). - \e RTAUDIO_MINIMIZE_LATENCY: Attempt to set stream parameters for lowest possible latency. - \e RTAUDIO_HOG_DEVICE: Attempt grab device for exclusive use. - \e RTAUDIO_ALSA_USE_DEFAULT: Use the "default" PCM device (ALSA only). By default, RtAudio streams pass and receive audio data from the client in an interleaved format. By passing the RTAUDIO_NONINTERLEAVED flag to the openStream() function, audio data will instead be presented in non-interleaved buffers. In this case, each buffer argument in the RtAudioCallback function will point to a single array of data, with \c nFrames samples for each channel concatenated back-to-back. For example, the first sample of data for the second channel would be located at index \c nFrames (assuming the \c buffer pointer was recast to the correct data type for the stream). Certain audio APIs offer a number of parameters that influence the I/O latency of a stream. By default, RtAudio will attempt to set these parameters internally for robust (glitch-free) performance (though some APIs, like Windows Direct Sound, make this difficult). By passing the RTAUDIO_MINIMIZE_LATENCY flag to the openStream() function, internal stream settings will be influenced in an attempt to minimize stream latency, though possibly at the expense of stream performance. If the RTAUDIO_HOG_DEVICE flag is set, RtAudio will attempt to open the input and/or output stream device(s) for exclusive use. Note that this is not possible with all supported audio APIs. If the RTAUDIO_SCHEDULE_REALTIME flag is set, RtAudio will attempt to select realtime scheduling (round-robin) for the callback thread. If the RTAUDIO_ALSA_USE_DEFAULT flag is set, RtAudio will attempt to open the "default" PCM device when using the ALSA API. Note that this will override any specified input or output device id. */ typedef unsigned int RtAudioStreamFlags; static const RtAudioStreamFlags RTAUDIO_NONINTERLEAVED = 0x1; // Use non-interleaved buffers (default = interleaved). static const RtAudioStreamFlags RTAUDIO_MINIMIZE_LATENCY = 0x2; // Attempt to set stream parameters for lowest possible latency. static const RtAudioStreamFlags RTAUDIO_HOG_DEVICE = 0x4; // Attempt grab device and prevent use by others. static const RtAudioStreamFlags RTAUDIO_SCHEDULE_REALTIME = 0x8; // Try to select realtime scheduling for callback thread. static const RtAudioStreamFlags RTAUDIO_ALSA_USE_DEFAULT = 0x10; // Use the "default" PCM device (ALSA only). /*! \typedef typedef unsigned long RtAudioStreamStatus; \brief RtAudio stream status (over- or underflow) flags. Notification of a stream over- or underflow is indicated by a non-zero stream \c status argument in the RtAudioCallback function. The stream status can be one of the following two options, depending on whether the stream is open for output and/or input: - \e RTAUDIO_INPUT_OVERFLOW: Input data was discarded because of an overflow condition at the driver. - \e RTAUDIO_OUTPUT_UNDERFLOW: The output buffer ran low, likely producing a break in the output sound. */ typedef unsigned int RtAudioStreamStatus; static const RtAudioStreamStatus RTAUDIO_INPUT_OVERFLOW = 0x1; // Input data was discarded because of an overflow condition at the driver. static const RtAudioStreamStatus RTAUDIO_OUTPUT_UNDERFLOW = 0x2; // The output buffer ran low, likely causing a gap in the output sound. //! RtAudio callback function prototype. /*! All RtAudio clients must create a function of type RtAudioCallback to read and/or write data from/to the audio stream. When the underlying audio system is ready for new input or output data, this function will be invoked. \param outputBuffer For output (or duplex) streams, the client should write \c nFrames of audio sample frames into this buffer. This argument should be recast to the datatype specified when the stream was opened. For input-only streams, this argument will be NULL. \param inputBuffer For input (or duplex) streams, this buffer will hold \c nFrames of input audio sample frames. This argument should be recast to the datatype specified when the stream was opened. For output-only streams, this argument will be NULL. \param nFrames The number of sample frames of input or output data in the buffers. The actual buffer size in bytes is dependent on the data type and number of channels in use. \param streamTime The number of seconds that have elapsed since the stream was started. \param status If non-zero, this argument indicates a data overflow or underflow condition for the stream. The particular condition can be determined by comparison with the RtAudioStreamStatus flags. \param userData A pointer to optional data provided by the client when opening the stream (default = NULL). To continue normal stream operation, the RtAudioCallback function should return a value of zero. To stop the stream and drain the output buffer, the function should return a value of one. To abort the stream immediately, the client should return a value of two. */ typedef int (*RtAudioCallback)( void *outputBuffer, void *inputBuffer, unsigned int nFrames, double streamTime, RtAudioStreamStatus status, void *userData ); /************************************************************************/ /*! \class RtAudioError \brief Exception handling class for RtAudio. The RtAudioError class is quite simple but it does allow errors to be "caught" by RtAudioError::Type. See the RtAudio documentation to know which methods can throw an RtAudioError. */ /************************************************************************/ class RtAudioError : public std::exception { public: //! Defined RtAudioError types. enum Type { WARNING, /*!< A non-critical error. */ DEBUG_WARNING, /*!< A non-critical error which might be useful for debugging. */ UNSPECIFIED, /*!< The default, unspecified error type. */ NO_DEVICES_FOUND, /*!< No devices found on system. */ INVALID_DEVICE, /*!< An invalid device ID was specified. */ MEMORY_ERROR, /*!< An error occured during memory allocation. */ INVALID_PARAMETER, /*!< An invalid parameter was specified to a function. */ INVALID_USE, /*!< The function was called incorrectly. */ DRIVER_ERROR, /*!< A system driver error occured. */ SYSTEM_ERROR, /*!< A system error occured. */ THREAD_ERROR /*!< A thread error occured. */ }; //! The constructor. RtAudioError( const std::string& message, Type type = RtAudioError::UNSPECIFIED ) throw() : message_(message), type_(type) {} //! The destructor. virtual ~RtAudioError( void ) throw() {} //! Prints thrown error message to stderr. virtual void printMessage( void ) const throw() { std::cerr << '\n' << message_ << "\n\n"; } //! Returns the thrown error message type. virtual const Type& getType(void) const throw() { return type_; } //! Returns the thrown error message string. virtual const std::string& getMessage(void) const throw() { return message_; } //! Returns the thrown error message as a c-style string. virtual const char* what( void ) const throw() { return message_.c_str(); } protected: std::string message_; Type type_; }; //! RtAudio error callback function prototype. /*! \param type Type of error. \param errorText Error description. */ typedef void (*RtAudioErrorCallback)( RtAudioError::Type type, const std::string &errorText ); // **************************************************************** // // // RtAudio class declaration. // // RtAudio is a "controller" used to select an available audio i/o // interface. It presents a common API for the user to call but all // functionality is implemented by the class RtApi and its // subclasses. RtAudio creates an instance of an RtApi subclass // based on the user's API choice. If no choice is made, RtAudio // attempts to make a "logical" API selection. // // **************************************************************** // class RtApi; class RtAudio { public: //! Audio API specifier arguments. enum Api { UNSPECIFIED, /*!< Search for a working compiled API. */ LINUX_ALSA, /*!< The Advanced Linux Sound Architecture API. */ LINUX_PULSE, /*!< The Linux PulseAudio API. */ LINUX_OSS, /*!< The Linux Open Sound System API. */ UNIX_JACK, /*!< The Jack Low-Latency Audio Server API. */ MACOSX_CORE, /*!< Macintosh OS-X Core Audio API. */ WINDOWS_WASAPI, /*!< The Microsoft WASAPI API. */ WINDOWS_ASIO, /*!< The Steinberg Audio Stream I/O API. */ WINDOWS_DS, /*!< The Microsoft Direct Sound API. */ RTAUDIO_DUMMY /*!< A compilable but non-functional API. */ }; //! The public device information structure for returning queried values. struct DeviceInfo { bool probed; /*!< true if the device capabilities were successfully probed. */ std::string name; /*!< Character string device identifier. */ unsigned int outputChannels; /*!< Maximum output channels supported by device. */ unsigned int inputChannels; /*!< Maximum input channels supported by device. */ unsigned int duplexChannels; /*!< Maximum simultaneous input/output channels supported by device. */ bool isDefaultOutput; /*!< true if this is the default output device. */ bool isDefaultInput; /*!< true if this is the default input device. */ std::vector sampleRates; /*!< Supported sample rates (queried from list of standard rates). */ unsigned int preferredSampleRate; /*!< Preferred sample rate, eg. for WASAPI the system sample rate. */ RtAudioFormat nativeFormats; /*!< Bit mask of supported data formats. */ // Default constructor. DeviceInfo() :probed(false), outputChannels(0), inputChannels(0), duplexChannels(0), isDefaultOutput(false), isDefaultInput(false), preferredSampleRate(0), nativeFormats(0) {} }; //! The structure for specifying input or ouput stream parameters. struct StreamParameters { unsigned int deviceId; /*!< Device index (0 to getDeviceCount() - 1). */ unsigned int nChannels; /*!< Number of channels. */ unsigned int firstChannel; /*!< First channel index on device (default = 0). */ // Default constructor. StreamParameters() : deviceId(0), nChannels(0), firstChannel(0) {} }; //! The structure for specifying stream options. /*! The following flags can be OR'ed together to allow a client to make changes to the default stream behavior: - \e RTAUDIO_NONINTERLEAVED: Use non-interleaved buffers (default = interleaved). - \e RTAUDIO_MINIMIZE_LATENCY: Attempt to set stream parameters for lowest possible latency. - \e RTAUDIO_HOG_DEVICE: Attempt grab device for exclusive use. - \e RTAUDIO_SCHEDULE_REALTIME: Attempt to select realtime scheduling for callback thread. - \e RTAUDIO_ALSA_USE_DEFAULT: Use the "default" PCM device (ALSA only). By default, RtAudio streams pass and receive audio data from the client in an interleaved format. By passing the RTAUDIO_NONINTERLEAVED flag to the openStream() function, audio data will instead be presented in non-interleaved buffers. In this case, each buffer argument in the RtAudioCallback function will point to a single array of data, with \c nFrames samples for each channel concatenated back-to-back. For example, the first sample of data for the second channel would be located at index \c nFrames (assuming the \c buffer pointer was recast to the correct data type for the stream). Certain audio APIs offer a number of parameters that influence the I/O latency of a stream. By default, RtAudio will attempt to set these parameters internally for robust (glitch-free) performance (though some APIs, like Windows Direct Sound, make this difficult). By passing the RTAUDIO_MINIMIZE_LATENCY flag to the openStream() function, internal stream settings will be influenced in an attempt to minimize stream latency, though possibly at the expense of stream performance. If the RTAUDIO_HOG_DEVICE flag is set, RtAudio will attempt to open the input and/or output stream device(s) for exclusive use. Note that this is not possible with all supported audio APIs. If the RTAUDIO_SCHEDULE_REALTIME flag is set, RtAudio will attempt to select realtime scheduling (round-robin) for the callback thread. The \c priority parameter will only be used if the RTAUDIO_SCHEDULE_REALTIME flag is set. It defines the thread's realtime priority. If the RTAUDIO_ALSA_USE_DEFAULT flag is set, RtAudio will attempt to open the "default" PCM device when using the ALSA API. Note that this will override any specified input or output device id. The \c numberOfBuffers parameter can be used to control stream latency in the Windows DirectSound, Linux OSS, and Linux Alsa APIs only. A value of two is usually the smallest allowed. Larger numbers can potentially result in more robust stream performance, though likely at the cost of stream latency. The value set by the user is replaced during execution of the RtAudio::openStream() function by the value actually used by the system. The \c streamName parameter can be used to set the client name when using the Jack API. By default, the client name is set to RtApiJack. However, if you wish to create multiple instances of RtAudio with Jack, each instance must have a unique client name. */ struct StreamOptions { RtAudioStreamFlags flags; /*!< A bit-mask of stream flags (RTAUDIO_NONINTERLEAVED, RTAUDIO_MINIMIZE_LATENCY, RTAUDIO_HOG_DEVICE, RTAUDIO_ALSA_USE_DEFAULT). */ unsigned int numberOfBuffers; /*!< Number of stream buffers. */ std::string streamName; /*!< A stream name (currently used only in Jack). */ int priority; /*!< Scheduling priority of callback thread (only used with flag RTAUDIO_SCHEDULE_REALTIME). */ // Default constructor. StreamOptions() : flags(0), numberOfBuffers(0), priority(0) {} }; //! A static function to determine the current RtAudio version. static std::string getVersion( void ) throw(); //! A static function to determine the available compiled audio APIs. /*! The values returned in the std::vector can be compared against the enumerated list values. Note that there can be more than one API compiled for certain operating systems. */ static void getCompiledApi( std::vector &apis ) throw(); //! The class constructor. /*! The constructor performs minor initialization tasks. An exception can be thrown if no API support is compiled. If no API argument is specified and multiple API support has been compiled, the default order of use is JACK, ALSA, OSS (Linux systems) and ASIO, DS (Windows systems). */ RtAudio( RtAudio::Api api=UNSPECIFIED ); //! The destructor. /*! If a stream is running or open, it will be stopped and closed automatically. */ ~RtAudio() throw(); //! Returns the audio API specifier for the current instance of RtAudio. RtAudio::Api getCurrentApi( void ) throw(); //! A public function that queries for the number of audio devices available. /*! This function performs a system query of available devices each time it is called, thus supporting devices connected \e after instantiation. If a system error occurs during processing, a warning will be issued. */ unsigned int getDeviceCount( void ) throw(); //! Return an RtAudio::DeviceInfo structure for a specified device number. /*! Any device integer between 0 and getDeviceCount() - 1 is valid. If an invalid argument is provided, an RtAudioError (type = INVALID_USE) will be thrown. If a device is busy or otherwise unavailable, the structure member "probed" will have a value of "false" and all other members are undefined. If the specified device is the current default input or output device, the corresponding "isDefault" member will have a value of "true". */ RtAudio::DeviceInfo getDeviceInfo( unsigned int device ); //! A function that returns the index of the default output device. /*! If the underlying audio API does not provide a "default device", or if no devices are available, the return value will be 0. Note that this is a valid device identifier and it is the client's responsibility to verify that a device is available before attempting to open a stream. */ unsigned int getDefaultOutputDevice( void ) throw(); //! A function that returns the index of the default input device. /*! If the underlying audio API does not provide a "default device", or if no devices are available, the return value will be 0. Note that this is a valid device identifier and it is the client's responsibility to verify that a device is available before attempting to open a stream. */ unsigned int getDefaultInputDevice( void ) throw(); //! A public function for opening a stream with the specified parameters. /*! An RtAudioError (type = SYSTEM_ERROR) is thrown if a stream cannot be opened with the specified parameters or an error occurs during processing. An RtAudioError (type = INVALID_USE) is thrown if any invalid device ID or channel number parameters are specified. \param outputParameters Specifies output stream parameters to use when opening a stream, including a device ID, number of channels, and starting channel number. For input-only streams, this argument should be NULL. The device ID is an index value between 0 and getDeviceCount() - 1. \param inputParameters Specifies input stream parameters to use when opening a stream, including a device ID, number of channels, and starting channel number. For output-only streams, this argument should be NULL. The device ID is an index value between 0 and getDeviceCount() - 1. \param format An RtAudioFormat specifying the desired sample data format. \param sampleRate The desired sample rate (sample frames per second). \param *bufferFrames A pointer to a value indicating the desired internal buffer size in sample frames. The actual value used by the device is returned via the same pointer. A value of zero can be specified, in which case the lowest allowable value is determined. \param callback A client-defined function that will be invoked when input data is available and/or output data is needed. \param userData An optional pointer to data that can be accessed from within the callback function. \param options An optional pointer to a structure containing various global stream options, including a list of OR'ed RtAudioStreamFlags and a suggested number of stream buffers that can be used to control stream latency. More buffers typically result in more robust performance, though at a cost of greater latency. If a value of zero is specified, a system-specific median value is chosen. If the RTAUDIO_MINIMIZE_LATENCY flag bit is set, the lowest allowable value is used. The actual value used is returned via the structure argument. The parameter is API dependent. \param errorCallback A client-defined function that will be invoked when an error has occured. */ void openStream( RtAudio::StreamParameters *outputParameters, RtAudio::StreamParameters *inputParameters, RtAudioFormat format, unsigned int sampleRate, unsigned int *bufferFrames, RtAudioCallback callback, void *userData = NULL, RtAudio::StreamOptions *options = NULL, RtAudioErrorCallback errorCallback = NULL ); //! A function that closes a stream and frees any associated stream memory. /*! If a stream is not open, this function issues a warning and returns (no exception is thrown). */ void closeStream( void ) throw(); //! A function that starts a stream. /*! An RtAudioError (type = SYSTEM_ERROR) is thrown if an error occurs during processing. An RtAudioError (type = INVALID_USE) is thrown if a stream is not open. A warning is issued if the stream is already running. */ void startStream( void ); //! Stop a stream, allowing any samples remaining in the output queue to be played. /*! An RtAudioError (type = SYSTEM_ERROR) is thrown if an error occurs during processing. An RtAudioError (type = INVALID_USE) is thrown if a stream is not open. A warning is issued if the stream is already stopped. */ void stopStream( void ); //! Stop a stream, discarding any samples remaining in the input/output queue. /*! An RtAudioError (type = SYSTEM_ERROR) is thrown if an error occurs during processing. An RtAudioError (type = INVALID_USE) is thrown if a stream is not open. A warning is issued if the stream is already stopped. */ void abortStream( void ); //! Returns true if a stream is open and false if not. bool isStreamOpen( void ) const throw(); //! Returns true if the stream is running and false if it is stopped or not open. bool isStreamRunning( void ) const throw(); //! Returns the number of elapsed seconds since the stream was started. /*! If a stream is not open, an RtAudioError (type = INVALID_USE) will be thrown. */ double getStreamTime( void ); //! Set the stream time to a time in seconds greater than or equal to 0.0. /*! If a stream is not open, an RtAudioError (type = INVALID_USE) will be thrown. */ void setStreamTime( double time ); //! Returns the internal stream latency in sample frames. /*! The stream latency refers to delay in audio input and/or output caused by internal buffering by the audio system and/or hardware. For duplex streams, the returned value will represent the sum of the input and output latencies. If a stream is not open, an RtAudioError (type = INVALID_USE) will be thrown. If the API does not report latency, the return value will be zero. */ long getStreamLatency( void ); //! Returns actual sample rate in use by the stream. /*! On some systems, the sample rate used may be slightly different than that specified in the stream parameters. If a stream is not open, an RtAudioError (type = INVALID_USE) will be thrown. */ unsigned int getStreamSampleRate( void ); //! Specify whether warning messages should be printed to stderr. void showWarnings( bool value = true ) throw(); /* --- Monocasual hack ---------------------------------------------------- */ //protected: /* ------------------------------------------------------------------------ */ void openRtApi( RtAudio::Api api ); RtApi *rtapi_; }; // Operating system dependent thread functionality. #if defined(__WINDOWS_DS__) || defined(__WINDOWS_ASIO__) || defined(__WINDOWS_WASAPI__) #ifndef NOMINMAX #define NOMINMAX #endif #include #include typedef uintptr_t ThreadHandle; typedef CRITICAL_SECTION StreamMutex; #elif defined(__LINUX_ALSA__) || defined(__LINUX_PULSE__) || defined(__UNIX_JACK__) || defined(__LINUX_OSS__) || defined(__MACOSX_CORE__) // Using pthread library for various flavors of unix. #include typedef pthread_t ThreadHandle; typedef pthread_mutex_t StreamMutex; #else // Setup for "dummy" behavior #define __RTAUDIO_DUMMY__ typedef int ThreadHandle; typedef int StreamMutex; #endif // This global structure type is used to pass callback information // between the private RtAudio stream structure and global callback // handling functions. struct CallbackInfo { void *object; // Used as a "this" pointer. ThreadHandle thread; void *callback; void *userData; void *errorCallback; void *apiInfo; // void pointer for API specific callback information bool isRunning; bool doRealtime; int priority; // Default constructor. CallbackInfo() :object(0), callback(0), userData(0), errorCallback(0), apiInfo(0), isRunning(false), doRealtime(false) {} }; // **************************************************************** // // // RtApi class declaration. // // Subclasses of RtApi contain all API- and OS-specific code necessary // to fully implement the RtAudio API. // // Note that RtApi is an abstract base class and cannot be // explicitly instantiated. The class RtAudio will create an // instance of an RtApi subclass (RtApiOss, RtApiAlsa, // RtApiJack, RtApiCore, RtApiDs, or RtApiAsio). // // **************************************************************** // #pragma pack(push, 1) class S24 { protected: unsigned char c3[3]; public: S24() {} S24& operator = ( const int& i ) { c3[0] = (i & 0x000000ff); c3[1] = (i & 0x0000ff00) >> 8; c3[2] = (i & 0x00ff0000) >> 16; return *this; } S24( const S24& v ) { *this = v; } S24( const double& d ) { *this = (int) d; } S24( const float& f ) { *this = (int) f; } S24( const signed short& s ) { *this = (int) s; } S24( const char& c ) { *this = (int) c; } int asInt() { int i = c3[0] | (c3[1] << 8) | (c3[2] << 16); if (i & 0x800000) i |= ~0xffffff; return i; } }; #pragma pack(pop) #if defined( HAVE_GETTIMEOFDAY ) #include #endif #include class RtApi { public: /* --- Monocasual hack ---------------------------------------------------- */ #ifdef __linux__ void *__HACK__getJackClient(); #endif /* ------------------------------------------------------------------------ */ RtApi(); virtual ~RtApi(); virtual RtAudio::Api getCurrentApi( void ) = 0; virtual unsigned int getDeviceCount( void ) = 0; virtual RtAudio::DeviceInfo getDeviceInfo( unsigned int device ) = 0; virtual unsigned int getDefaultInputDevice( void ); virtual unsigned int getDefaultOutputDevice( void ); void openStream( RtAudio::StreamParameters *outputParameters, RtAudio::StreamParameters *inputParameters, RtAudioFormat format, unsigned int sampleRate, unsigned int *bufferFrames, RtAudioCallback callback, void *userData, RtAudio::StreamOptions *options, RtAudioErrorCallback errorCallback ); virtual void closeStream( void ); virtual void startStream( void ) = 0; virtual void stopStream( void ) = 0; virtual void abortStream( void ) = 0; long getStreamLatency( void ); unsigned int getStreamSampleRate( void ); virtual double getStreamTime( void ); virtual void setStreamTime( double time ); bool isStreamOpen( void ) const { return stream_.state != STREAM_CLOSED; } bool isStreamRunning( void ) const { return stream_.state == STREAM_RUNNING; } void showWarnings( bool value ) { showWarnings_ = value; } protected: static const unsigned int MAX_SAMPLE_RATES; static const unsigned int SAMPLE_RATES[]; enum { FAILURE, SUCCESS }; enum StreamState { STREAM_STOPPED, STREAM_STOPPING, STREAM_RUNNING, STREAM_CLOSED = -50 }; enum StreamMode { OUTPUT, INPUT, DUPLEX, UNINITIALIZED = -75 }; // A protected structure used for buffer conversion. struct ConvertInfo { int channels; int inJump, outJump; RtAudioFormat inFormat, outFormat; std::vector inOffset; std::vector outOffset; }; // A protected structure for audio streams. struct RtApiStream { unsigned int device[2]; // Playback and record, respectively. void *apiHandle; // void pointer for API specific stream handle information StreamMode mode; // OUTPUT, INPUT, or DUPLEX. StreamState state; // STOPPED, RUNNING, or CLOSED char *userBuffer[2]; // Playback and record, respectively. char *deviceBuffer; bool doConvertBuffer[2]; // Playback and record, respectively. bool userInterleaved; bool deviceInterleaved[2]; // Playback and record, respectively. bool doByteSwap[2]; // Playback and record, respectively. unsigned int sampleRate; unsigned int bufferSize; unsigned int nBuffers; unsigned int nUserChannels[2]; // Playback and record, respectively. unsigned int nDeviceChannels[2]; // Playback and record channels, respectively. unsigned int channelOffset[2]; // Playback and record, respectively. unsigned long latency[2]; // Playback and record, respectively. RtAudioFormat userFormat; RtAudioFormat deviceFormat[2]; // Playback and record, respectively. StreamMutex mutex; CallbackInfo callbackInfo; ConvertInfo convertInfo[2]; double streamTime; // Number of elapsed seconds since the stream started. #if defined(HAVE_GETTIMEOFDAY) struct timeval lastTickTimestamp; #endif RtApiStream() :apiHandle(0), deviceBuffer(0) { device[0] = 11111; device[1] = 11111; } }; typedef S24 Int24; typedef signed short Int16; typedef signed int Int32; typedef float Float32; typedef double Float64; std::ostringstream errorStream_; std::string errorText_; bool showWarnings_; RtApiStream stream_; bool firstErrorOccurred_; /*! Protected, api-specific method that attempts to open a device with the given parameters. This function MUST be implemented by all subclasses. If an error is encountered during the probe, a "warning" message is reported and FAILURE is returned. A successful probe is indicated by a return value of SUCCESS. */ virtual bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, RtAudio::StreamOptions *options ); //! A protected function used to increment the stream time. void tickStreamTime( void ); //! Protected common method to clear an RtApiStream structure. void clearStreamInfo(); /*! Protected common method that throws an RtAudioError (type = INVALID_USE) if a stream is not open. */ void verifyStream( void ); //! Protected common error method to allow global control over error handling. void error( RtAudioError::Type type ); /*! Protected method used to perform format, channel number, and/or interleaving conversions between the user and device buffers. */ void convertBuffer( char *outBuffer, char *inBuffer, ConvertInfo &info ); //! Protected common method used to perform byte-swapping on buffers. void byteSwapBuffer( char *buffer, unsigned int samples, RtAudioFormat format ); //! Protected common method that returns the number of bytes for a given format. unsigned int formatBytes( RtAudioFormat format ); //! Protected common method that sets up the parameters for buffer conversion. void setConvertInfo( StreamMode mode, unsigned int firstChannel ); }; // **************************************************************** // // // Inline RtAudio definitions. // // **************************************************************** // inline RtAudio::Api RtAudio :: getCurrentApi( void ) throw() { return rtapi_->getCurrentApi(); } inline unsigned int RtAudio :: getDeviceCount( void ) throw() { return rtapi_->getDeviceCount(); } inline RtAudio::DeviceInfo RtAudio :: getDeviceInfo( unsigned int device ) { return rtapi_->getDeviceInfo( device ); } inline unsigned int RtAudio :: getDefaultInputDevice( void ) throw() { return rtapi_->getDefaultInputDevice(); } inline unsigned int RtAudio :: getDefaultOutputDevice( void ) throw() { return rtapi_->getDefaultOutputDevice(); } inline void RtAudio :: closeStream( void ) throw() { return rtapi_->closeStream(); } inline void RtAudio :: startStream( void ) { return rtapi_->startStream(); } inline void RtAudio :: stopStream( void ) { return rtapi_->stopStream(); } inline void RtAudio :: abortStream( void ) { return rtapi_->abortStream(); } inline bool RtAudio :: isStreamOpen( void ) const throw() { return rtapi_->isStreamOpen(); } inline bool RtAudio :: isStreamRunning( void ) const throw() { return rtapi_->isStreamRunning(); } inline long RtAudio :: getStreamLatency( void ) { return rtapi_->getStreamLatency(); } inline unsigned int RtAudio :: getStreamSampleRate( void ) { return rtapi_->getStreamSampleRate(); } inline double RtAudio :: getStreamTime( void ) { return rtapi_->getStreamTime(); } inline void RtAudio :: setStreamTime( double time ) { return rtapi_->setStreamTime( time ); } inline void RtAudio :: showWarnings( bool value ) throw() { rtapi_->showWarnings( value ); } // RtApi Subclass prototypes. #if defined(__MACOSX_CORE__) #include class RtApiCore: public RtApi { public: RtApiCore(); ~RtApiCore(); RtAudio::Api getCurrentApi( void ) { return RtAudio::MACOSX_CORE; } unsigned int getDeviceCount( void ); RtAudio::DeviceInfo getDeviceInfo( unsigned int device ); unsigned int getDefaultOutputDevice( void ); unsigned int getDefaultInputDevice( void ); void closeStream( void ); void startStream( void ); void stopStream( void ); void abortStream( void ); long getStreamLatency( void ); // This function is intended for internal use only. It must be // public because it is called by the internal callback handler, // which is not a member of RtAudio. External use of this function // will most likely produce highly undesireable results! bool callbackEvent( AudioDeviceID deviceId, const AudioBufferList *inBufferList, const AudioBufferList *outBufferList ); private: bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, RtAudio::StreamOptions *options ); static const char* getErrorCode( OSStatus code ); }; #endif #if defined(__UNIX_JACK__) class RtApiJack: public RtApi { public: RtApiJack(); ~RtApiJack(); RtAudio::Api getCurrentApi( void ) { return RtAudio::UNIX_JACK; } unsigned int getDeviceCount( void ); RtAudio::DeviceInfo getDeviceInfo( unsigned int device ); void closeStream( void ); void startStream( void ); void stopStream( void ); void abortStream( void ); long getStreamLatency( void ); // This function is intended for internal use only. It must be // public because it is called by the internal callback handler, // which is not a member of RtAudio. External use of this function // will most likely produce highly undesireable results! bool callbackEvent( unsigned long nframes ); private: bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, RtAudio::StreamOptions *options ); }; #endif #if defined(__WINDOWS_ASIO__) class RtApiAsio: public RtApi { public: RtApiAsio(); ~RtApiAsio(); RtAudio::Api getCurrentApi( void ) { return RtAudio::WINDOWS_ASIO; } unsigned int getDeviceCount( void ); RtAudio::DeviceInfo getDeviceInfo( unsigned int device ); void closeStream( void ); void startStream( void ); void stopStream( void ); void abortStream( void ); long getStreamLatency( void ); // This function is intended for internal use only. It must be // public because it is called by the internal callback handler, // which is not a member of RtAudio. External use of this function // will most likely produce highly undesireable results! bool callbackEvent( long bufferIndex ); private: std::vector devices_; void saveDeviceInfo( void ); bool coInitialized_; bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, RtAudio::StreamOptions *options ); }; #endif #if defined(__WINDOWS_DS__) class RtApiDs: public RtApi { public: RtApiDs(); ~RtApiDs(); RtAudio::Api getCurrentApi( void ) { return RtAudio::WINDOWS_DS; } unsigned int getDeviceCount( void ); unsigned int getDefaultOutputDevice( void ); unsigned int getDefaultInputDevice( void ); RtAudio::DeviceInfo getDeviceInfo( unsigned int device ); void closeStream( void ); void startStream( void ); void stopStream( void ); void abortStream( void ); long getStreamLatency( void ); // This function is intended for internal use only. It must be // public because it is called by the internal callback handler, // which is not a member of RtAudio. External use of this function // will most likely produce highly undesireable results! void callbackEvent( void ); private: bool coInitialized_; bool buffersRolling; long duplexPrerollBytes; std::vector dsDevices; bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, RtAudio::StreamOptions *options ); }; #endif #if defined(__WINDOWS_WASAPI__) struct IMMDeviceEnumerator; class RtApiWasapi : public RtApi { public: RtApiWasapi(); ~RtApiWasapi(); RtAudio::Api getCurrentApi( void ) { return RtAudio::WINDOWS_WASAPI; } unsigned int getDeviceCount( void ); RtAudio::DeviceInfo getDeviceInfo( unsigned int device ); unsigned int getDefaultOutputDevice( void ); unsigned int getDefaultInputDevice( void ); void closeStream( void ); void startStream( void ); void stopStream( void ); void abortStream( void ); private: bool coInitialized_; IMMDeviceEnumerator* deviceEnumerator_; bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int* bufferSize, RtAudio::StreamOptions* options ); static DWORD WINAPI runWasapiThread( void* wasapiPtr ); static DWORD WINAPI stopWasapiThread( void* wasapiPtr ); static DWORD WINAPI abortWasapiThread( void* wasapiPtr ); void wasapiThread(); }; #endif #if defined(__LINUX_ALSA__) class RtApiAlsa: public RtApi { public: RtApiAlsa(); ~RtApiAlsa(); RtAudio::Api getCurrentApi() { return RtAudio::LINUX_ALSA; } unsigned int getDeviceCount( void ); RtAudio::DeviceInfo getDeviceInfo( unsigned int device ); void closeStream( void ); void startStream( void ); void stopStream( void ); void abortStream( void ); // This function is intended for internal use only. It must be // public because it is called by the internal callback handler, // which is not a member of RtAudio. External use of this function // will most likely produce highly undesireable results! void callbackEvent( void ); private: std::vector devices_; void saveDeviceInfo( void ); bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, RtAudio::StreamOptions *options ); }; #endif #if defined(__LINUX_PULSE__) class RtApiPulse: public RtApi { public: ~RtApiPulse(); RtAudio::Api getCurrentApi() { return RtAudio::LINUX_PULSE; } unsigned int getDeviceCount( void ); RtAudio::DeviceInfo getDeviceInfo( unsigned int device ); void closeStream( void ); void startStream( void ); void stopStream( void ); void abortStream( void ); // This function is intended for internal use only. It must be // public because it is called by the internal callback handler, // which is not a member of RtAudio. External use of this function // will most likely produce highly undesireable results! void callbackEvent( void ); private: std::vector devices_; void saveDeviceInfo( void ); bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, RtAudio::StreamOptions *options ); }; #endif #if defined(__LINUX_OSS__) class RtApiOss: public RtApi { public: RtApiOss(); ~RtApiOss(); RtAudio::Api getCurrentApi() { return RtAudio::LINUX_OSS; } unsigned int getDeviceCount( void ); RtAudio::DeviceInfo getDeviceInfo( unsigned int device ); void closeStream( void ); void startStream( void ); void stopStream( void ); void abortStream( void ); // This function is intended for internal use only. It must be // public because it is called by the internal callback handler, // which is not a member of RtAudio. External use of this function // will most likely produce highly undesireable results! void callbackEvent( void ); private: bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, RtAudio::StreamOptions *options ); }; #endif #if defined(__RTAUDIO_DUMMY__) class RtApiDummy: public RtApi { public: RtApiDummy() { errorText_ = "RtApiDummy: This class provides no functionality."; error( RtAudioError::WARNING ); } RtAudio::Api getCurrentApi( void ) { return RtAudio::RTAUDIO_DUMMY; } unsigned int getDeviceCount( void ) { return 0; } RtAudio::DeviceInfo getDeviceInfo( unsigned int /*device*/ ) { RtAudio::DeviceInfo info; return info; } void closeStream( void ) {} void startStream( void ) {} void stopStream( void ) {} void abortStream( void ) {} private: bool probeDeviceOpen( unsigned int /*device*/, StreamMode /*mode*/, unsigned int /*channels*/, unsigned int /*firstChannel*/, unsigned int /*sampleRate*/, RtAudioFormat /*format*/, unsigned int * /*bufferSize*/, RtAudio::StreamOptions * /*options*/ ) { return false; } }; #endif #endif // Indentation settings for Vim and Emacs // // Local Variables: // c-basic-offset: 2 // indent-tabs-mode: nil // End: // // vim: et sts=2 sw=2 giada-0.14.5/src/ext/000077500000000000000000000000001322662744500142505ustar00rootroot00000000000000giada-0.14.5/src/ext/giada.ico000066400000000000000000000226761322662744500160260ustar00rootroot0000000000000000 %(0`      $'+.03467776531/,(%!  $).6H^cWLKMMNMMKIFB>:50+%   <zXWUROKFA<70+%  HWJGC?;61+&! P69d60-*&# f,AE + T  _ ]!  ,*m>  =U {)^J|  h0  A !!!!!!1""""""""""""""""""!!!#########"""X############""" 3"""%%%%%%%%%%%%T 8m%%%%%%+)&&&&&&&&&&&&&&&<'''''''''((( j"""(((###?&&&))))))))))))kV************%%%)!!!+++###++++++,,,,,,###------------  ..............."...000000000///C(,,,111&&&111111111111---K111222222222111# i333444444444444444333& u555555555555444 666666666666777777U888888888888777%I888999999999999999:::::::::;;;;;;N+++<<<<<<<<<<<<<<<=============== w j???????????????111???@@@@@@@@@@@@ 777AAAAAABBBBBBAAA fBBBCCCCCCCCCCCC***CCCDDDDDDDDDCCC>/DDDEEEEEEFFFFFF>>>RFFFGGGGGGGGGFFFm;;;HHHHHHHHHHHHGGG5IIIJJJJJJJJJJJJKKKKKKKKKKKKKKK!!!<<. * * -------------------------------------------------------------------------- */ #include #include #include "../gui/dialogs/gd_mainWindow.h" #include "../gui/dialogs/sampleEditor.h" #include "../gui/dialogs/gd_warnings.h" #include "../gui/elems/basics/input.h" #include "../gui/elems/basics/dial.h" #include "../gui/elems/sampleEditor/waveTools.h" #include "../gui/elems/sampleEditor/volumeTool.h" #include "../gui/elems/sampleEditor/boostTool.h" #include "../gui/elems/sampleEditor/panTool.h" #include "../gui/elems/sampleEditor/pitchTool.h" #include "../gui/elems/sampleEditor/rangeTool.h" #include "../gui/elems/sampleEditor/waveform.h" #include "../gui/elems/mainWindow/keyboard/keyboard.h" #include "../gui/elems/mainWindow/keyboard/channel.h" #include "../gui/elems/mainWindow/keyboard/sampleChannel.h" #include "../gui/elems/mainWindow/keyboard/channelButton.h" #include "../utils/gui.h" #include "../utils/fs.h" #include "../utils/log.h" #include "../core/kernelAudio.h" #include "../core/mixerHandler.h" #include "../core/mixer.h" #include "../core/clock.h" #include "../core/pluginHost.h" #include "../core/conf.h" #include "../core/wave.h" #include "../core/channel.h" #include "../core/sampleChannel.h" #include "../core/midiChannel.h" #include "../core/plugin.h" #include "../core/waveManager.h" #include "main.h" #include "channel.h" extern gdMainWindow* G_MainWin; using std::string; using namespace giada::m; static bool __soloSession__ = false; int glue_loadChannel(SampleChannel* ch, const string& fname) { /* Always stop a channel before loading a new sample in it. This will prevent issues if tracker is outside the boundaries of the new sample -> segfault. */ if (ch->status & (STATUS_PLAY | STATUS_ENDING)) ch->hardStop(0); /* Save the patch and take the last browser's dir in order to re-use it the next time. */ conf::samplePath = gu_dirname(fname); Wave* wave = nullptr; int result = waveManager::create(fname, &wave); if (result != G_RES_OK) return result; if (wave->getRate() != conf::samplerate) { gu_log("[glue_loadChannel] input rate (%d) != system rate (%d), conversion needed\n", wave->getRate(), conf::samplerate); result = waveManager::resample(wave, conf::rsmpQuality, conf::samplerate); if (result != G_RES_OK) { delete wave; return result; } } ch->pushWave(wave); G_MainWin->keyboard->updateChannel(ch->guiChannel); return result; } /* -------------------------------------------------------------------------- */ Channel* glue_addChannel(int column, int type, int size) { Channel* ch = mh::addChannel(type); geChannel* gch = G_MainWin->keyboard->addChannel(column, ch, size); ch->guiChannel = gch; return ch; } /* -------------------------------------------------------------------------- */ void glue_deleteChannel(Channel* ch) { if (!gdConfirmWin("Warning", "Delete channel: are you sure?")) return; recorder::clearChan(ch->index); ch->hasActions = false; #ifdef WITH_VST pluginHost::freeStack(pluginHost::CHANNEL, &mixer::mutex_plugins, ch); #endif Fl::lock(); G_MainWin->keyboard->deleteChannel(ch->guiChannel); Fl::unlock(); mh::deleteChannel(ch); gu_closeAllSubwindows(); } /* -------------------------------------------------------------------------- */ void glue_freeChannel(Channel* ch) { if (ch->status == STATUS_PLAY) { if (!gdConfirmWin("Warning", "This action will stop the channel: are you sure?")) return; } else if (!gdConfirmWin("Warning", "Free channel: are you sure?")) return; G_MainWin->keyboard->freeChannel(ch->guiChannel); recorder::clearChan(ch->index); ch->hasActions = false; ch->empty(); /* delete any related subwindow */ /** TODO - use gu_closeAllSubwindows() */ G_MainWin->delSubWindow(WID_FILE_BROWSER); G_MainWin->delSubWindow(WID_ACTION_EDITOR); G_MainWin->delSubWindow(WID_SAMPLE_EDITOR); G_MainWin->delSubWindow(WID_FX_LIST); } /* -------------------------------------------------------------------------- */ void glue_toggleArm(Channel* ch, bool gui) { ch->setArmed(!ch->isArmed()); if (!gui) ch->guiChannel->arm->value(ch->isArmed()); } /* -------------------------------------------------------------------------- */ void glue_toggleInputMonitor(Channel* ch) { SampleChannel* sch = static_cast(ch); sch->inputMonitor = !sch->inputMonitor; } /* -------------------------------------------------------------------------- */ int glue_cloneChannel(Channel* src) { Channel* ch = mh::addChannel(src->type); geChannel* gch = G_MainWin->keyboard->addChannel(src->guiChannel->getColumnIndex(), ch, src->guiChannel->getSize()); ch->guiChannel = gch; ch->copy(src, &mixer::mutex_plugins); G_MainWin->keyboard->updateChannel(ch->guiChannel); return true; } /* -------------------------------------------------------------------------- */ void glue_setVolume(Channel* ch, float v, bool gui, bool editor) { ch->volume = v; /* Changing channel volume? Update wave editor (if it's shown). */ if (!editor) { gdSampleEditor *gdEditor = (gdSampleEditor*) gu_getSubwindow(G_MainWin, WID_SAMPLE_EDITOR); if (gdEditor) { Fl::lock(); gdEditor->volumeTool->refresh(); Fl::unlock(); } } if (!gui) { Fl::lock(); ch->guiChannel->vol->value(v); Fl::unlock(); } } /* -------------------------------------------------------------------------- */ void glue_setPitch(SampleChannel* ch, float val) { ch->setPitch(val); gdSampleEditor* gdEditor = static_cast(gu_getSubwindow(G_MainWin, WID_SAMPLE_EDITOR)); if (gdEditor) { Fl::lock(); gdEditor->pitchTool->refresh(); Fl::unlock(); } } /* -------------------------------------------------------------------------- */ void glue_setPanning(SampleChannel* ch, float val) { ch->setPan(val); gdSampleEditor* gdEditor = static_cast(gu_getSubwindow(G_MainWin, WID_SAMPLE_EDITOR)); if (gdEditor) { Fl::lock(); gdEditor->panTool->refresh(); Fl::unlock(); } } /* -------------------------------------------------------------------------- */ void glue_toggleMute(Channel* ch, bool gui) { if (recorder::active && recorder::canRec(ch, clock::isRunning(), mixer::recording)) { if (!ch->mute) { recorder::startOverdub(ch->index, G_ACTION_MUTES, clock::getCurrentFrame(), kernelAudio::getRealBufSize()); ch->readActions = false; // don't read actions while overdubbing } else recorder::stopOverdub(clock::getCurrentFrame(), clock::getTotalFrames(), &mixer::mutex_recs); } ch->mute ? ch->unsetMute(false) : ch->setMute(false); if (!gui) { Fl::lock(); ch->guiChannel->mute->value(ch->mute); Fl::unlock(); } } /* -------------------------------------------------------------------------- */ void glue_toggleSolo(Channel* ch, bool gui) { ch->solo ? glue_setSoloOn(ch, gui) : glue_setSoloOff(ch, gui); } /* -------------------------------------------------------------------------- */ void glue_kill(Channel* ch) { ch->kill(0); // on frame 0: it's a user-generated event } /* -------------------------------------------------------------------------- */ void glue_setSoloOn(Channel* ch, bool gui) { /* if there's no solo session, store mute configuration of all chans * and start the session */ if (!__soloSession__) { for (unsigned i=0; imute_s = och->mute; } __soloSession__ = true; } ch->solo = !ch->solo; ch->sendMidiLsolo(); /* mute all other channels and unmute this (if muted) */ for (unsigned i=0; isolo && !och->mute) { och->setMute(false); Fl::lock(); och->guiChannel->mute->value(true); Fl::unlock(); } } if (ch->mute) { ch->unsetMute(false); Fl::lock(); ch->guiChannel->mute->value(false); Fl::unlock(); } if (!gui) { Fl::lock(); ch->guiChannel->solo->value(1); Fl::unlock(); } } /* -------------------------------------------------------------------------- */ void glue_setSoloOff(Channel* ch, bool gui) { /* if this is uniqueSolo, stop solo session and restore mute status, * else mute this */ if (mh::uniqueSolo(ch)) { __soloSession__ = false; for (unsigned i=0; imute_s) { och->setMute(false); Fl::lock(); och->guiChannel->mute->value(true); Fl::unlock(); } else { och->unsetMute(false); Fl::lock(); och->guiChannel->mute->value(false); Fl::unlock(); } och->mute_s = false; } } else { ch->setMute(false); Fl::lock(); ch->guiChannel->mute->value(true); Fl::unlock(); } ch->solo = !ch->solo; ch->sendMidiLsolo(); if (!gui) { Fl::lock(); ch->guiChannel->solo->value(0); Fl::unlock(); } } /* -------------------------------------------------------------------------- */ void glue_setBoost(SampleChannel* ch, float val) { ch->setBoost(val); gdSampleEditor *gdEditor = static_cast(gu_getSubwindow(G_MainWin, WID_SAMPLE_EDITOR)); if (gdEditor) { Fl::lock(); gdEditor->boostTool->refresh(); Fl::unlock(); } } /* -------------------------------------------------------------------------- */ void glue_setName(Channel* ch, const string& name) { ch->setName(name); ch->guiChannel->update(); } /* -------------------------------------------------------------------------- */ void glue_toggleReadingRecs(SampleChannel* ch, bool gui) { /* When you call glue_startReadingRecs with conf::treatRecsAsLoops, the member value ch->readActions actually is not set to true immediately, because the channel is in wait mode (REC_WAITING). ch->readActions will become true on the next first beat. So a 'stop rec' command should occur also when ch->readActions is false but the channel is in wait mode; this check will handle the case of when you press 'R', the channel goes into REC_WAITING and then you press 'R' again to undo the status. */ if (ch->readActions || (!ch->readActions && ch->recStatus == REC_WAITING)) glue_stopReadingRecs(ch, gui); else glue_startReadingRecs(ch, gui); } /* -------------------------------------------------------------------------- */ void glue_startReadingRecs(SampleChannel* ch, bool gui) { if (conf::treatRecsAsLoops) ch->recStatus = REC_WAITING; else ch->setReadActions(true, conf::recsStopOnChanHalt); if (!gui) { Fl::lock(); ((geSampleChannel*)ch->guiChannel)->readActions->value(1); Fl::unlock(); } } /* -------------------------------------------------------------------------- */ void glue_stopReadingRecs(SampleChannel* ch, bool gui) { /* First of all, if the mixer is not running just stop and disable everything. Then if "treatRecsAsLoop" wait until the sequencer reaches beat 0, so put the channel in REC_ENDING status. */ if (!clock::isRunning()) { ch->recStatus = REC_STOPPED; ch->readActions = false; } else if (conf::treatRecsAsLoops) ch->recStatus = REC_ENDING; else ch->setReadActions(false, conf::recsStopOnChanHalt); if (!gui) { Fl::lock(); ((geSampleChannel*)ch->guiChannel)->readActions->value(0); Fl::unlock(); } } giada-0.14.5/src/glue/channel.h000066400000000000000000000056201322662744500161700ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef G_GLUE_CHANNEL_H #define G_GLUE_CHANNEL_H #include class Channel; class SampleChannel; class gdSampleEditor; /* addChannel * add an empty new channel to the stack. Returns the new channel. */ Channel* glue_addChannel(int column, int type, int size); /* loadChannel * fill an existing channel with a wave. */ int glue_loadChannel(SampleChannel* ch, const std::string& fname); /* deleteChannel * Remove a channel from Mixer. */ void glue_deleteChannel(Channel* ch); /* freeChannel * Unload the sample from a sample channel. */ void glue_freeChannel(Channel* ch); /* cloneChannel * Make an exact copy of Channel *ch. */ int glue_cloneChannel(Channel* ch); /* toggle/set* Toggles or set several channel properties. If gui == true the signal comes from a manual interaction on the GUI, otherwise it's a MIDI/Jack/external signal. */ void glue_toggleArm(Channel* ch, bool gui=true); void glue_toggleInputMonitor(Channel* ch); void glue_kill(Channel* ch); void glue_toggleMute(Channel* ch, bool gui=true); void glue_setSoloOn(Channel* ch, bool gui=true); void glue_setSoloOff(Channel* ch, bool gui=true); void glue_toggleSolo(Channel* ch, bool gui=true); void glue_setVolume(Channel* ch, float v, bool gui=true, bool editor=false); void glue_setName(Channel* ch, const std::string& name); void glue_setPitch(SampleChannel* ch, float val); void glue_setPanning(SampleChannel* ch, float val); void glue_setBoost(SampleChannel* ch, float val); /* toggleReadingRecs Handles the 'R' button. If gui == true the signal comes from an user interaction on the GUI, otherwise it's a MIDI/Jack/external signal. */ void glue_toggleReadingRecs(SampleChannel* ch, bool gui=true); void glue_startReadingRecs(SampleChannel* ch, bool gui=true); void glue_stopReadingRecs(SampleChannel* ch, bool gui=true); #endif giada-0.14.5/src/glue/io.cpp000066400000000000000000000217531322662744500155270ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include #include "../gui/dialogs/gd_mainWindow.h" #include "../gui/dialogs/gd_warnings.h" #include "../gui/elems/mainWindow/mainTransport.h" #include "../gui/elems/mainWindow/mainTimer.h" #include "../gui/elems/mainWindow/keyboard/keyboard.h" #include "../gui/elems/mainWindow/keyboard/channel.h" #include "../gui/elems/mainWindow/keyboard/sampleChannel.h" #include "../utils/gui.h" #include "../utils/log.h" #include "../core/recorder.h" #include "../core/kernelAudio.h" #include "../core/mixer.h" #include "../core/mixerHandler.h" #include "../core/wave.h" #include "../core/channel.h" #include "../core/clock.h" #include "../core/sampleChannel.h" #include "../core/midiChannel.h" #include "main.h" #include "channel.h" #include "transport.h" #include "io.h" extern gdMainWindow *G_MainWin; using namespace giada::m; void glue_keyPress(Channel *ch, bool ctrl, bool shift) { if (ch->type == CHANNEL_SAMPLE) glue_keyPress((SampleChannel*)ch, ctrl, shift); else glue_keyPress((MidiChannel*)ch, ctrl, shift); } /* -------------------------------------------------------------------------- */ void glue_keyRelease(Channel *ch, bool ctrl, bool shift) { if (ch->type == CHANNEL_SAMPLE) glue_keyRelease((SampleChannel*)ch, ctrl, shift); } /* -------------------------------------------------------------------------- */ void glue_keyPress(MidiChannel *ch, bool ctrl, bool shift) { if (ctrl) glue_toggleMute(ch); else if (shift) ch->kill(0); // on frame 0: user-generated event else ch->start(0, true, clock::getQuantize(), clock::isRunning(), false, true); // on frame 0: user-generated event } /* -------------------------------------------------------------------------- */ void glue_keyPress(SampleChannel *ch, bool ctrl, bool shift) { /* case CTRL */ if (ctrl) glue_toggleMute(ch); /* case SHIFT * * action recording on: * if seq is playing, rec a killchan * action recording off: * if chan has recorded events: * | if seq is playing OR channel 'c' is stopped, de/activate recs * | else kill chan * else kill chan */ else if (shift) { if (recorder::active) { if (clock::isRunning()) { ch->kill(0); // on frame 0: user-generated event if (recorder::canRec(ch, clock::isRunning(), mixer::recording) && !(ch->mode & LOOP_ANY)) { // don't record killChan actions for LOOP channels recorder::rec(ch->index, G_ACTION_KILL, clock::getCurrentFrame()); ch->hasActions = true; } } } else { if (ch->hasActions) { if (clock::isRunning() || ch->status == STATUS_OFF) ch->readActions ? glue_stopReadingRecs(ch) : glue_startReadingRecs(ch); else ch->kill(0); // on frame 0: user-generated event } else ch->kill(0); // on frame 0: user-generated event } } else { /* case no modifier */ /* record now if the quantizer is off, otherwise let mixer to handle it * when a quantoWait has passed. Moreover, KEYPRESS and KEYREL are * meaningless for loop modes */ if (clock::getQuantize() == 0 && recorder::canRec(ch, clock::isRunning(), mixer::recording) && !(ch->mode & LOOP_ANY)) { if (ch->mode == SINGLE_PRESS) { recorder::startOverdub(ch->index, G_ACTION_KEYS, clock::getCurrentFrame(), kernelAudio::getRealBufSize()); ch->readActions = false; // don't read actions while overdubbing } else { recorder::rec(ch->index, G_ACTION_KEYPRESS, clock::getCurrentFrame()); ch->hasActions = true; /* Why return here? You record an action (as done on line 148) and then you call ch->start (line 165): Mixer, which is on another thread, reads your newly recorded action if you have readActions == true, and then ch->start kicks in right after it (as done on line 165). The result: Mixer plays the channel (due to the new action) but ch->start kills it right away (because the sample is playing). Fix: call ch->start only if you are not recording anything, i.e. let Mixer play it. */ if (ch->readActions) return; } } /* This is a user-generated event, so it's on frame 0 */ ch->start(0, true, clock::getQuantize(), clock::isRunning(), false, true); } /* the GUI update is done by gui_refresh() */ } /* -------------------------------------------------------------------------- */ void glue_keyRelease(SampleChannel *ch, bool ctrl, bool shift) { if (ctrl || shift) return; ch->stop(); /* record a key release only if channel is single_press. For any * other mode the KEY REL is meaningless. */ if (ch->mode == SINGLE_PRESS && recorder::canRec(ch, clock::isRunning(), mixer::recording)) recorder::stopOverdub(clock::getCurrentFrame(), clock::getTotalFrames(), &mixer::mutex_recs); /* the GUI update is done by gui_refresh() */ } /* -------------------------------------------------------------------------- */ void glue_startStopActionRec(bool gui) { recorder::active ? glue_stopActionRec(gui) : glue_startActionRec(gui); } /* -------------------------------------------------------------------------- */ void glue_startActionRec(bool gui) { if (kernelAudio::getStatus() == false) return; recorder::active = true; if (!clock::isRunning()) glue_startSeq(false); // update gui ayway if (!gui) { Fl::lock(); G_MainWin->mainTransport->updateRecAction(1); Fl::unlock(); } } /* -------------------------------------------------------------------------- */ void glue_stopActionRec(bool gui) { /* stop the recorder and sort new actions */ recorder::active = false; recorder::sortActions(); for (unsigned i=0; itype == CHANNEL_MIDI) continue; SampleChannel *ch = (SampleChannel*) mixer::channels.at(i); G_MainWin->keyboard->setChannelWithActions((geSampleChannel*)ch->guiChannel); if (!ch->readActions && ch->hasActions) glue_startReadingRecs(ch, false); } if (!gui) { Fl::lock(); G_MainWin->mainTransport->updateRecAction(0); Fl::unlock(); } gu_refreshActionEditor(); // in case it's open } /* -------------------------------------------------------------------------- */ void glue_startStopInputRec(bool gui) { if (mixer::recording) glue_stopInputRec(gui); else if (!glue_startInputRec(gui)) gdAlert("No channels armed/available for audio recording."); } /* -------------------------------------------------------------------------- */ int glue_startInputRec(bool gui) { if (kernelAudio::getStatus() == false) return false; if (!mh::startInputRec()) { Fl::lock(); G_MainWin->mainTransport->updateRecInput(0); // set it off, anyway Fl::unlock(); return false; } if (!clock::isRunning()) glue_startSeq(false); // update gui anyway Fl::lock(); if (!gui) G_MainWin->mainTransport->updateRecInput(1); G_MainWin->mainTimer->setLock(true); Fl::unlock(); /* Update sample name inside sample channels' main button. This is useless for midi channel, but let's do it anyway. */ for (unsigned i=0; iguiChannel->update(); return true; } /* -------------------------------------------------------------------------- */ int glue_stopInputRec(bool gui) { mh::stopInputRec(); /* Start all sample channels in loop mode that were armed, i.e. that were recording stuff and not yet in play. They are also started in force mode, i.e. they must start playing right away at the current frame, not at the next first beat. */ for (Channel* ch : mixer::channels) { if (ch->type == CHANNEL_MIDI) continue; SampleChannel* sch = static_cast(ch); if (sch->mode & (LOOP_ANY) && sch->status == STATUS_OFF && sch->isArmed()) sch->start(clock::getCurrentFrame(), true, clock::getQuantize(), clock::isRunning(), true, true); } Fl::lock(); if (!gui) G_MainWin->mainTransport->updateRecInput(0); G_MainWin->mainTimer->setLock(false); Fl::unlock(); return 1; } giada-0.14.5/src/glue/io.h000066400000000000000000000050421322662744500151650ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * glue * Intermediate layer GUI <-> CORE. * * How to know if you need another glue_ function? Ask yourself if the * new action will ever be called via MIDI or keyboard/mouse. If yes, * put it here. * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef G_GLUE_IO_H #define G_GLUE_IO_H /* keyPress / keyRelease * handle the key pressure, either via mouse/keyboard or MIDI. If gui * is true it means that the event comes from the main window (mouse, * keyb or MIDI), otherwise the event comes from the action recorder. */ void glue_keyPress (class Channel *ch, bool ctrl=0, bool shift=0); void glue_keyPress (class SampleChannel *ch, bool ctrl=0, bool shift=0); void glue_keyPress (class MidiChannel *ch, bool ctrl=0, bool shift=0); void glue_keyRelease(class Channel *ch, bool ctrl=0, bool shift=0); void glue_keyRelease(class SampleChannel *ch, bool ctrl=0, bool shift=0); /* start/stopActionRec Handles the action recording. If gui == true the signal comes from an user interaction, otherwise it's a MIDI/Jack/external signal. */ void glue_startStopActionRec(bool gui=true); void glue_startActionRec(bool gui=true); void glue_stopActionRec(bool gui=true); /* start/stopInputRec Handles the input recording (take). If gui == true the signal comes from an internal interaction on the GUI, otherwise it's a MIDI/Jack/external signal. */ void glue_startStopInputRec(bool gui=true); int glue_startInputRec (bool gui=true); int glue_stopInputRec (bool gui=true); #endif giada-0.14.5/src/glue/main.cpp000066400000000000000000000144571322662744500160470ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include #include #include "../gui/elems/mainWindow/mainIO.h" #include "../gui/elems/mainWindow/mainTimer.h" #include "../gui/elems/mainWindow/keyboard/sampleChannel.h" #include "../gui/elems/mainWindow/keyboard/keyboard.h" #include "../gui/dialogs/gd_mainWindow.h" #include "../utils/gui.h" #include "../utils/string.h" #include "../utils/log.h" #include "../core/mixerHandler.h" #include "../core/mixer.h" #include "../core/midiChannel.h" #include "../core/clock.h" #include "../core/kernelMidi.h" #include "../core/kernelAudio.h" #include "../core/conf.h" #ifdef WITH_VST #include "../core/pluginHost.h" #endif #include "main.h" extern gdMainWindow *G_MainWin; using namespace giada::m; void glue_setBpm(const char *v1, const char *v2) { /* Never change this stuff while recording audio */ if (mixer::recording) return; char bpmS[6]; float bpmF = atof(v1) + (atof(v2)/10); if (bpmF < 20.0f) { bpmF = 20.0f; sprintf(bpmS, "20.0"); } else sprintf(bpmS, "%s.%s", v1, !strcmp(v2, "") ? "0" : v2); /* a value such as atof("120.1") will never be 120.1 but 120.0999999, * because of the rounding error. So we pass the real "wrong" value to * G_Mixer and we show the nice looking (but fake) one to the GUI. */ float oldBpmF = clock::getBpm(); clock::setBpm(bpmF); recorder::updateBpm(oldBpmF, bpmF, clock::getQuanto()); mixer::allocVirtualInput(clock::getTotalFrames()); #ifdef __linux__ kernelAudio::jackSetBpm(clock::getBpm()); #endif gu_refreshActionEditor(); G_MainWin->mainTimer->setBpm(bpmS); gu_log("[glue] Bpm changed to %s (real=%f)\n", bpmS, clock::getBpm()); } /* -------------------------------------------------------------------------- */ void glue_setBpm(float v) { if (v < G_MIN_BPM || v > G_MAX_BPM) v = G_DEFAULT_BPM; double fIpart; double fPpart = modf(v, &fIpart); int iIpart = fIpart; int iPpart = ceilf(fPpart); glue_setBpm(gu_iToString(iIpart).c_str(), gu_iToString(iPpart).c_str()); } /* -------------------------------------------------------------------------- */ void glue_setBeats(int beats, int bars, bool expand) { /* Never change this stuff while recording audio */ if (mixer::recording) return; /* Temp vars to store old data (they are necessary) */ int oldBeats = clock::getBeats(); unsigned oldTotalFrames = clock::getTotalFrames(); clock::setBeats(beats); clock::setBars(bars); clock::updateFrameBars(); mixer::allocVirtualInput(clock::getTotalFrames()); /* Update recorded actions, if 'expand' required and an expansion is taking place. */ if (expand && clock::getBeats() > oldBeats) recorder::expand(oldTotalFrames, clock::getTotalFrames()); G_MainWin->mainTimer->setMeter(clock::getBeats(), clock::getBars()); gu_refreshActionEditor(); // in case the action editor is open } /* -------------------------------------------------------------------------- */ void glue_rewindSeq(bool gui, bool notifyJack) { mh::rewindSequencer(); /* FIXME - potential desync when Quantizer is enabled from this point on. Mixer would wait, while the following calls would be made regardless of its state. */ #ifdef __linux__ if (notifyJack) kernelAudio::jackSetPosition(0); #endif if (conf::midiSync == MIDI_SYNC_CLOCK_M) kernelMidi::send(MIDI_POSITION_PTR, 0, 0); } /* -------------------------------------------------------------------------- */ void glue_quantize(int val) { clock::setQuantize(val); } /* -------------------------------------------------------------------------- */ void glue_setOutVol(float v, bool gui) { mixer::outVol = v; if (!gui) { Fl::lock(); G_MainWin->mainIO->setOutVol(v); Fl::unlock(); } } /* -------------------------------------------------------------------------- */ void glue_setInVol(float v, bool gui) { mixer::inVol = v; if (!gui) { Fl::lock(); G_MainWin->mainIO->setInVol(v); Fl::unlock(); } } /* -------------------------------------------------------------------------- */ void glue_clearAllSamples() { clock::stop(); for (unsigned i=0; iempty(); mixer::channels.at(i)->guiChannel->reset(); } recorder::init(); return; } /* -------------------------------------------------------------------------- */ void glue_clearAllRecs() { recorder::init(); gu_updateControls(); } /* -------------------------------------------------------------------------- */ void glue_resetToInitState(bool resetGui, bool createColumns) { gu_closeAllSubwindows(); mixer::close(); clock::init(conf::samplerate, conf::midiTCfps); mixer::init(clock::getTotalFrames(), kernelAudio::getRealBufSize()); recorder::init(); #ifdef WITH_VST pluginHost::freeAllStacks(&mixer::channels, &mixer::mutex_plugins); #endif G_MainWin->keyboard->clear(); if (createColumns) G_MainWin->keyboard->init(); gu_updateMainWinLabel(G_DEFAULT_PATCH_NAME); if (resetGui) gu_updateControls(); } /* -------------------------------------------------------------------------- */ /* never expand or shrink recordings (last param of setBeats = false): * this is live manipulation */ void glue_beatsMultiply() { glue_setBeats(clock::getBeats() * 2, clock::getBars(), false); } void glue_beatsDivide() { glue_setBeats(clock::getBeats() / 2, clock::getBars(), false); } giada-0.14.5/src/glue/main.h000066400000000000000000000037741322662744500155140ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * glue * Intermediate layer GUI <-> CORE. * * How to know if you need another glue_ function? Ask yourself if the * new action will ever be called via MIDI or keyboard/mouse. If yes, * put it here. * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef G_GLUE_MAIN_H #define G_GLUE_MAIN_H void glue_setBpm(const char *v1, const char *v2); void glue_setBpm(float v); void glue_setBeats(int beats, int bars, bool expand); void glue_quantize(int val); void glue_setOutVol(float v, bool gui=true); void glue_setInVol(float v, bool gui=true); void glue_clearAllSamples(); void glue_clearAllRecs(); /* resetToInitState * reset Giada to init state. If resetGui also refresh all widgets. If * createColumns also build initial empty columns. */ void glue_resetToInitState(bool resetGui=true, bool createColumns=true); /* beatsDivide/Multiply * shrinks or enlarges the number of beats by 2. */ void glue_beatsMultiply(); void glue_beatsDivide(); #endif giada-0.14.5/src/glue/plugin.cpp000066400000000000000000000113051322662744500164060ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifdef WITH_VST #include "../core/pluginHost.h" #include "../core/mixer.h" #include "../core/plugin.h" #include "../core/channel.h" #include "../core/const.h" #include "../core/conf.h" #include "../utils/gui.h" #include "../gui/dialogs/gd_mainWindow.h" #include "../gui/dialogs/pluginWindow.h" #include "../gui/dialogs/pluginList.h" #include "../gui/dialogs/gd_warnings.h" #include "../gui/dialogs/gd_config.h" #include "../gui/dialogs/browser/browserDir.h" #include "plugin.h" extern gdMainWindow* G_MainWin; using namespace giada::m; namespace giada { namespace c { namespace plugin { namespace { /* getPluginWindow Returns the plugInWindow (GUI-less one) with the parameter list. It might be nullptr if there is no plug-in window shown on screen. */ gdPluginWindow* getPluginWindow(const Plugin* p) { /* Get the parent window first: the plug-in list. Then, if it exists, get the child window - the actual pluginWindow. */ gdPluginList* parent = static_cast(gu_getSubwindow(G_MainWin, WID_FX_LIST)); if (parent == nullptr) return nullptr; return static_cast(gu_getSubwindow(parent, p->getId() + 1)); } } // {anonymous} /* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */ Plugin* addPlugin(Channel* ch, int index, int stackType) { if (index >= pluginHost::countAvailablePlugins()) return nullptr; return pluginHost::addPlugin(index, stackType, &mixer::mutex_plugins, ch); } /* -------------------------------------------------------------------------- */ void swapPlugins(Channel* ch, int index1, int index2, int stackType) { pluginHost::swapPlugin(index1, index2, stackType, &mixer::mutex_plugins, ch); } /* -------------------------------------------------------------------------- */ void freePlugin(Channel* ch, int index, int stackType) { pluginHost::freePlugin(index, stackType, &mixer::mutex_plugins, ch); } /* -------------------------------------------------------------------------- */ void setProgram(Plugin* p, int index) { p->setCurrentProgram(index); /* No need to update plug-in editor if it has one: the plug-in's editor takes care of it on its own. Conversely, update the specific parameter for UI-less plug-ins. */ if (p->hasEditor()) return; gdPluginWindow* child = getPluginWindow(p); if (child == nullptr) return; child->updateParameters(true); } /* -------------------------------------------------------------------------- */ void setParameter(Plugin* p, int index, float value, bool gui) { p->setParameter(index, value); /* No need to update plug-in editor if it has one: the plug-in's editor takes care of it on its own. Conversely, update the specific parameter for UI-less plug-ins. */ if (p->hasEditor()) return; gdPluginWindow* child = getPluginWindow(p); if (child == nullptr) return; Fl::lock(); child->updateParameter(index, !gui); Fl::unlock(); } /* -------------------------------------------------------------------------- */ void setPluginPathCb(void* data) { gdBrowserDir* browser = (gdBrowserDir*) data; if (browser->getCurrentPath() == "") { gdAlert("Invalid path."); return; } if (!conf::pluginPath.empty() && conf::pluginPath.back() != ';') conf::pluginPath += ";"; conf::pluginPath += browser->getCurrentPath(); browser->do_callback(); gdConfig* configWin = static_cast(gu_getSubwindow(G_MainWin, WID_CONFIG)); configWin->refreshVstPath(); } }}}; // giada::c::plugin:: #endif giada-0.14.5/src/glue/plugin.h000066400000000000000000000034461322662744500160620ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * glue * Intermediate layer GUI <-> CORE. * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef G_GLUE_PLUGIN_H #define G_GLUE_PLUGIN_H #ifdef WITH_VST class Plugin; class Channel; namespace giada { namespace c { namespace plugin { Plugin* addPlugin(Channel* ch, int index, int stackType); void swapPlugins(Channel* ch, int indexP1, int indexP2, int stackType); void freePlugin(Channel* ch, int index, int stackType); void setParameter(Plugin* p, int index, float value, bool gui=true); void setProgram(Plugin* p, int index); /* setPluginPathCb Callback attached to the DirBrowser for adding new Plug-in search paths in the configuration window. */ void setPluginPathCb(void* data); }}}; // giada::c::plugin:: #endif #endif giada-0.14.5/src/glue/recorder.cpp000066400000000000000000000137351322662744500167260ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include "../gui/dialogs/gd_warnings.h" #include "../gui/elems/mainWindow/keyboard/channel.h" #include "../gui/elems/mainWindow/keyboard/sampleChannel.h" #include "../core/const.h" #include "../core/clock.h" #include "../core/kernelMidi.h" #include "../core/channel.h" #include "../core/recorder.h" #include "../utils/gui.h" #include "../utils/log.h" #include "recorder.h" using std::vector; using namespace giada; namespace giada { namespace c { namespace recorder { namespace { void updateChannel(geChannel* gch) { gch->ch->hasActions = m::recorder::hasActions(gch->ch->index); if (gch->ch->type == CHANNEL_SAMPLE && !gch->ch->hasActions) static_cast(gch)->hideActionButton(); /* TODO - set mute=false */ gu_refreshActionEditor(); // refresh a.editor window, it could be open } }; // {namespace} /* -------------------------------------------------------------------------- */ void clearAllActions(geChannel* gch) { if (!gdConfirmWin("Warning", "Clear all actions: are you sure?")) return; m::recorder::clearChan(gch->ch->index); updateChannel(gch); } /* -------------------------------------------------------------------------- */ void clearVolumeActions(geChannel* gch) { if (!gdConfirmWin("Warning", "Clear all volume actions: are you sure?")) return; m::recorder::clearAction(gch->ch->index, G_ACTION_VOLUME); updateChannel(gch); } /* -------------------------------------------------------------------------- */ void clearStartStopActions(geChannel* gch) { if (!gdConfirmWin("Warning", "Clear all start/stop actions: are you sure?")) return; m::recorder::clearAction(gch->ch->index, G_ACTION_KEYPRESS | G_ACTION_KEYREL | G_ACTION_KILL); updateChannel(gch); } /* -------------------------------------------------------------------------- */ void clearMuteActions(geChannel* gch) { if (!gdConfirmWin("Warning", "Clear all mute actions: are you sure?")) return; m::recorder::clearAction(gch->ch->index, G_ACTION_MUTEON | G_ACTION_MUTEOFF); updateChannel(gch); } /* -------------------------------------------------------------------------- */ void recordMidiAction(int chan, int note, int frame_a, int frame_b) { if (frame_b == 0) frame_b = frame_a + G_DEFAULT_MIDI_ACTION_SIZE; /* Avoid frame overflow. */ int overflow = frame_b - m::clock::getTotalFrames(); if (overflow > 0) { frame_b -= overflow; frame_a -= overflow; } /* Prepare MIDI events, with maximum velocity (0x3F) for now. */ m::MidiEvent event_a = m::MidiEvent(m::MidiEvent::NOTE_ON, note, 0x3F); m::MidiEvent event_b = m::MidiEvent(m::MidiEvent::NOTE_OFF, note, 0x3F); /* Avoid overlapping actions. Find the next action past frame_a and compare its frame: if smaller than frame_b, an overlap occurs. Shrink the new action accordingly. */ m::recorder::action* next = nullptr; m::recorder::getNextAction(chan, G_ACTION_MIDI, frame_a, &next, event_a.getRaw(), 0x0000FF00); if (next != nullptr && next->frame <= frame_b) { frame_b = next->frame - 2; gu_log("[recorder::recordMidiAction] Shrink new action, due to overlap\n"); } m::recorder::rec(chan, G_ACTION_MIDI, frame_a, event_a.getRaw()); m::recorder::rec(chan, G_ACTION_MIDI, frame_b, event_b.getRaw()); } /* -------------------------------------------------------------------------- */ vector getMidiActions(int chan, int frameLimit) { vector out; m::recorder::sortActions(); for (unsigned i=0; i frameLimit) continue; for (unsigned j=0; jiValue); /* Skip action if: - does not belong to this channel - is not a MIDI action (we only want MIDI things here) - is not a MIDI Note On type. We don't want any other kind of action here */ if (a1->chan != chan || a1->type != G_ACTION_MIDI || a1midi.getStatus() != m::MidiEvent::NOTE_ON) continue; /* Prepare the composite action. Action 1 exists for sure, so fill it up right away. */ m::recorder::Composite cmp; cmp.a1 = *a1; /* Search for the next action. Must have: same channel, G_ACTION_MIDI, greater than a1->frame and with MIDI properties of note_off (0x80), same note of a1 and random velocity: we don't care about it (and so we mask it with 0x0000FF00). */ m::MidiEvent a2midi(m::MidiEvent::NOTE_OFF, a1midi.getNote(), 0x0); m::recorder::getNextAction(chan, G_ACTION_MIDI, a1->frame, &a2, a2midi.getRaw(), 0x0000FF00); /* If action 2 has been found, add it to the composite duo. Otherwise set the action 2 frame to -1: it should be intended as "orphaned". */ if (a2 != nullptr) cmp.a2 = *a2; else cmp.a2.frame = -1; out.push_back(cmp); } } return out; } }}} // giada::c::recorder::giada-0.14.5/src/glue/recorder.h000066400000000000000000000036771322662744500163770ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef G_GLUE_RECORDER_H #define G_GLUE_RECORDER_H #include #include "../core/recorder.h" class geChannel; namespace giada { namespace c { namespace recorder { void clearAllActions(geChannel *gch); void clearMuteActions(geChannel *gch); void clearVolumeActions(geChannel *gch); void clearStartStopActions(geChannel *gch); /* recordMidiAction Records a new MIDI action at frame_a. If frame_b == 0, uses the default action size. This function is designed for the Piano Roll (not for live recording). */ void recordMidiAction(int chan, int note, int frame_a, int frame_b=0); /* getMidiActions Returns a list of Composite actions, ready to be displayed in a MIDI note editor as pairs of NoteOn+NoteOff. */ std::vector getMidiActions(int channel, int frameLimit); }}} // giada::c::recorder:: #endif giada-0.14.5/src/glue/sampleEditor.cpp000066400000000000000000000177721322662744500175560ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include #include #include "../gui/dialogs/gd_mainWindow.h" #include "../gui/dialogs/sampleEditor.h" #include "../gui/dialogs/gd_warnings.h" #include "../gui/elems/basics/button.h" #include "../gui/elems/sampleEditor/waveTools.h" #include "../gui/elems/sampleEditor/volumeTool.h" #include "../gui/elems/sampleEditor/boostTool.h" #include "../gui/elems/sampleEditor/panTool.h" #include "../gui/elems/sampleEditor/pitchTool.h" #include "../gui/elems/sampleEditor/rangeTool.h" #include "../gui/elems/sampleEditor/shiftTool.h" #include "../gui/elems/sampleEditor/waveform.h" #include "../gui/elems/mainWindow/keyboard/channel.h" #include "../core/sampleChannel.h" #include "../core/waveFx.h" #include "../core/wave.h" #include "../core/waveManager.h" #include "../core/const.h" #include "../utils/gui.h" #include "../utils/log.h" #include "channel.h" #include "sampleEditor.h" extern gdMainWindow *G_MainWin; using namespace giada::m; namespace giada { namespace c { namespace sampleEditor { namespace { /* m_waveBuffer A Wave used during cut/copy/paste operations. */ Wave* m_waveBuffer = nullptr; }; // {anonymous} /* -------------------------------------------------------------------------- */ gdSampleEditor* getSampleEditorWindow() { gdSampleEditor* se = static_cast(gu_getSubwindow(G_MainWin, WID_SAMPLE_EDITOR)); assert(se != nullptr); return se; } /* -------------------------------------------------------------------------- */ void setBeginEnd(SampleChannel* ch, int b, int e) { ch->setBegin(b); ch->setEnd(e); gdSampleEditor* gdEditor = getSampleEditorWindow(); Fl::lock(); gdEditor->rangeTool->refresh(); Fl::unlock(); gdEditor->waveTools->waveform->recalcPoints(); gdEditor->waveTools->waveform->clearSel(); gdEditor->waveTools->waveform->redraw(); } /* -------------------------------------------------------------------------- */ void cut(SampleChannel* ch, int a, int b) { copy(ch, a, b); if (!wfx::cut(ch->wave, a, b)) { gdAlert("Unable to cut the sample!"); return; } setBeginEnd(ch, ch->getBegin(), ch->getEnd()); gdSampleEditor* gdEditor = getSampleEditorWindow(); gdEditor->waveTools->waveform->clearSel(); gdEditor->waveTools->waveform->refresh(); gdEditor->updateInfo(); } /* -------------------------------------------------------------------------- */ void copy(SampleChannel* ch, int a, int b) { if (m_waveBuffer != nullptr) delete m_waveBuffer; int result = waveManager::createFromWave(ch->wave, a, b, &m_waveBuffer); if (result != G_RES_OK) { gu_log("[sampleEditor::copy] unable to create wave buffer!\n"); return; } } /* -------------------------------------------------------------------------- */ void paste(SampleChannel* ch, int a) { if (!isWaveBufferFull()) { gu_log("[sampleEditor::paste] Buffer is empty, nothing to paste\n"); return; } wfx::paste(m_waveBuffer, ch->wave, a); /* Shift begin/end points to keep the previous position. */ int delta = m_waveBuffer->getSize(); if (a < ch->getBegin() && a < ch->getEnd()) setBeginEnd(ch, ch->getBegin() + delta, ch->getEnd() + delta); else if (a < ch->getEnd()) setBeginEnd(ch, ch->getBegin(), ch->getEnd() + delta); gdSampleEditor* gdEditor = getSampleEditorWindow(); gdEditor->waveTools->waveform->clearSel(); gdEditor->waveTools->waveform->refresh(); gdEditor->updateInfo(); } /* -------------------------------------------------------------------------- */ void silence(SampleChannel* ch, int a, int b) { wfx::silence(ch->wave, a, b); gdSampleEditor* gdEditor = getSampleEditorWindow(); gdEditor->waveTools->waveform->refresh(); } /* -------------------------------------------------------------------------- */ void fade(SampleChannel* ch, int a, int b, int type) { wfx::fade(ch->wave, a, b, type); gdSampleEditor* gdEditor = getSampleEditorWindow(); gdEditor->waveTools->waveform->refresh(); } /* -------------------------------------------------------------------------- */ void smoothEdges(SampleChannel* ch, int a, int b) { wfx::smooth(ch->wave, a, b); gdSampleEditor* gdEditor = getSampleEditorWindow(); gdEditor->waveTools->waveform->refresh(); } /* -------------------------------------------------------------------------- */ void reverse(SampleChannel* ch, int a, int b) { wfx::reverse(ch->wave, a, b); gdSampleEditor* gdEditor = getSampleEditorWindow(); gdEditor->waveTools->waveform->refresh(); } /* -------------------------------------------------------------------------- */ void normalizeHard(SampleChannel* ch, int a, int b) { wfx::normalizeHard(ch->wave, a, b); gdSampleEditor* gdEditor = getSampleEditorWindow(); gdEditor->waveTools->waveform->refresh(); } /* -------------------------------------------------------------------------- */ void trim(SampleChannel* ch, int a, int b) { if (!wfx::trim(ch->wave, a, b)) { gdAlert("Unable to trim the sample!"); return; } setBeginEnd(ch, ch->getBegin(), ch->getEnd()); gdSampleEditor* gdEditor = getSampleEditorWindow(); gdEditor->waveTools->waveform->clearSel(); gdEditor->waveTools->waveform->refresh(); gdEditor->updateInfo(); } /* -------------------------------------------------------------------------- */ void setPlayHead(SampleChannel* ch, int f) { ch->setTrackerPreview(f); gdSampleEditor* gdEditor = getSampleEditorWindow(); gdEditor->waveTools->waveform->redraw(); } /* -------------------------------------------------------------------------- */ void setPreview(SampleChannel* ch, int mode) { ch->setPreviewMode(mode); gdSampleEditor* gdEditor = getSampleEditorWindow(); gdEditor->play->value(!gdEditor->play->value()); } /* -------------------------------------------------------------------------- */ void rewindPreview(SampleChannel* ch) { geWaveform* waveform = getSampleEditorWindow()->waveTools->waveform; if (waveform->isSelected() && ch->getTrackerPreview() != waveform->getSelectionA()) setPlayHead(ch, waveform->getSelectionA()); else setPlayHead(ch, 0); } /* -------------------------------------------------------------------------- */ void toNewChannel(SampleChannel* ch, int a, int b) { SampleChannel* newCh = static_cast(glue_addChannel( ch->guiChannel->getColumnIndex(), CHANNEL_SAMPLE, G_GUI_CHANNEL_H_1)); Wave* wave = nullptr; int result = waveManager::createFromWave(ch->wave, a, b, &wave); if (result != G_RES_OK) { gdAlert("Unable to copy to new channel!"); return; } newCh->pushWave(wave); newCh->guiChannel->update(); } /* -------------------------------------------------------------------------- */ bool isWaveBufferFull() { return m_waveBuffer != nullptr; } /* -------------------------------------------------------------------------- */ void shift(SampleChannel* ch, int offset) { wfx::shift(ch->wave, offset - ch->getShift()); ch->setShift(offset); gdSampleEditor* gdEditor = getSampleEditorWindow(); gdEditor->shiftTool->refresh(); gdEditor->waveTools->waveform->refresh(); } }}}; // giada::c::sampleEditor:: giada-0.14.5/src/glue/sampleEditor.h000066400000000000000000000045261322662744500172140ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef G_GLUE_SAMPLE_EDITOR_H #define G_GLUE_SAMPLE_EDITOR_H class SampleChannel; class geWaveform; namespace giada { namespace c { namespace sampleEditor { /* setBeginEnd Sets start/end points in the sample editor. */ void setBeginEnd(SampleChannel* ch, int b, int e); void cut(SampleChannel* ch, int a, int b); void copy(SampleChannel* ch, int a, int b); /* paste Pastes what's defined in m_copyBuffer into channel 'ch' at point 'a'. If m_copyBuffer is empty, does nothing. */ void paste(SampleChannel* ch, int a); void trim(SampleChannel* ch, int a, int b); void reverse(SampleChannel* ch, int a, int b); void normalizeHard(SampleChannel* ch, int a, int b); void silence(SampleChannel* ch, int a, int b); void fade(SampleChannel* ch, int a, int b, int type); void smoothEdges(SampleChannel* ch, int a, int b); void shift(SampleChannel* ch, int offset); bool isWaveBufferFull(); /* setPlayHead Changes playhead's position. Used in preview. */ void setPlayHead(SampleChannel* ch, int f); void setPreview(SampleChannel* ch, int mode); void rewindPreview(SampleChannel* ch); /* toNewChannel Copies the selected range into a new sample channel. */ void toNewChannel(SampleChannel* ch, int a, int b); }}}; // giada::c::sampleEditor:: #endif giada-0.14.5/src/glue/storage.cpp000066400000000000000000000267461322662744500165730ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include "../core/mixer.h" #include "../core/mixerHandler.h" #include "../core/channel.h" #include "../core/pluginHost.h" #include "../core/plugin.h" #include "../core/conf.h" #include "../core/patch.h" #include "../core/sampleChannel.h" #include "../core/midiChannel.h" #include "../core/waveManager.h" #include "../core/clock.h" #include "../core/wave.h" #include "../utils/gui.h" #include "../utils/log.h" #include "../utils/string.h" #include "../utils/fs.h" #include "../gui/elems/basics/progress.h" #include "../gui/elems/mainWindow/keyboard/column.h" #include "../gui/elems/mainWindow/keyboard/keyboard.h" #include "../gui/dialogs/gd_mainWindow.h" #include "../gui/dialogs/gd_warnings.h" #include "../gui/dialogs/browser/browserSave.h" #include "../gui/dialogs/browser/browserLoad.h" #include "main.h" #include "channel.h" #include "storage.h" extern gdMainWindow *G_MainWin; using std::string; using std::vector; using namespace giada::m; #ifdef WITH_VST static void glue_fillPatchGlobalsPlugins__(vector *host, vector *patch) { for (unsigned i=0; isize(); i++) { Plugin *pl = host->at(i); patch::plugin_t ppl; ppl.path = pl->getUniqueId(); ppl.bypass = pl->isBypassed(); int numParams = pl->getNumParameters(); for (int k=0; kgetParameter(k)); patch->push_back(ppl); } } #endif /* -------------------------------------------------------------------------- */ static void glue_fillPatchColumns__() { for (unsigned i=0; ikeyboard->getTotalColumns(); i++) { geColumn *gCol = G_MainWin->keyboard->getColumn(i); patch::column_t pCol; pCol.index = gCol->getIndex(); pCol.width = gCol->w(); for (int k=0; kcountChannels(); k++) { Channel *colChannel = gCol->getChannel(k); for (unsigned j=0; jindex); break; } } } patch::columns.push_back(pCol); } } /* -------------------------------------------------------------------------- */ static void glue_fillPatchChannels__(bool isProject) { for (unsigned i=0; iwritePatch(i, isProject); } } /* -------------------------------------------------------------------------- */ static void glue_fillPatchGlobals__(const string &name) { patch::version = G_VERSION_STR; patch::versionMajor = G_VERSION_MAJOR; patch::versionMinor = G_VERSION_MINOR; patch::versionPatch = G_VERSION_PATCH; patch::name = name; patch::bpm = clock::getBpm(); patch::bars = clock::getBars(); patch::beats = clock::getBeats(); patch::quantize = clock::getQuantize(); patch::masterVolIn = mixer::inVol; patch::masterVolOut = mixer::outVol; patch::metronome = mixer::metronome; #ifdef WITH_VST glue_fillPatchGlobalsPlugins__(pluginHost::getStack(pluginHost::MASTER_IN), &patch::masterInPlugins); glue_fillPatchGlobalsPlugins__(pluginHost::getStack(pluginHost::MASTER_OUT), &patch::masterOutPlugins); #endif } /* -------------------------------------------------------------------------- */ static bool glue_savePatch__(const string &fullPath, const string &name, bool isProject) { patch::init(); glue_fillPatchGlobals__(name); glue_fillPatchChannels__(isProject); glue_fillPatchColumns__(); if (patch::write(fullPath)) { gu_updateMainWinLabel(name); gu_log("[glue_savePatch] patch saved as %s\n", fullPath.c_str()); return true; } return false; } /* -------------------------------------------------------------------------- */ static string glue_makeSamplePath__(const string& base, const Wave* w, int k) { return base + G_SLASH + w->getBasename(false) + "-" + gu_iToString(k) + "." + w->getExtension(); } static string glue_makeUniqueSamplePath__(const string& base, const SampleChannel* ch) { string path = base + G_SLASH + ch->wave->getBasename(true); if (mh::uniqueSamplePath(ch, path)) return path; int k = 0; path = glue_makeSamplePath__(base, ch->wave, k); while (!mh::uniqueSamplePath(ch, path)) path = glue_makeSamplePath__(base, ch->wave, k++); return path; } /* -------------------------------------------------------------------------- */ void glue_savePatch(void* data) { gdBrowserSave* browser = (gdBrowserSave*) data; string name = gu_stripExt(browser->getName()); string fullPath = browser->getCurrentPath() + G_SLASH + name + ".gptc"; if (name == "") { gdAlert("Please choose a file name."); return; } if (gu_fileExists(fullPath)) if (!gdConfirmWin("Warning", "File exists: overwrite?")) return; if (glue_savePatch__(fullPath, name, false)) { // false == not a project conf::patchPath = gu_dirname(fullPath); browser->do_callback(); } else gdAlert("Unable to save the patch!"); } /* -------------------------------------------------------------------------- */ void glue_loadPatch(void* data) { gdBrowserLoad* browser = (gdBrowserLoad*) data; string fullPath = browser->getSelectedItem(); bool isProject = gu_isProject(browser->getSelectedItem()); browser->showStatusBar(); gu_log("[glue] loading %s...\n", fullPath.c_str()); string fileToLoad = fullPath; // patch file to read from string basePath = ""; // base path, in case of reading from a project if (isProject) { fileToLoad = fullPath + G_SLASH + gu_stripExt(gu_basename(fullPath)) + ".gptc"; basePath = fullPath + G_SLASH; } int res = patch::read(fileToLoad); if (res != PATCH_READ_OK) { if (res == PATCH_UNREADABLE) isProject ? gdAlert("This project is unreadable.") : gdAlert("This patch is unreadable."); else if (res == PATCH_INVALID) isProject ? gdAlert("This project is not valid.") : gdAlert("This patch is not valid."); browser->hideStatusBar(); return; } /* Close all other windows. This prevents segfault if plugin windows GUIs are open. */ gu_closeAllSubwindows(); /* Reset the system. False(1): don't update the gui right now. False(2): do not create empty columns. */ glue_resetToInitState(false, false); browser->setStatusBar(0.1f); /* Add common stuff, columns and channels. Also increment the progress bar by 0.8 / total_channels steps. */ float steps = 0.8 / patch::channels.size(); for (const patch::column_t& col : patch::columns) { G_MainWin->keyboard->addColumn(col.width); unsigned k = 0; for (const patch::channel_t& pch : patch::channels) { if (pch.column == col.index) { Channel* ch = glue_addChannel(pch.column, pch.type, pch.size); ch->readPatch(basePath, k, &mixer::mutex_plugins, conf::samplerate, conf::rsmpQuality); } browser->setStatusBar(steps); k++; } } /* Fill Mixer. */ mh::readPatch(); /* Let recorder recompute the actions' positions if the current samplerate != patch samplerate. */ recorder::updateSamplerate(conf::samplerate, patch::samplerate); /* Save patchPath by taking the last dir of the broswer, in order to reuse it the next time. */ conf::patchPath = gu_dirname(fullPath); /* Refresh GUI. */ gu_updateControls(); gu_updateMainWinLabel(patch::name); browser->setStatusBar(0.1f); gu_log("[glue] patch loaded successfully\n"); #ifdef WITH_VST if (pluginHost::hasMissingPlugins()) gdAlert("Some plugins were not loaded successfully.\nCheck the plugin browser to know more."); #endif browser->do_callback(); } /* -------------------------------------------------------------------------- */ void glue_saveProject(void* data) { gdBrowserSave* browser = (gdBrowserSave*) data; string name = gu_stripExt(browser->getName()); string folderPath = browser->getCurrentPath(); string fullPath = folderPath + G_SLASH + name + ".gprj"; if (name == "") { gdAlert("Please choose a project name."); return; } if (gu_isProject(fullPath) && !gdConfirmWin("Warning", "Project exists: overwrite?")) return; if (!gu_dirExists(fullPath) && !gu_mkdir(fullPath)) { gu_log("[glue_saveProject] Unable to make project directory!\n"); return; } gu_log("[glue_saveProject] Project dir created: %s\n", fullPath.c_str()); /* Copy all samples inside the folder. Takes and logical ones are saved via glue_saveSample(). Update the new sample path: everything now comes from the project folder (folderPath). Also make sure the file path is unique inside the project folder.*/ for (const Channel* ch : mixer::channels) { if (ch->type == CHANNEL_MIDI) continue; const SampleChannel* sch = static_cast(ch); if (sch->wave == nullptr) continue; sch->wave->setPath(glue_makeUniqueSamplePath__(fullPath, sch)); gu_log("[glue_saveProject] Save file to %s\n", sch->wave->getPath().c_str()); waveManager::save(sch->wave, sch->wave->getPath()); // TODO - error checking } string gptcPath = fullPath + G_SLASH + name + ".gptc"; if (glue_savePatch__(gptcPath, name, true)) // true == it's a project browser->do_callback(); else gdAlert("Unable to save the project!"); } /* -------------------------------------------------------------------------- */ void glue_loadSample(void* data) { gdBrowserLoad* browser = (gdBrowserLoad*) data; string fullPath = browser->getSelectedItem(); if (fullPath.empty()) return; int res = glue_loadChannel(static_cast(browser->getChannel()), fullPath); if (res == G_RES_OK) { conf::samplePath = gu_dirname(fullPath); browser->do_callback(); G_MainWin->delSubWindow(WID_SAMPLE_EDITOR); // if editor is open } else G_MainWin->keyboard->printChannelMessage(res); } /* -------------------------------------------------------------------------- */ void glue_saveSample(void *data) { gdBrowserSave* browser = (gdBrowserSave*) data; string name = browser->getName(); string folderPath = browser->getCurrentPath(); if (name == "") { gdAlert("Please choose a file name."); return; } /* bruteforce check extension. */ string filePath = folderPath + G_SLASH + gu_stripExt(name) + ".wav"; if (gu_fileExists(filePath)) if (!gdConfirmWin("Warning", "File exists: overwrite?")) return; SampleChannel* ch = static_cast(browser->getChannel()); if (waveManager::save(ch->wave, filePath)) { gu_log("[glue_saveSample] sample saved to %s\n", filePath.c_str()); conf::samplePath = gu_dirname(filePath); browser->do_callback(); } else gdAlert("Unable to save this sample!"); } giada-0.14.5/src/glue/storage.h000066400000000000000000000025251322662744500162250ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef G_GLUE_STORAGE_H #define G_GLUE_STORAGE_H void glue_loadPatch (void *data); void glue_savePatch (void *data); void glue_saveProject(void *data); void glue_saveSample (void *data); void glue_loadSample (void *data); #endif giada-0.14.5/src/glue/transport.cpp000066400000000000000000000056261322662744500171550ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include #include "../gui/elems/mainWindow/mainTransport.h" #include "../gui/dialogs/gd_mainWindow.h" #include "../core/clock.h" #include "../core/kernelAudio.h" #include "../core/mixerHandler.h" #include "../core/mixer.h" #include "../core/recorder.h" #include "transport.h" extern gdMainWindow *G_MainWin; using namespace giada::m; void glue_startStopSeq(bool gui) { clock::isRunning() ? glue_stopSeq(gui) : glue_startSeq(gui); } /* -------------------------------------------------------------------------- */ void glue_startSeq(bool gui) { clock::start(); #ifdef __linux__ kernelAudio::jackStart(); #endif if (!gui) { Fl::lock(); G_MainWin->mainTransport->updatePlay(1); Fl::unlock(); } } /* -------------------------------------------------------------------------- */ void glue_stopSeq(bool gui) { mh::stopSequencer(); #ifdef __linux__ kernelAudio::jackStop(); #endif /* what to do if we stop the sequencer and some action recs are active? * Deactivate the button and delete any 'rec on' status */ if (recorder::active) { recorder::active = false; Fl::lock(); G_MainWin->mainTransport->updateRecAction(0); Fl::unlock(); } /* if input recs are active (who knows why) we must deactivate them. * One might stop the sequencer while an input rec is running. */ if (mixer::recording) { mh::stopInputRec(); Fl::lock(); G_MainWin->mainTransport->updateRecInput(0); Fl::unlock(); } if (!gui) { Fl::lock(); G_MainWin->mainTransport->updatePlay(0); Fl::unlock(); } } /* -------------------------------------------------------------------------- */ void glue_startStopMetronome(bool gui) { mixer::metronome = !mixer::metronome; if (!gui) { Fl::lock(); G_MainWin->mainTransport->updateMetronome(mixer::metronome); Fl::unlock(); } } giada-0.14.5/src/glue/transport.h000066400000000000000000000030231322662744500166070ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef G_GLUE_TRANSPORT_H #define G_GLUE_TRANSPORT_H /* start, stop, rewind sequencer If gui == true the signal comes from an user interaction on the GUI, otherwise it's a MIDI/Jack/external signal. */ void glue_startStopSeq(bool gui=true); void glue_startSeq(bool gui=true); void glue_stopSeq(bool gui=true); void glue_rewindSeq(bool gui=true, bool notifyJack=true); void glue_startStopMetronome(bool gui=true); #endif giada-0.14.5/src/gui/000077500000000000000000000000001322662744500142345ustar00rootroot00000000000000giada-0.14.5/src/gui/dialogs/000077500000000000000000000000001322662744500156565ustar00rootroot00000000000000giada-0.14.5/src/gui/dialogs/beatsInput.cpp000066400000000000000000000057441322662744500205120ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include #include "../../utils/gui.h" #include "../../utils/string.h" #include "../../core/mixer.h" #include "../../core/clock.h" #include "../../core/conf.h" #include "../../core/const.h" #include "../../glue/main.h" #include "../elems/basics/input.h" #include "../elems/basics/button.h" #include "../elems/basics/check.h" #include "beatsInput.h" #include "gd_mainWindow.h" extern gdMainWindow* mainWin; using namespace giada::m; gdBeatsInput::gdBeatsInput() : gdWindow(180, 60, "Beats") { if (conf::beatsX) resize(conf::beatsX, conf::beatsY, w(), h()); set_modal(); beats = new geInput(8, 8, 43, G_GUI_UNIT); bars = new geInput(beats->x()+beats->w()+4, 8, 43, G_GUI_UNIT); ok = new geButton(bars->x()+bars->w()+4, 8, 70, G_GUI_UNIT, "Ok"); resizeRec = new geCheck(8, 40, 12, 12, "resize recorded actions"); end(); beats->maximum_size(2); beats->value(gu_iToString(clock::getBeats()).c_str()); beats->type(FL_INT_INPUT); bars->maximum_size(2); bars->value(gu_iToString(clock::getBars()).c_str()); bars->type(FL_INT_INPUT); ok->shortcut(FL_Enter); ok->callback(cb_update, (void*)this); resizeRec->value(conf::resizeRecordings); gu_setFavicon(this); setId(WID_BEATS); show(); } /* -------------------------------------------------------------------------- */ gdBeatsInput::~gdBeatsInput() { conf::beatsX = x(); conf::beatsY = y(); conf::resizeRecordings = resizeRec->value(); } /* -------------------------------------------------------------------------- */ void gdBeatsInput::cb_update(Fl_Widget* w, void* p) { ((gdBeatsInput*)p)->cb_update(); } /* -------------------------------------------------------------------------- */ void gdBeatsInput::cb_update() { if (!strcmp(beats->value(), "") || !strcmp(bars->value(), "")) return; glue_setBeats(atoi(beats->value()), atoi(bars->value()), resizeRec->value()); do_callback(); } giada-0.14.5/src/gui/dialogs/beatsInput.h000066400000000000000000000027231322662744500201510ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef GD_BEATSINPUT_H #define GD_BEATSINPUT_H #include "window.h" class geInput; class geButton; class geCheck; class gdBeatsInput : public gdWindow { private: static void cb_update(Fl_Widget* w, void* p); void cb_update(); geInput* beats; geInput* bars; geButton* ok; geCheck* resizeRec; public: gdBeatsInput(); ~gdBeatsInput(); }; #endif giada-0.14.5/src/gui/dialogs/bpmInput.cpp000066400000000000000000000054711322662744500201670ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include #include "../../core/conf.h" #include "../../core/const.h" #include "../../core/mixer.h" #include "../../core/clock.h" #include "../../glue/main.h" #include "../../utils/gui.h" #include "../../utils/string.h" #include "../elems/basics/button.h" #include "../elems/basics/input.h" #include "bpmInput.h" #include "gd_mainWindow.h" extern gdMainWindow *mainWin; using std::vector; using std::string; using namespace giada::m; gdBpmInput::gdBpmInput(const char* label) : gdWindow(144, 36, "Bpm") { if (conf::bpmX) resize(conf::bpmX, conf::bpmY, w(), h()); set_modal(); input_a = new geInput(8, 8, 30, G_GUI_UNIT); input_b = new geInput(42, 8, 20, G_GUI_UNIT); ok = new geButton(66, 8, 70, G_GUI_UNIT, "Ok"); end(); input_a->maximum_size(3); input_a->type(FL_INT_INPUT); input_a->value(gu_fToString(clock::getBpm(), 0).c_str()); /* Use the decimal value from the string label. */ vector tokens; gu_split(label, ".", &tokens); input_b->maximum_size(1); input_b->type(FL_INT_INPUT); input_b->value(tokens[1].c_str()); ok->shortcut(FL_Enter); ok->callback(cb_update, (void*)this); gu_setFavicon(this); setId(WID_BPM); show(); } /* -------------------------------------------------------------------------- */ gdBpmInput::~gdBpmInput() { conf::bpmX = x(); conf::bpmY = y(); } /* -------------------------------------------------------------------------- */ void gdBpmInput::cb_update(Fl_Widget* w, void* p) { ((gdBpmInput*)p)->cb_update(); } /* -------------------------------------------------------------------------- */ void gdBpmInput::cb_update() { if (strcmp(input_a->value(), "") == 0) return; glue_setBpm(input_a->value(), input_b->value()); do_callback(); } giada-0.14.5/src/gui/dialogs/bpmInput.h000066400000000000000000000027461322662744500176360ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef GD_BPMINPUT_H #define GD_BPMINPUT_H #include "window.h" class geInput; class geButton; class gdBpmInput : public gdWindow { private: static void cb_update(Fl_Widget* w, void* p); void cb_update(); geInput* input_a; geInput* input_b; geButton* ok; public: gdBpmInput(const char* label); // pointer to mainWin->timing->bpm->label() ~gdBpmInput(); }; #endif giada-0.14.5/src/gui/dialogs/browser/000077500000000000000000000000001322662744500173415ustar00rootroot00000000000000giada-0.14.5/src/gui/dialogs/browser/browserBase.cpp000066400000000000000000000127741322662744500223360ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include "../../../core/graphics.h" #include "../../../core/conf.h" #include "../../../core/const.h" #include "../../../utils/gui.h" #include "../../../utils/fs.h" #include "../../elems/browser.h" #include "../../elems/basics/button.h" #include "../../elems/basics/input.h" #include "../../elems/basics/progress.h" #include "../../elems/basics/check.h" #include "browserBase.h" using std::string; using namespace giada::m; gdBrowserBase::gdBrowserBase(int x, int y, int w, int h, const string& title, const string& path, void (*callback)(void*)) : gdWindow(x, y, w, h, title.c_str()), callback(callback) { set_non_modal(); groupTop = new Fl_Group(8, 8, w-16, 40); hiddenFiles = new geCheck(groupTop->x(), groupTop->y(), 400, 20, "Show hidden files"); where = new geInput(groupTop->x(), hiddenFiles->y()+hiddenFiles->h(), 20, 20); updir = new geButton(groupTop->x()+groupTop->w()-20, where->y(), 20, 20, "", updirOff_xpm, updirOn_xpm); groupTop->end(); groupTop->resizable(where); hiddenFiles->callback(cb_toggleHiddenFiles, (void*) this); where->readonly(true); where->cursor_color(G_COLOR_BLACK); where->value(path.c_str()); updir->callback(cb_up, (void*) this); browser = new geBrowser(8, groupTop->y()+groupTop->h()+8, w-16, h-93); browser->loadDir(path); if (path == conf::browserLastPath) browser->preselect(conf::browserPosition, conf::browserLastValue); Fl_Group *groupButtons = new Fl_Group(8, browser->y()+browser->h()+8, w-16, 20); ok = new geButton(w-88, groupButtons->y(), 80, 20); cancel = new geButton(w-ok->w()-96, groupButtons->y(), 80, 20, "Cancel"); status = new geProgress(8, groupButtons->y(), cancel->x()-16, 20); status->minimum(0); status->maximum(1); status->hide(); // show the bar only if necessary groupButtons->resizable(status); groupButtons->end(); end(); cancel->callback(cb_close, (void*) this); resizable(browser); size_range(320, 200); gu_setFavicon(this); show(); } /* -------------------------------------------------------------------------- */ gdBrowserBase::~gdBrowserBase() { conf::browserX = x(); conf::browserY = y(); conf::browserW = w(); conf::browserH = h(); conf::browserPosition = browser->position(); conf::browserLastPath = gu_dirname(browser->getSelectedItem()); conf::browserLastValue = browser->value(); } /* -------------------------------------------------------------------------- */ void gdBrowserBase::cb_up (Fl_Widget* v, void* p) { ((gdBrowserBase*)p)->cb_up(); } void gdBrowserBase::cb_close(Fl_Widget* v, void* p) { ((gdBrowserBase*)p)->cb_close(); } void gdBrowserBase::cb_toggleHiddenFiles(Fl_Widget *v, void *p) { ((gdBrowserBase*)p)->cb_toggleHiddenFiles(); } /* -------------------------------------------------------------------------- */ void gdBrowserBase::cb_up() { string dir = browser->getCurrentDir(); /* Take 'dir' path and remove all chars up to the next slash, e.g.: /path/to/my/dir -> /path/to/my Make sure not to remove the leading '/' (OS X/Linux only). */ dir = dir.substr(0, dir.find_last_of(G_SLASH_STR)); #if defined(G_OS_MAC) || defined(G_OS_LINUX) if (dir.empty()) dir = G_SLASH_STR; #endif browser->loadDir(dir); where->value(browser->getCurrentDir().c_str()); } /* -------------------------------------------------------------------------- */ void gdBrowserBase::cb_close() { do_callback(); } /* -------------------------------------------------------------------------- */ void gdBrowserBase::cb_toggleHiddenFiles() { browser->toggleHiddenFiles(); } /* -------------------------------------------------------------------------- */ void gdBrowserBase::setStatusBar(float v) { status->value(status->value() + v); Fl::wait(0); } /* -------------------------------------------------------------------------- */ void gdBrowserBase::showStatusBar() { status->show(); } /* -------------------------------------------------------------------------- */ void gdBrowserBase::hideStatusBar() { status->hide(); } /* -------------------------------------------------------------------------- */ string gdBrowserBase::getCurrentPath() const { return where->value(); } Channel* gdBrowserBase::getChannel() const { return channel; } string gdBrowserBase::getSelectedItem() const { return browser->getSelectedItem(); } /* -------------------------------------------------------------------------- */ void gdBrowserBase::fireCallback() const { callback((void*) this); } giada-0.14.5/src/gui/dialogs/browser/browserBase.h000066400000000000000000000045021322662744500217710ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef GD_BROWSER_BASE_H #define GD_BROWSER_BASE_H #include "../window.h" class Fl_Group; class Channel; class geCheck; class geBrowser; class geButton; class geInput; class geProgress; class gdBrowserBase : public gdWindow { protected: Channel* channel; Fl_Group* groupTop; geCheck* hiddenFiles; geBrowser* browser; geButton* ok; geButton* cancel; geInput* where; geButton* updir; geProgress* status; static void cb_up(Fl_Widget* v, void* p); static void cb_close(Fl_Widget* w, void* p); static void cb_toggleHiddenFiles(Fl_Widget* w, void* p); void cb_up(); void cb_close(); void cb_toggleHiddenFiles(); /* Callback * Fired when the save/load button is pressed. */ void (*callback)(void*); gdBrowserBase(int x, int y, int w, int h, const std::string& title, const std::string& path, void (*callback)(void*)); public: ~gdBrowserBase(); /* getSelectedItem * Return the full path of the selected file. */ std::string getSelectedItem() const; std::string getCurrentPath() const; Channel* getChannel() const; void fireCallback() const; /* setStatusBar * Increment status bar for progress tracking. */ void setStatusBar(float v); void showStatusBar(); void hideStatusBar(); }; #endif giada-0.14.5/src/gui/dialogs/browser/browserDir.cpp000066400000000000000000000047201322662744500221720ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include "../../../utils/fs.h" #include "../../elems/browser.h" #include "../../elems/basics/button.h" #include "../../elems/basics/input.h" #include "browserDir.h" using std::string; gdBrowserDir::gdBrowserDir(int x, int y, int w, int h, const string& title, const string& path, void (*callback)(void*)) : gdBrowserBase(x, y, w, h, title, path, callback) { where->size(groupTop->w()-updir->w()-8, 20); browser->callback(cb_down, (void*) this); ok->label("Select"); ok->callback(cb_load, (void*) this); ok->shortcut(FL_ENTER); /* On OS X the 'where' input doesn't get resized properly on startup. Let's force it. */ where->redraw(); } /* -------------------------------------------------------------------------- */ void gdBrowserDir::cb_load(Fl_Widget* v, void* p) { ((gdBrowserDir*)p)->cb_load(); } void gdBrowserDir::cb_down(Fl_Widget* v, void* p) { ((gdBrowserDir*)p)->cb_down(); } /* -------------------------------------------------------------------------- */ void gdBrowserDir::cb_load() { callback((void*) this); } /* -------------------------------------------------------------------------- */ void gdBrowserDir::cb_down() { string path = browser->getSelectedItem(); if (path.empty() || !gu_isDir(path)) // when click on an empty area or not a dir return; browser->loadDir(path); where->value(browser->getCurrentDir().c_str()); } giada-0.14.5/src/gui/dialogs/browser/browserDir.h000066400000000000000000000030121322662744500216300ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef GD_BROWSER_DIR_H #define GD_BROWSER_DIR_H #include "browserBase.h" class Channel; class gdBrowserDir : public gdBrowserBase { private: static void cb_load(Fl_Widget* w, void* p); static void cb_down(Fl_Widget* w, void* p); void cb_load(); void cb_down(); public: gdBrowserDir(int x, int y, int w, int h, const std::string& title, const std::string& path, void (*callback)(void*)); }; #endif giada-0.14.5/src/gui/dialogs/browser/browserLoad.cpp000066400000000000000000000047501322662744500223360ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include "../../../utils/fs.h" #include "../../elems/browser.h" #include "../../elems/basics/button.h" #include "../../elems/basics/input.h" #include "browserLoad.h" using std::string; gdBrowserLoad::gdBrowserLoad(int x, int y, int w, int h, const string& title, const string& path, void (*cb)(void*), Channel* ch) : gdBrowserBase(x, y, w, h, title, path, cb) { channel = ch; where->size(groupTop->w()-updir->w()-8, 20); browser->callback(cb_down, (void*) this); ok->label("Load"); ok->callback(cb_load, (void*) this); ok->shortcut(FL_ENTER); /* On OS X the 'where' input doesn't get resized properly on startup. Let's force it. */ where->redraw(); } /* -------------------------------------------------------------------------- */ void gdBrowserLoad::cb_load(Fl_Widget* v, void* p) { ((gdBrowserLoad*)p)->cb_load(); } void gdBrowserLoad::cb_down(Fl_Widget* v, void* p) { ((gdBrowserLoad*)p)->cb_down(); } /* -------------------------------------------------------------------------- */ void gdBrowserLoad::cb_load() { callback((void*) this); } /* -------------------------------------------------------------------------- */ void gdBrowserLoad::cb_down() { string path = browser->getSelectedItem(); if (path.empty() || !gu_isDir(path)) // when click on an empty area or not a dir return; browser->loadDir(path); where->value(browser->getCurrentDir().c_str()); } giada-0.14.5/src/gui/dialogs/browser/browserLoad.h000066400000000000000000000030341322662744500217750ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef GD_BROWSER_LOAD_H #define GD_BROWSER_LOAD_H #include "browserBase.h" class Channel; class gdBrowserLoad : public gdBrowserBase { private: static void cb_load(Fl_Widget* w, void* p); static void cb_down(Fl_Widget* v, void* p); void cb_load(); void cb_down(); public: gdBrowserLoad(int x, int y, int w, int h, const std::string& title, const std::string& path, void (*callback)(void*), Channel* ch); }; #endif giada-0.14.5/src/gui/dialogs/browser/browserSave.cpp000066400000000000000000000057421322662744500223570ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include "../../../utils/fs.h" #include "../../elems/browser.h" #include "../../elems/basics/button.h" #include "../../elems/basics/input.h" #include "browserSave.h" using std::string; gdBrowserSave::gdBrowserSave(int x, int y, int w, int h, const string& title, const string& path, const string& _name, void (*cb)(void*), Channel* ch) : gdBrowserBase(x, y, w, h, title, path, cb) { channel = ch; where->size(groupTop->w()-236, 20); name = new geInput(where->x()+where->w()+8, where->y(), 200, 20); name->value(_name.c_str()); groupTop->add(name); browser->callback(cb_down, (void*) this); ok->label("Save"); ok->callback(cb_save, (void*) this); ok->shortcut(FL_ENTER); /* On OS X the 'where' and 'name' inputs don't get resized properly on startup. Let's force them. */ where->redraw(); name->redraw(); } /* -------------------------------------------------------------------------- */ void gdBrowserSave::cb_save(Fl_Widget* v, void* p) { ((gdBrowserSave*)p)->cb_save(); } void gdBrowserSave::cb_down(Fl_Widget* v, void* p) { ((gdBrowserSave*)p)->cb_down(); } /* -------------------------------------------------------------------------- */ void gdBrowserSave::cb_down() { string path = browser->getSelectedItem(); if (path.empty()) // when click on an empty area return; /* if the selected item is a directory just load its content. If it's a file * use it as the file name (i.e. fill name->value()). */ if (gu_isDir(path)) { browser->loadDir(path); where->value(browser->getCurrentDir().c_str()); } else name->value(browser->getSelectedItem(false).c_str()); } /* -------------------------------------------------------------------------- */ string gdBrowserSave::getName() const { return name->value(); } /* -------------------------------------------------------------------------- */ void gdBrowserSave::cb_save() { callback((void*) this); } giada-0.14.5/src/gui/dialogs/browser/browserSave.h000066400000000000000000000031671322662744500220230ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef GD_BROWSER_SAVE_H #define GD_BROWSER_SAVE_H #include "browserBase.h" class Channel; class geInput; class gdBrowserSave : public gdBrowserBase { private: geInput* name; static void cb_down(Fl_Widget* v, void* p); static void cb_save(Fl_Widget* w, void* p); void cb_down(); void cb_save(); public: gdBrowserSave(int x, int y, int w, int h, const std::string& title, const std::string& path, const std::string& name, void (*callback)(void*), Channel* ch); std::string getName() const; }; #endif giada-0.14.5/src/gui/dialogs/channelNameInput.cpp000066400000000000000000000055661322662744500216270ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include "../../glue/channel.h" #include "../../utils/gui.h" #include "../../core/const.h" #include "../../core/conf.h" #include "../../core/channel.h" #include "../elems/basics/button.h" #include "../elems/basics/input.h" #include "channelNameInput.h" using namespace giada::m; gdChannelNameInput::gdChannelNameInput(Channel* ch) : gdWindow(400, 64, "New channel name"), m_ch (ch) { if (conf::nameX) resize(conf::nameX, conf::nameY, w(), h()); set_modal(); m_name = new geInput(G_GUI_OUTER_MARGIN, G_GUI_OUTER_MARGIN, w() - (G_GUI_OUTER_MARGIN * 2), G_GUI_UNIT); m_ok = new geButton(w() - 70 - G_GUI_OUTER_MARGIN, m_name->y()+m_name->h() + G_GUI_OUTER_MARGIN, 70, G_GUI_UNIT, "Ok"); m_cancel = new geButton(m_ok->x() - 70 - G_GUI_OUTER_MARGIN, m_ok->y(), 70, G_GUI_UNIT, "Cancel"); end(); m_name->value(m_ch->getName().c_str()); m_ok->shortcut(FL_Enter); m_ok->callback(cb_update, (void*)this); m_cancel->callback(cb_cancel, (void*)this); gu_setFavicon(this); setId(WID_SAMPLE_NAME); show(); } /* -------------------------------------------------------------------------- */ gdChannelNameInput::~gdChannelNameInput() { conf::nameX = x(); conf::nameY = y(); } /* -------------------------------------------------------------------------- */ void gdChannelNameInput::cb_update(Fl_Widget* w, void* p) { ((gdChannelNameInput*)p)->cb_update(); } void gdChannelNameInput::cb_cancel(Fl_Widget* w, void* p) { ((gdChannelNameInput*)p)->cb_cancel(); } /* -------------------------------------------------------------------------- */ void gdChannelNameInput::cb_cancel() { do_callback(); } /* -------------------------------------------------------------------------- */ void gdChannelNameInput::cb_update() { glue_setName(m_ch, m_name->value()); do_callback(); } giada-0.14.5/src/gui/dialogs/channelNameInput.h000066400000000000000000000031051322662744500212570ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef GD_CHANNEL_NAME_INPUT_H #define GD_CHANNEL_NAME_INPUT_H #include "window.h" class Channel; class geInput; class geButton; class gdChannelNameInput : public gdWindow { private: static void cb_update(Fl_Widget* w, void* p); static void cb_cancel(Fl_Widget* w, void* p); void cb_update(); void cb_cancel(); Channel* m_ch; geInput* m_name; geButton* m_ok; geButton* m_cancel; public: gdChannelNameInput(Channel* ch); ~gdChannelNameInput(); }; #endif giada-0.14.5/src/gui/dialogs/gd_about.cpp000066400000000000000000000074401322662744500201530ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include #include #include #include "../../core/conf.h" #include "../../core/const.h" #include "../../core/graphics.h" #ifdef WITH_VST #include "../../deps/juce-config.h" #endif #include "../../utils/gui.h" #include "../../utils/string.h" #include "../../utils/deps.h" #include "../elems/basics/button.h" #include "../elems/basics/box.h" #include "gd_about.h" using std::string; using namespace giada::m; using namespace giada::u; gdAbout::gdAbout() #ifdef WITH_VST : gdWindow(340, 435, "About Giada") #else : gdWindow(340, 350, "About Giada") #endif { if (conf::aboutX) resize(conf::aboutX, conf::aboutY, w(), h()); set_modal(); logo = new geBox(8, 10, 324, 86); text = new geBox(8, 120, 324, 145); close = new geButton(252, h()-28, 80, 20, "Close"); #ifdef WITH_VST vstLogo = new geBox(8, 265, 324, 50); vstText = new geBox(8, 315, 324, 46); #endif end(); logo->image(new Fl_Pixmap(giada_logo_xpm)); text->align(FL_ALIGN_CENTER | FL_ALIGN_INSIDE | FL_ALIGN_TOP); string message = gu_format( "Version " G_VERSION_STR " (" BUILD_DATE ")\n\n" "Developed by Monocasual Laboratories\n" "Based on FLTK (%d.%d.%d), RtAudio (%s),\n" "RtMidi (%s), Libsamplerate, Jansson (%s),\n" "Libsndfile (%s)" #ifdef WITH_VST ", JUCE (%d.%d.%d)\n\n" #else "\n\n" #endif "Released under the terms of the GNU General\n" "Public License (GPL v3)\n\n" "News, infos, contacts and documentation:\n" "www.giadamusic.com", FL_MAJOR_VERSION, FL_MINOR_VERSION, FL_PATCH_VERSION, deps::getRtAudioVersion().c_str(), deps::getRtMidiVersion().c_str(), JANSSON_VERSION, deps::getLibsndfileVersion().c_str() #ifdef WITH_VST , JUCE_MAJOR_VERSION, JUCE_MINOR_VERSION, JUCE_BUILDNUMBER #endif ); int tw = 0; int th = 0; fl_measure(message.c_str(), tw, th); text->copy_label(message.c_str()); text->size(text->w(), th); #ifdef WITH_VST vstLogo->image(new Fl_Pixmap(vstLogo_xpm)); vstLogo->position(vstLogo->x(), text->y()+text->h()+8); vstText->label( "VST Plug-In Technology by Steinberg\n" "VST is a trademark of Steinberg\nMedia Technologies GmbH" ); vstText->position(vstText->x(), vstLogo->y()+vstLogo->h()); #endif close->callback(cb_close, (void*)this); gu_setFavicon(this); setId(WID_ABOUT); show(); } /* -------------------------------------------------------------------------- */ gdAbout::~gdAbout() { conf::aboutX = x(); conf::aboutY = y(); } /* -------------------------------------------------------------------------- */ void gdAbout::cb_close(Fl_Widget *w, void *p) { ((gdAbout*)p)->__cb_close(); } /* -------------------------------------------------------------------------- */ void gdAbout::__cb_close() { do_callback(); } giada-0.14.5/src/gui/dialogs/gd_about.h000066400000000000000000000027341322662744500176210ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef GD_ABOUT_H #define GD_ABOUT_H #include "window.h" class geBox; class geButton; class gdAbout : public gdWindow { private: geBox *logo; geBox *text; geButton *close; #ifdef WITH_VST geBox *vstText; geBox *vstLogo; #endif public: gdAbout(); ~gdAbout(); static void cb_close(Fl_Widget *w, void *p); inline void __cb_close(); }; #endif giada-0.14.5/src/gui/dialogs/gd_actionEditor.cpp000066400000000000000000000173501322662744500214660ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include #include "../../utils/gui.h" #include "../../utils/string.h" #include "../../core/graphics.h" #include "../../core/conf.h" #include "../../core/const.h" #include "../../core/clock.h" #include "../../core/sampleChannel.h" #include "../elems/basics/scroll.h" #include "../elems/basics/button.h" #include "../elems/basics/resizerBar.h" #include "../elems/basics/choice.h" #include "../elems/basics/box.h" #include "../elems/actionEditor/actionEditor.h" #include "../elems/actionEditor/envelopeEditor.h" #include "../elems/actionEditor/muteEditor.h" #include "../elems/actionEditor/noteEditor.h" #include "../elems/actionEditor/gridTool.h" #include "gd_actionEditor.h" using std::string; using namespace giada::m; gdActionEditor::gdActionEditor(Channel *chan) : gdWindow(640, 284), chan (chan), zoom (100), coverX (0) { if (conf::actionEditorW) { resize(conf::actionEditorX, conf::actionEditorY, conf::actionEditorW, conf::actionEditorH); zoom = conf::actionEditorZoom; } totalWidth = (int) std::ceil(clock::getFramesInSequencer() / (float) zoom); /* container with zoom buttons and the action type selector. Scheme of * the resizable boxes: |[--b1--][actionType][--b2--][+][-]| */ Fl_Group *upperArea = new Fl_Group(8, 8, w()-16, 20); upperArea->begin(); if (chan->type == CHANNEL_SAMPLE) { actionType = new geChoice(8, 8, 80, 20); gridTool = new geGridTool(actionType->x()+actionType->w()+4, 8, this); actionType->add("key press"); actionType->add("key release"); actionType->add("kill chan"); actionType->value(0); SampleChannel *ch = (SampleChannel*) chan; if (ch->mode == SINGLE_PRESS || ch->mode & LOOP_ANY) actionType->deactivate(); } else { gridTool = new geGridTool(8, 8, this); } geBox *b1 = new geBox(gridTool->x()+gridTool->w()+4, 8, 300, 20); // padding actionType - zoomButtons zoomIn = new geButton(w()-8-40-4, 8, 20, 20, "", zoomInOff_xpm, zoomInOn_xpm); zoomOut = new geButton(w()-8-20, 8, 20, 20, "", zoomOutOff_xpm, zoomOutOn_xpm); upperArea->end(); upperArea->resizable(b1); zoomIn->callback(cb_zoomIn, (void*)this); zoomOut->callback(cb_zoomOut, (void*)this); /* main scroller: contains all widgets */ scroller = new geScroll(8, 36, w()-16, h()-44); if (chan->type == CHANNEL_SAMPLE) { SampleChannel *ch = (SampleChannel*) chan; ac = new geActionEditor (scroller->x(), upperArea->y()+upperArea->h()+8, this, ch); mc = new geMuteEditor (scroller->x(), ac->y()+ac->h()+8, this); vc = new geEnvelopeEditor(scroller->x(), mc->y()+mc->h()+8, this, G_ACTION_VOLUME, G_RANGE_FLOAT, "volume"); scroller->add(ac); //scroller->add(new geResizerBar(ac->x(), ac->y()+ac->h(), scroller->w(), 8)); scroller->add(mc); //scroller->add(new geResizerBar(mc->x(), mc->y()+mc->h(), scroller->w(), 8)); scroller->add(vc); //scroller->add(new geResizerBar(vc->x(), vc->y()+vc->h(), scroller->w(), 8)); /* fill volume envelope with actions from recorder */ vc->fill(); /* if channel is LOOP_ANY, deactivate it: a loop mode channel cannot * hold keypress/keyrelease actions */ if (ch->mode & LOOP_ANY) ac->deactivate(); } else { pr = new geNoteEditor(scroller->x(), upperArea->y()+upperArea->h()+8, this); scroller->add(pr); /* TODO - avoid magic number 30 for minimum height */ scroller->add(new geResizerBar(pr->x(), pr->y()+pr->h(), scroller->w(), 30, 8)); } end(); /* compute values */ update(); gridTool->calc(); gu_setFavicon(this); string buf = "Edit Actions in Channel " + gu_iToString(chan->index+1); label(buf.c_str()); set_non_modal(); size_range(640, 284); resizable(scroller); show(); } /* -------------------------------------------------------------------------- */ gdActionEditor::~gdActionEditor() { conf::actionEditorX = x(); conf::actionEditorY = y(); conf::actionEditorW = w(); conf::actionEditorH = h(); conf::actionEditorZoom = zoom; /** CHECKME - missing clear() ? */ } /* -------------------------------------------------------------------------- */ void gdActionEditor::cb_zoomIn(Fl_Widget *w, void *p) { ((gdActionEditor*)p)->__cb_zoomIn(); } void gdActionEditor::cb_zoomOut(Fl_Widget *w, void *p) { ((gdActionEditor*)p)->__cb_zoomOut(); } /* -------------------------------------------------------------------------- */ void gdActionEditor::__cb_zoomIn() { /* zoom 50: empirical value, to avoid a totalWidth > 16 bit signed * (32767 max), unsupported by FLTK 1.3.x */ if (zoom <= 50) return; zoom /= 2; update(); if (chan->type == CHANNEL_SAMPLE) { ac->size(totalWidth, ac->h()); mc->size(totalWidth, mc->h()); vc->size(totalWidth, vc->h()); ac->updateActions(); mc->updateActions(); vc->updateActions(); } else { pr->size(totalWidth, pr->h()); pr->updateActions(); } /* scroll to pointer */ int shift = Fl::event_x() + scroller->xposition(); scroller->scroll_to(scroller->xposition() + shift, scroller->yposition()); /* update all underlying widgets */ gridTool->calc(); scroller->redraw(); } /* -------------------------------------------------------------------------- */ void gdActionEditor::__cb_zoomOut() { zoom *= 2; update(); if (chan->type == CHANNEL_SAMPLE) { ac->size(totalWidth, ac->h()); mc->size(totalWidth, mc->h()); vc->size(totalWidth, vc->h()); ac->updateActions(); mc->updateActions(); vc->updateActions(); } else { pr->size(totalWidth, pr->h()); pr->updateActions(); } /* scroll to pointer */ int shift = (Fl::event_x() + scroller->xposition()) / -2; if (scroller->xposition() + shift < 0) shift = 0; scroller->scroll_to(scroller->xposition() + shift, scroller->yposition()); /* update all underlying widgets */ gridTool->calc(); scroller->redraw(); } /* -------------------------------------------------------------------------- */ void gdActionEditor::update() { totalWidth = (int) ceilf(clock::getFramesInSequencer() / (float) zoom); if (totalWidth < scroller->w()) { totalWidth = scroller->w(); zoom = (int) ceilf(clock::getFramesInSequencer() / (float) totalWidth); scroller->scroll_to(0, scroller->yposition()); } } /* -------------------------------------------------------------------------- */ int gdActionEditor::handle(int e) { int ret = Fl_Group::handle(e); switch (e) { case FL_MOUSEWHEEL: { Fl::event_dy() == -1 ? __cb_zoomIn() : __cb_zoomOut(); ret = 1; break; } } return ret; } /* -------------------------------------------------------------------------- */ int gdActionEditor::getActionType() { if (actionType->value() == 0) return G_ACTION_KEYPRESS; else if (actionType->value() == 1) return G_ACTION_KEYREL; else if (actionType->value() == 2) return G_ACTION_KILL; else return -1; } giada-0.14.5/src/gui/dialogs/gd_actionEditor.h000066400000000000000000000046651322662744500211400ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef GD_ACTIONEDITOR_H #define GD_ACTIONEDITOR_H #include #include #include #include #include "window.h" class Channel; class geChoice; class geGridTool; class geButton; class geButton; class geScroll; class geActionEditor; class geMuteEditor; class geEnvelopeEditor; class geNoteEditor; /* gActionEditor Main window which contains the tools for dealing with actions. This class calculates chan, zoom, frames per beat, and so on. Each sub-widget contains a pointer to this window to query those data. */ class gdActionEditor : public gdWindow { private: /* update Computes total width, in pixel. */ void update(); public: gdActionEditor(Channel *chan); ~gdActionEditor(); int handle(int e); int getActionType(); static void cb_zoomIn(Fl_Widget *w, void *p); static void cb_zoomOut(Fl_Widget *w, void *p); inline void __cb_zoomIn(); inline void __cb_zoomOut(); geChoice *actionType; geGridTool *gridTool; geButton *zoomIn; geButton *zoomOut; geScroll *scroller; // widget container geActionEditor *ac; geMuteEditor *mc; geEnvelopeEditor *vc; geNoteEditor *pr; Channel *chan; int zoom; int totalWidth; // total width of the widget, in pixel (zoom affected) int coverX; // x1 of the unused area (x2 = totalWidth) }; #endif giada-0.14.5/src/gui/dialogs/gd_config.cpp000066400000000000000000000070001322662744500202760ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include #include "../../core/conf.h" #include "../../core/const.h" #include "../../utils/gui.h" #include "../elems/basics/boxtypes.h" #include "../elems/basics/button.h" #include "../elems/config/tabMisc.h" #include "../elems/config/tabMidi.h" #include "../elems/config/tabAudio.h" #include "../elems/config/tabBehaviors.h" #include "../elems/config/tabPlugins.h" #include "gd_config.h" using namespace giada::m; gdConfig::gdConfig(int w, int h) : gdWindow(w, h, "Configuration") { if (conf::configX) resize(conf::configX, conf::configY, this->w(), this->h()); Fl_Tabs* tabs = new Fl_Tabs(8, 8, w-16, h-44); tabs->box(G_CUSTOM_BORDER_BOX); tabs->labelcolor(G_COLOR_LIGHT_2); tabs->begin(); tabAudio = new geTabAudio(tabs->x()+10, tabs->y()+20, tabs->w()-20, tabs->h()-40); tabMidi = new geTabMidi(tabs->x()+10, tabs->y()+20, tabs->w()-20, tabs->h()-40); tabBehaviors = new geTabBehaviors(tabs->x()+10, tabs->y()+20, tabs->w()-20, tabs->h()-40); tabMisc = new geTabMisc(tabs->x()+10, tabs->y()+20, tabs->w()-20, tabs->h()-40); #ifdef WITH_VST tabPlugins = new geTabPlugins(tabs->x()+10, tabs->y()+20, tabs->w()-20, tabs->h()-40); #endif tabs->end(); save = new geButton (w-88, h-28, 80, 20, "Save"); cancel = new geButton (w-176, h-28, 80, 20, "Cancel"); end(); save->callback(cb_save_config, (void*)this); cancel->callback(cb_cancel, (void*)this); gu_setFavicon(this); setId(WID_CONFIG); show(); } /* -------------------------------------------------------------------------- */ gdConfig::~gdConfig() { conf::configX = x(); conf::configY = y(); } /* -------------------------------------------------------------------------- */ void gdConfig::cb_save_config(Fl_Widget* w, void* p) { ((gdConfig*)p)->cb_save_config(); } void gdConfig::cb_cancel (Fl_Widget* w, void* p) { ((gdConfig*)p)->cb_cancel(); } /* -------------------------------------------------------------------------- */ void gdConfig::cb_save_config() { tabAudio->save(); tabBehaviors->save(); tabMidi->save(); tabMisc->save(); #ifdef WITH_VST tabPlugins->save(); #endif do_callback(); } /* -------------------------------------------------------------------------- */ void gdConfig::cb_cancel() { do_callback(); } /* -------------------------------------------------------------------------- */ #ifdef WITH_VST void gdConfig::refreshVstPath() { tabPlugins->refreshVstPath(); } #endifgiada-0.14.5/src/gui/dialogs/gd_config.h000066400000000000000000000035311322662744500177500ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef GD_CONFIG_H #define GD_CONFIG_H #include "window.h" class geTabAudio; class geTabBehaviors; class geTabMidi; class geTabMisc; #ifdef WITH_VST class geTabPlugins; #endif class geButton; class geChoice; class geCheck; class geInput; class geRadio; class geBox; class gdConfig : public gdWindow { private: static void cb_save_config(Fl_Widget* w, void* p); static void cb_cancel(Fl_Widget* w, void* p); void cb_save_config(); void cb_cancel(); public: geTabAudio* tabAudio; geTabBehaviors* tabBehaviors; geTabMidi* tabMidi; geTabMisc* tabMisc; #ifdef WITH_VST geTabPlugins* tabPlugins; #endif geButton* save; geButton* cancel; gdConfig(int w, int h); ~gdConfig(); #ifdef WITH_VST void refreshVstPath(); #endif }; #endif giada-0.14.5/src/gui/dialogs/gd_devInfo.cpp000066400000000000000000000056221322662744500204330ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include #include "../../core/kernelAudio.h" #include "../../utils/gui.h" #include "../../utils/string.h" #include "../elems/basics/button.h" #include "../elems/basics/box.h" #include "window.h" #include "gd_devInfo.h" using std::string; using namespace giada::m; gdDevInfo::gdDevInfo(unsigned dev) : Fl_Window(340, 300, "Device information") { set_modal(); text = new geBox(8, 8, 320, 200, "", (Fl_Align) (FL_ALIGN_LEFT | FL_ALIGN_TOP)); close = new geButton(252, h()-28, 80, 20, "Close"); end(); string body = ""; int lines = 7; body = "Device name: " + kernelAudio::getDeviceName(dev) + "\n"; body += "Total output(s): " + gu_iToString(kernelAudio::getMaxOutChans(dev)) + "\n"; body += "Total intput(s): " + gu_iToString(kernelAudio::getMaxInChans(dev)) + "\n"; body += "Duplex channel(s): " + gu_iToString(kernelAudio::getDuplexChans(dev)) + "\n"; body += "Default output: " + string(kernelAudio::isDefaultOut(dev) ? "yes" : "no") + "\n"; body += "Default input: " + string(kernelAudio::isDefaultIn(dev) ? "yes" : "no") + "\n"; int totalFreq = kernelAudio::getTotalFreqs(dev); body += "Supported frequencies: " + gu_iToString(totalFreq); for (int i=0; icopy_label(body.c_str()); /* resize the window to fit the content. fl_height() returns the height * of a line. fl_height() * total lines + margins + button size */ resize(x(), y(), w(), (lines * fl_height()) + 8 + 8 + 8 + 20); close->position(close->x(), (lines * fl_height()) + 8 + 8); close->callback(__cb_window_closer, (void*)this); gu_setFavicon(this); show(); } gdDevInfo::~gdDevInfo() {} giada-0.14.5/src/gui/dialogs/gd_devInfo.h000066400000000000000000000025431322662744500200770ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef GD_DEV_INFO_H #define GD_DEV_INFO_H #include class geBox; class geButton; class gdDevInfo : public Fl_Window { private: geBox *text; geButton *close; public: gdDevInfo(unsigned dev); ~gdDevInfo(); }; #endif giada-0.14.5/src/gui/dialogs/gd_keyGrabber.cpp000066400000000000000000000072161322662744500211170ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include "../../utils/gui.h" #include "../../utils/string.h" #include "../../core/conf.h" #include "../../core/channel.h" #include "../../core/sampleChannel.h" #include "../../core/midiChannel.h" #include "../../utils/log.h" #include "../elems/basics/box.h" #include "../elems/mainWindow/keyboard/keyboard.h" #include "../elems/mainWindow/keyboard/channel.h" #include "../elems/mainWindow/keyboard/channelButton.h" #include "gd_keyGrabber.h" #include "gd_config.h" #include "gd_mainWindow.h" extern gdMainWindow *mainWin; using std::string; gdKeyGrabber::gdKeyGrabber(Channel *ch) : gdWindow(300, 126, "Key configuration"), ch(ch) { set_modal(); text = new geBox(8, 8, 284, 80, ""); clear = new geButton(w()-88, text->y()+text->h()+8, 80, 20, "Clear"); cancel = new geButton(clear->x()-88, clear->y(), 80, 20, "Close"); end(); clear->callback(cb_clear, (void*)this); cancel->callback(cb_cancel, (void*)this); updateText(ch->key); gu_setFavicon(this); show(); } /* -------------------------------------------------------------------------- */ void gdKeyGrabber::cb_clear (Fl_Widget *w, void *p) { ((gdKeyGrabber*)p)->__cb_clear(); } void gdKeyGrabber::cb_cancel(Fl_Widget *w, void *p) { ((gdKeyGrabber*)p)->__cb_cancel(); } /* -------------------------------------------------------------------------- */ void gdKeyGrabber::__cb_cancel() { do_callback(); } /* -------------------------------------------------------------------------- */ void gdKeyGrabber::__cb_clear() { updateText(0); setButtonLabel(0); } /* -------------------------------------------------------------------------- */ void gdKeyGrabber::setButtonLabel(int key) { ch->guiChannel->mainButton->setKey(key); ch->key = key; } /* -------------------------------------------------------------------------- */ void gdKeyGrabber::updateText(int key) { string tmp = "Press a key.\n\nCurrent binding: "; if (key != 0) tmp += static_cast(key); else tmp += "[none]"; text->copy_label(tmp.c_str()); } /* -------------------------------------------------------------------------- */ int gdKeyGrabber::handle(int e) { int ret = Fl_Group::handle(e); switch(e) { case FL_KEYUP: { int x = Fl::event_key(); if (strlen(Fl::event_text()) != 0 && x != FL_BackSpace && x != FL_Enter && x != FL_Delete && x != FL_Tab && x != FL_End && x != ' ') { gu_log("set key '%c' (%d) for channel %d\n", x, x, ch->index); setButtonLabel(x); updateText(x); break; } else gu_log("invalid key\n"); } } return(ret); } giada-0.14.5/src/gui/dialogs/gd_keyGrabber.h000066400000000000000000000031771322662744500205660ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef GD_KEYGRABBER_H #define GD_KEYGRABBER_H #include #include "window.h" class Channel; class geBox; class geButton; class gdKeyGrabber : public gdWindow { private: Channel *ch; geBox *text; geButton *clear; geButton *cancel; static void cb_clear (Fl_Widget *w, void *p); static void cb_cancel(Fl_Widget *w, void *p); inline void __cb_clear (); inline void __cb_cancel(); void setButtonLabel(int key); void updateText(int key); public: gdKeyGrabber(Channel *ch); int handle(int e); }; #endif giada-0.14.5/src/gui/dialogs/gd_mainWindow.cpp000066400000000000000000000072031322662744500211520ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include #include "../../core/const.h" #include "../../core/init.h" #include "../../utils/gui.h" #include "../elems/basics/boxtypes.h" #include "../elems/mainWindow/mainIO.h" #include "../elems/mainWindow/mainMenu.h" #include "../elems/mainWindow/mainTimer.h" #include "../elems/mainWindow/mainTransport.h" #include "../elems/mainWindow/beatMeter.h" #include "../elems/mainWindow/keyboard/keyboard.h" #include "gd_warnings.h" #include "gd_mainWindow.h" extern gdMainWindow *G_MainWin; gdMainWindow::gdMainWindow(int W, int H, const char *title, int argc, char **argv) : gdWindow(W, H, title) { Fl::visible_focus(0); Fl::background(25, 25, 25); Fl::set_boxtype(G_CUSTOM_BORDER_BOX, g_customBorderBox, 1, 1, 2, 2); Fl::set_boxtype(G_CUSTOM_UP_BOX, g_customUpBox, 1, 1, 2, 2); Fl::set_boxtype(G_CUSTOM_DOWN_BOX, g_customDownBox, 1, 1, 2, 2); Fl::set_boxtype(FL_BORDER_BOX, G_CUSTOM_BORDER_BOX); Fl::set_boxtype(FL_UP_BOX, G_CUSTOM_UP_BOX); Fl::set_boxtype(FL_DOWN_BOX, G_CUSTOM_DOWN_BOX); size_range(G_GUI_WIDTH, G_GUI_HEIGHT); mainMenu = new geMainMenu(8, -1); mainIO = new geMainIO(412, 8); mainTransport = new geMainTransport(8, 39); mainTimer = new geMainTimer(628, 44); beatMeter = new geBeatMeter(100, 83, 609, 20); keyboard = new geKeyboard(8, 122, w()-16, 380); /* zone 1 - menus, and I/O tools */ Fl_Group *zone1 = new Fl_Group(8, 8, W-16, 20); zone1->add(mainMenu); zone1->resizable(new Fl_Box(300, 8, 80, 20)); zone1->add(mainIO); /* zone 2 - mainTransport and timing tools */ Fl_Group *zone2 = new Fl_Group(8, mainTransport->y(), W-16, mainTransport->h()); zone2->add(mainTransport); zone2->resizable(new Fl_Box(mainTransport->x()+mainTransport->w()+4, zone2->y(), 80, 20)); zone2->add(mainTimer); /* zone 3 - beat meter */ Fl_Group *zone3 = new Fl_Group(8, beatMeter->y(), W-16, beatMeter->h()); zone3->add(beatMeter); /* zone 4 - the keyboard (Fl_Group is unnecessary here, keyboard is * a group by itself) */ resizable(keyboard); add(zone1); add(zone2); add(zone3); add(keyboard); callback(cb_endprogram); gu_setFavicon(this); show(argc, argv); } /* -------------------------------------------------------------------------- */ void gdMainWindow::cb_endprogram(Fl_Widget *v, void *p) { G_MainWin->__cb_endprogram(); } /* -------------------------------------------------------------------------- */ void gdMainWindow::__cb_endprogram() { if (!gdConfirmWin("Warning", "Quit Giada: are you sure?")) return; init_shutdown(); hide(); delete this; } giada-0.14.5/src/gui/dialogs/gd_mainWindow.h000066400000000000000000000033071322662744500206200ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef GD_MAINWINDOW_H #define GD_MAINWINDOW_H #include "window.h" class Fl_Widget; class geKeyboard; class geBeatMeter; class geMainMenu; class geMainIO; class geMainTimer; class geMainTransport; class gdMainWindow : public gdWindow { private: static void cb_endprogram (Fl_Widget *v, void *p); inline void __cb_endprogram(); public: geKeyboard *keyboard; geBeatMeter *beatMeter; geMainMenu *mainMenu; geMainIO *mainIO; geMainTimer *mainTimer; geMainTransport *mainTransport; gdMainWindow(int w, int h, const char *title, int argc, char **argv); }; #endif giada-0.14.5/src/gui/dialogs/gd_warnings.cpp000066400000000000000000000046421322662744500206720ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include #include #include "../../utils/gui.h" #include "../../core/const.h" #include "../elems/basics/button.h" #include "../elems/basics/box.h" #include "window.h" #include "gd_warnings.h" void gdAlert(const char *c) { Fl_Window *modal = new Fl_Window( (Fl::w() / 2) - 150, (Fl::h() / 2) - 47, 300, 90, "Alert"); modal->set_modal(); modal->begin(); geBox *box = new geBox(10, 10, 280, 40, c); geButton *b = new geButton(210, 60, 80, 20, "Close"); modal->end(); box->labelsize(G_GUI_FONT_SIZE_BASE); b->callback(__cb_window_closer, (void *)modal); b->shortcut(FL_Enter); gu_setFavicon(modal); modal->show(); } int gdConfirmWin(const char *title, const char *msg) { Fl_Window *win = new Fl_Window( (Fl::w() / 2) - 150, (Fl::h() / 2) - 47, 300, 90, title); win->set_modal(); win->begin(); new geBox(10, 10, 280, 40, msg); geButton *ok = new geButton(212, 62, 80, 20, "Ok"); geButton *ko = new geButton(124, 62, 80, 20, "Cancel"); win->end(); ok->shortcut(FL_Enter); gu_setFavicon(win); win->show(); /* no callbacks here. readqueue() check the event stack. */ int r = 0; while (true) { Fl_Widget *o = Fl::readqueue(); if (!o) Fl::wait(); else if (o == ok) {r = 1; break;} else if (o == ko) {r = 0; break;} } //delete win; win->hide(); return r; } giada-0.14.5/src/gui/dialogs/gd_warnings.h000066400000000000000000000023641322662744500203360ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef GD_WARNINGS_H #define GD_WARNINGS_H void gdAlert(const char *c); int gdConfirmWin(const char *title, const char *msg); #endif giada-0.14.5/src/gui/dialogs/midiIO/000077500000000000000000000000001322662744500170305ustar00rootroot00000000000000giada-0.14.5/src/gui/dialogs/midiIO/midiInputBase.cpp000066400000000000000000000064051322662744500222760ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include "../../../core/midiDispatcher.h" #include "../../../core/channel.h" #include "../../../core/conf.h" #include "../../../utils/log.h" #include "../../elems/midiLearner.h" #include "midiInputBase.h" using std::string; using namespace giada::m; gdMidiInputBase::gdMidiInputBase(int x, int y, int w, int h, const char* title) : gdWindow(x, y, w, h, title) { } /* -------------------------------------------------------------------------- */ gdMidiInputBase::~gdMidiInputBase() { midiDispatcher::stopMidiLearn(); } /* -------------------------------------------------------------------------- */ void gdMidiInputBase::stopMidiLearn(geMidiLearner* learner) { midiDispatcher::stopMidiLearn(); learner->updateValue(); } /* -------------------------------------------------------------------------- */ void gdMidiInputBase::cb_learn(uint32_t* param, uint32_t msg, geMidiLearner* l) { *param = msg; stopMidiLearn(l); gu_log("[gdMidiGrabber] MIDI learn done - message=0x%X\n", msg); } /* -------------------------------------------------------------------------- */ void gdMidiInputBase::cb_learn(uint32_t msg, void* d) { geMidiLearner::cbData_t* data = (geMidiLearner::cbData_t*) d; geMidiLearner* learner = data->learner; Channel* channel = data->channel; uint32_t* param = learner->param; int midiChannel = (*param & 0x0F000000) >> 24; // Brutally extract channel /* No MIDI learning if we are learning a Channel (channel != nullptr) and the selected MIDI channel is filtered OR if we are learning a global parameter (channel == nullptr) and the selected MIDI channel is filtered. */ if ((channel != nullptr && !channel->isMidiInAllowed(midiChannel)) || (channel == nullptr && !conf::isMidiInAllowed(midiChannel))) return; gdMidiInputBase* window = static_cast(data->window); window->cb_learn(param, msg, learner); } /* -------------------------------------------------------------------------- */ void gdMidiInputBase::cb_close(Fl_Widget* w, void* p) { ((gdMidiInputBase*)p)->cb_close(); } /* -------------------------------------------------------------------------- */ void gdMidiInputBase::cb_close() { do_callback(); } giada-0.14.5/src/gui/dialogs/midiIO/midiInputBase.h000066400000000000000000000033501322662744500217370ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef GD_MIDI_INPUT_BASE_H #define GD_MIDI_INPUT_BASE_H #include "../window.h" class geButton; class geMidiLearner; class gdMidiInputBase : public gdWindow { protected: static const int LEARNER_WIDTH = 284; geButton* ok; void stopMidiLearn(geMidiLearner* l); /* cb_learn * callback attached to kernelMidi to learn various actions. */ static void cb_learn(uint32_t msg, void* data); static void cb_close(Fl_Widget* w, void* p); void cb_learn(uint32_t* param, uint32_t msg, geMidiLearner* l); void cb_close(); public: gdMidiInputBase(int x, int y, int w, int h, const char* title); ~gdMidiInputBase(); }; #endif giada-0.14.5/src/gui/dialogs/midiIO/midiInputChannel.cpp000066400000000000000000000155111322662744500227720ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include #include "../../../utils/gui.h" #include "../../../utils/log.h" #include "../../../core/const.h" #include "../../../core/conf.h" #include "../../../core/sampleChannel.h" #ifdef WITH_VST #include "../../../core/pluginHost.h" #include "../../../core/plugin.h" #endif #include "../../../utils/string.h" #include "../../elems/midiLearner.h" #include "../../elems/basics/scroll.h" #include "../../elems/basics/box.h" #include "../../elems/basics/button.h" #include "../../elems/basics/choice.h" #include "../../elems/basics/check.h" #include "midiInputChannel.h" using std::string; using std::vector; using namespace giada::m; gdMidiInputChannel::gdMidiInputChannel(Channel* ch) : gdMidiInputBase(conf::midiInputX, conf::midiInputY, conf::midiInputW, conf::midiInputH, "MIDI Input Setup"), ch(ch) { string title = "MIDI Input Setup (channel " + gu_iToString(ch->index+1) + ")"; label(title.c_str()); size_range(G_DEFAULT_MIDI_INPUT_UI_W, G_DEFAULT_MIDI_INPUT_UI_H); Fl_Group* groupHeader = new Fl_Group(G_GUI_OUTER_MARGIN, G_GUI_OUTER_MARGIN, w(), 20); groupHeader->begin(); enable = new geCheck(G_GUI_OUTER_MARGIN, G_GUI_OUTER_MARGIN, 120, G_GUI_UNIT, "enable MIDI input"); channel = new geChoice(enable->x()+enable->w()+44, G_GUI_OUTER_MARGIN, 120, G_GUI_UNIT); groupHeader->resizable(nullptr); groupHeader->end(); container = new geScroll(G_GUI_OUTER_MARGIN, enable->y()+enable->h()+G_GUI_OUTER_MARGIN, w()-16, h()-76); container->begin(); addChannelLearners(); #ifdef WITH_VST addPluginLearners(); #endif container->end(); Fl_Group* groupButtons = new Fl_Group(8, container->y()+container->h()+8, container->w(), 20); groupButtons->begin(); geBox* spacer = new geBox(groupButtons->x(), groupButtons->y(), 100, 20); // spacer window border <-> buttons ok = new geButton(w()-88, groupButtons->y(), 80, 20, "Close"); groupButtons->resizable(spacer); groupButtons->end(); ok->callback(cb_close, (void*)this); enable->value(ch->midiIn); enable->callback(cb_enable, (void*)this); channel->add("Channel (any)"); channel->add("Channel 1"); channel->add("Channel 2"); channel->add("Channel 3"); channel->add("Channel 4"); channel->add("Channel 5"); channel->add("Channel 6"); channel->add("Channel 7"); channel->add("Channel 8"); channel->add("Channel 9"); channel->add("Channel 10"); channel->add("Channel 11"); channel->add("Channel 12"); channel->add("Channel 13"); channel->add("Channel 14"); channel->add("Channel 15"); channel->add("Channel 16"); channel->value(ch->getMidiInFilter() == -1 ? 0 : ch->getMidiInFilter() + 1); channel->callback(cb_setChannel, (void*)this); resizable(container); end(); gu_setFavicon(this); set_modal(); show(); } /* -------------------------------------------------------------------------- */ gdMidiInputChannel::~gdMidiInputChannel() { conf::midiInputX = x(); conf::midiInputY = y(); conf::midiInputW = w(); conf::midiInputH = h(); } /* -------------------------------------------------------------------------- */ void gdMidiInputChannel::addChannelLearners() { Fl_Pack* pack = new Fl_Pack(container->x(), container->y(), LEARNER_WIDTH, 200); pack->spacing(4); pack->begin(); geBox *header = new geBox(0, 0, LEARNER_WIDTH, 20, "channel"); header->box(FL_BORDER_BOX); new geMidiLearner(0, 0, LEARNER_WIDTH, "key press", cb_learn, &ch->midiInKeyPress, ch); new geMidiLearner(0, 0, LEARNER_WIDTH, "key release", cb_learn, &ch->midiInKeyRel, ch); new geMidiLearner(0, 0, LEARNER_WIDTH, "key kill", cb_learn, &ch->midiInKill, ch); new geMidiLearner(0, 0, LEARNER_WIDTH, "arm", cb_learn, &ch->midiInArm, ch); new geMidiLearner(0, 0, LEARNER_WIDTH, "mute", cb_learn, &ch->midiInMute, ch); new geMidiLearner(0, 0, LEARNER_WIDTH, "solo", cb_learn, &ch->midiInSolo, ch); new geMidiLearner(0, 0, LEARNER_WIDTH, "volume", cb_learn, &ch->midiInVolume, ch); if (ch->type == CHANNEL_SAMPLE) { new geMidiLearner(0, 0, LEARNER_WIDTH, "pitch", cb_learn, &(static_cast(ch))->midiInPitch, ch); new geMidiLearner(0, 0, LEARNER_WIDTH, "read actions", cb_learn, &(static_cast(ch))->midiInReadActions, ch); } pack->end(); } /* -------------------------------------------------------------------------- */ #ifdef WITH_VST void gdMidiInputChannel::addPluginLearners() { vector* plugins = pluginHost::getStack(pluginHost::CHANNEL, ch); for (unsigned i=0; isize(); i++) { Fl_Pack* pack = new Fl_Pack(container->x() + ((i + 1) * (LEARNER_WIDTH + 8)), container->y(), LEARNER_WIDTH, 200); pack->spacing(4); pack->begin(); Plugin* plugin = plugins->at(i); geBox* header = new geBox(0, 0, LEARNER_WIDTH, 20, plugin->getName().c_str()); header->box(FL_BORDER_BOX); for (int k=0; kgetNumParameters(); k++) new geMidiLearner(0, 0, LEARNER_WIDTH, plugin->getParameterName(k).c_str(), cb_learn, &plugin->midiInParams.at(k), ch); pack->end(); } } #endif /* -------------------------------------------------------------------------- */ void gdMidiInputChannel::cb_enable(Fl_Widget* w, void* p) { ((gdMidiInputChannel*)p)->cb_enable(); } void gdMidiInputChannel::cb_setChannel(Fl_Widget* w, void* p) { ((gdMidiInputChannel*)p)->cb_setChannel(); } /* -------------------------------------------------------------------------- */ void gdMidiInputChannel::cb_enable() { ch->midiIn = enable->value(); enable->value() ? channel->activate() : channel->deactivate(); } /* -------------------------------------------------------------------------- */ void gdMidiInputChannel::cb_setChannel() { ch->setMidiInFilter(channel->value() == 0 ? -1 : channel->value() - 1); gu_log("[gdMidiInputChannel] Set MIDI channel to %d\n", ch->getMidiInFilter()); } giada-0.14.5/src/gui/dialogs/midiIO/midiInputChannel.h000066400000000000000000000033301322662744500224330ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * midiInputChannel * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef GD_MIDI_INPUT_CHANNEL_H #define GD_MIDI_INPUT_CHANNEL_H #include "midiInputBase.h" class Channel; class geScroll; class geCheck; class geChoice; class gdMidiInputChannel : public gdMidiInputBase { private: Channel* ch; geScroll* container; geCheck* enable; geChoice* channel; static void cb_enable(Fl_Widget* w, void* p); static void cb_setChannel(Fl_Widget* w, void* p); void cb_enable(); void cb_setChannel(); void addChannelLearners(); #ifdef WITH_VST void addPluginLearners(); #endif public: gdMidiInputChannel(Channel* ch); ~gdMidiInputChannel(); }; #endif giada-0.14.5/src/gui/dialogs/midiIO/midiInputMaster.cpp000066400000000000000000000110711322662744500226520ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include #include "../../../utils/gui.h" #include "../../../core/conf.h" #include "../../../core/const.h" #include "../../elems/midiLearner.h" #include "../../elems/basics/button.h" #include "../../elems/basics/check.h" #include "../../elems/basics/choice.h" #include "midiInputMaster.h" using namespace giada::m; gdMidiInputMaster::gdMidiInputMaster() : gdMidiInputBase(0, 0, 300, 284, "MIDI Input Setup (global)") { set_modal(); Fl_Group* groupHeader = new Fl_Group(G_GUI_OUTER_MARGIN, G_GUI_OUTER_MARGIN, w(), 20); groupHeader->begin(); enable = new geCheck(G_GUI_OUTER_MARGIN, G_GUI_OUTER_MARGIN, 120, G_GUI_UNIT, "enable MIDI input"); channel = new geChoice(enable->x()+enable->w()+44, G_GUI_OUTER_MARGIN, 120, G_GUI_UNIT); groupHeader->resizable(nullptr); groupHeader->end(); Fl_Pack* pack = new Fl_Pack(G_GUI_OUTER_MARGIN, groupHeader->y()+groupHeader->h()+G_GUI_OUTER_MARGIN, LEARNER_WIDTH, 212); pack->spacing(G_GUI_INNER_MARGIN); pack->begin(); new geMidiLearner(0, 0, LEARNER_WIDTH, "rewind", &cb_learn, &conf::midiInRewind, nullptr); new geMidiLearner(0, 0, LEARNER_WIDTH, "play/stop", &cb_learn, &conf::midiInStartStop, nullptr); new geMidiLearner(0, 0, LEARNER_WIDTH, "action recording", &cb_learn, &conf::midiInActionRec, nullptr); new geMidiLearner(0, 0, LEARNER_WIDTH, "input recording", &cb_learn, &conf::midiInInputRec, nullptr); new geMidiLearner(0, 0, LEARNER_WIDTH, "metronome", &cb_learn, &conf::midiInMetronome, nullptr); new geMidiLearner(0, 0, LEARNER_WIDTH, "input volume", &cb_learn, &conf::midiInVolumeIn, nullptr); new geMidiLearner(0, 0, LEARNER_WIDTH, "output volume", &cb_learn, &conf::midiInVolumeOut, nullptr); new geMidiLearner(0, 0, LEARNER_WIDTH, "sequencer ×2", &cb_learn, &conf::midiInBeatDouble, nullptr); new geMidiLearner(0, 0, LEARNER_WIDTH, "sequencer ÷2", &cb_learn, &conf::midiInBeatHalf, nullptr); pack->end(); ok = new geButton(w()-88, pack->y()+pack->h()+G_GUI_OUTER_MARGIN, 80, G_GUI_UNIT, "Close"); end(); ok->callback(cb_close, (void*)this); enable->value(conf::midiIn); enable->callback(cb_enable, (void*)this); channel->add("Channel (any)"); channel->add("Channel 1"); channel->add("Channel 2"); channel->add("Channel 3"); channel->add("Channel 4"); channel->add("Channel 5"); channel->add("Channel 6"); channel->add("Channel 7"); channel->add("Channel 8"); channel->add("Channel 9"); channel->add("Channel 10"); channel->add("Channel 11"); channel->add("Channel 12"); channel->add("Channel 13"); channel->add("Channel 14"); channel->add("Channel 15"); channel->add("Channel 16"); channel->value(conf::midiInFilter -1 ? 0 : conf::midiInFilter + 1); channel->callback(cb_setChannel, (void*)this); gu_setFavicon(this); show(); } /* -------------------------------------------------------------------------- */ void gdMidiInputMaster::cb_enable(Fl_Widget* w, void* p) { ((gdMidiInputMaster*)p)->cb_enable(); } void gdMidiInputMaster::cb_setChannel(Fl_Widget* w, void* p) { ((gdMidiInputMaster*)p)->cb_setChannel(); } /* -------------------------------------------------------------------------- */ void gdMidiInputMaster::cb_enable() { conf::midiIn = enable->value(); enable->value() ? channel->activate() : channel->deactivate(); } /* -------------------------------------------------------------------------- */ void gdMidiInputMaster::cb_setChannel() { conf::midiInFilter = channel->value() == 0 ? -1 : channel->value() - 1; } giada-0.14.5/src/gui/dialogs/midiIO/midiInputMaster.h000066400000000000000000000030321322662744500223150ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * midiInputMaster * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef GD_MIDI_INPUT_MASTER_H #define GD_MIDI_INPUT_MASTER_H #include "midiInputBase.h" class geCheck; class geChoice; class gdMidiInputMaster : public gdMidiInputBase { private: geCheck* enable; geChoice* channel; static void cb_enable(Fl_Widget* w, void* p); static void cb_setChannel(Fl_Widget* w, void* p); void cb_enable(); void cb_setChannel(); public: gdMidiInputMaster(); }; #endif giada-0.14.5/src/gui/dialogs/midiIO/midiOutputBase.cpp000066400000000000000000000061501322662744500224740ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include "../../../utils/log.h" #include "../../../utils/string.h" #include "../../elems/midiLearner.h" #include "midiOutputBase.h" using std::string; using namespace giada::m; gdMidiOutputBase::gdMidiOutputBase(int w, int h) : gdWindow(w, h, "Midi Output Setup") { } /* -------------------------------------------------------------------------- */ void gdMidiOutputBase::stopMidiLearn(geMidiLearner *learner) { midiDispatcher::stopMidiLearn(); learner->updateValue(); } /* -------------------------------------------------------------------------- */ void gdMidiOutputBase::__cb_learn(uint32_t *param, uint32_t msg, geMidiLearner *l) { *param = msg; stopMidiLearn(l); gu_log("[gdMidiGrabber] MIDI learn done - message=0x%X\n", msg); } /* -------------------------------------------------------------------------- */ void gdMidiOutputBase::cb_learn(uint32_t msg, void *d) { geMidiLearner::cbData_t *data = (geMidiLearner::cbData_t*) d; gdMidiOutputBase *window = (gdMidiOutputBase*) data->window; geMidiLearner *learner = data->learner; uint32_t *param = learner->param; window->__cb_learn(param, msg, learner); } /* -------------------------------------------------------------------------- */ void gdMidiOutputBase::cb_close(Fl_Widget *w, void *p) { ((gdMidiOutputBase*)p)->__cb_close(); } /* -------------------------------------------------------------------------- */ void gdMidiOutputBase::__cb_close() { do_callback(); } /* -------------------------------------------------------------------------- */ void gdMidiOutputBase::cb_enableLightning(Fl_Widget *w, void *p) { ((gdMidiOutputBase*)p)->__cb_enableLightning(); } /* -------------------------------------------------------------------------- */ void gdMidiOutputBase::__cb_enableLightning() {} /* -------------------------------------------------------------------------- */ void gdMidiOutputBase::setTitle(int chanNum) { string tmp = "MIDI Output Setup (channel " + gu_iToString(chanNum) + ")"; copy_label(tmp.c_str()); } giada-0.14.5/src/gui/dialogs/midiIO/midiOutputBase.h000066400000000000000000000046531322662744500221470ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef GD_MIDI_OUTPUT_BASE_H #define GD_MIDI_OUTPUT_BASE_H #include #include "../window.h" class geButton; class geCheck; class geMidiLearner; /* There's no such thing as a gdMidiOutputMaster vs gdMidiOutputChannel. MIDI output master is managed by the configuration window, hence gdMidiOutput deals only with channels. Both MidiOutputMidiCh and MidiOutputSampleCh have the MIDI lighting widget set. In addition MidiOutputMidiCh has the MIDI message output box. */ /* TODO - gdMidiOutput is almost the same thing of gdMidiInput. Create another parent class gdMidiIO to inherit from */ class gdMidiOutputBase : public gdWindow { protected: geButton *close; geCheck *enableLightning; void stopMidiLearn(geMidiLearner *l); /* cb_learn * callback attached to kernelMidi to learn various actions. */ static void cb_learn (uint32_t msg, void *data); inline void __cb_learn(uint32_t *param, uint32_t msg, geMidiLearner *l); /* cb_close close current window. */ static void cb_close (Fl_Widget *w, void *p); inline void __cb_close(); /* cb_enableLightning enable MIDI lightning output. */ static void cb_enableLightning (Fl_Widget *w, void *p); inline void __cb_enableLightning(); /* setTitle * set window title. */ void setTitle(int chanNum); public: gdMidiOutputBase(int w, int h); }; #endif giada-0.14.5/src/gui/dialogs/midiIO/midiOutputMidiCh.cpp000066400000000000000000000076171322662744500227700ustar00rootroot00000000000000 /* ----------------------------------------------------------------------------- * , ch * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include "../../../core/midiChannel.h" #include "../../../utils/gui.h" #include "../../elems/midiLearner.h" #include "../../elems/basics/button.h" #include "../../elems/basics/check.h" #include "../../elems/basics/choice.h" #include "../../elems/mainWindow/keyboard/channel.h" #include "midiOutputMidiCh.h" gdMidiOutputMidiCh::gdMidiOutputMidiCh(MidiChannel* ch) : gdMidiOutputBase(300, 168), ch(ch) { setTitle(ch->index+1); begin(); enableOut = new geCheck(x()+8, y()+8, 150, 20, "Enable MIDI output"); chanListOut = new geChoice(w()-108, y()+8, 100, 20); enableLightning = new geCheck(x()+8, chanListOut->y()+chanListOut->h()+8, 120, 20, "Enable MIDI lightning output"); new geMidiLearner(x()+8, enableLightning->y()+enableLightning->h()+8, w()-16, "playing", cb_learn, &ch->midiOutLplaying, ch); new geMidiLearner(x()+8, enableLightning->y()+enableLightning->h()+32, w()-16, "mute", cb_learn, &ch->midiOutLmute, ch); new geMidiLearner(x()+8, enableLightning->y()+enableLightning->h()+56, w()-16, "solo", cb_learn, &ch->midiOutLsolo, ch); close = new geButton(w()-88, enableLightning->y()+enableLightning->h()+84, 80, 20, "Close"); end(); chanListOut->add("Channel 1"); chanListOut->add("Channel 2"); chanListOut->add("Channel 3"); chanListOut->add("Channel 4"); chanListOut->add("Channel 5"); chanListOut->add("Channel 6"); chanListOut->add("Channel 7"); chanListOut->add("Channel 8"); chanListOut->add("Channel 9"); chanListOut->add("Channel 10"); chanListOut->add("Channel 11"); chanListOut->add("Channel 12"); chanListOut->add("Channel 13"); chanListOut->add("Channel 14"); chanListOut->add("Channel 15"); chanListOut->add("Channel 16"); chanListOut->value(0); if (ch->midiOut) enableOut->value(1); else chanListOut->deactivate(); if (ch->midiOutL) enableLightning->value(1); chanListOut->value(ch->midiOutChan); enableOut->callback(cb_enableChanList, (void*)this); close->callback(cb_close, (void*)this); set_modal(); gu_setFavicon(this); show(); } /* -------------------------------------------------------------------------- */ void gdMidiOutputMidiCh::cb_close (Fl_Widget *w, void *p) { ((gdMidiOutputMidiCh*)p)->__cb_close(); } void gdMidiOutputMidiCh::cb_enableChanList(Fl_Widget *w, void *p) { ((gdMidiOutputMidiCh*)p)->__cb_enableChanList(); } /* -------------------------------------------------------------------------- */ void gdMidiOutputMidiCh::__cb_enableChanList() { enableOut->value() ? chanListOut->activate() : chanListOut->deactivate(); } /* -------------------------------------------------------------------------- */ void gdMidiOutputMidiCh::__cb_close() { ch->midiOut = enableOut->value(); ch->midiOutChan = chanListOut->value(); ch->midiOutL = enableLightning->value(); ch->guiChannel->update(); do_callback(); } giada-0.14.5/src/gui/dialogs/midiIO/midiOutputMidiCh.h000066400000000000000000000032571322662744500224310ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * midiOutputMidiCh * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef GD_MIDI_OUTPUT_MIDI_CH_H #define GD_MIDI_OUTPUT_MIDI_CH_H #include "midiOutputBase.h" class gdMidiOutputMidiCh : public gdMidiOutputBase { private: static void cb_enableChanList (Fl_Widget *w, void *p); inline void __cb_enableChanList(); /* __cb_close override parent method, we need to do more stuff on close. */ static void cb_close (Fl_Widget *w, void *p); inline void __cb_close(); class geCheck *enableOut; class geChoice *chanListOut; class MidiChannel *ch; public: gdMidiOutputMidiCh(class MidiChannel *ch); }; #endif giada-0.14.5/src/gui/dialogs/midiIO/midiOutputSampleCh.cpp000066400000000000000000000050361322662744500233200ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include "../../../core/sampleChannel.h" #include "../../../utils/gui.h" #include "../../elems/midiLearner.h" #include "../../elems/basics/button.h" #include "../../elems/basics/check.h" #include "midiOutputSampleCh.h" gdMidiOutputSampleCh::gdMidiOutputSampleCh(SampleChannel* ch) : gdMidiOutputBase(300, 140), ch(ch) { setTitle(ch->index+1); enableLightning = new geCheck(8, 8, 120, 20, "Enable MIDI lightning output"); new geMidiLearner(8, enableLightning->y()+enableLightning->h()+8, w()-16, "playing", cb_learn, &ch->midiOutLplaying, ch); new geMidiLearner(8, enableLightning->y()+enableLightning->h()+32, w()-16, "mute", cb_learn, &ch->midiOutLmute, ch); new geMidiLearner(8, enableLightning->y()+enableLightning->h()+56, w()-16, "solo", cb_learn, &ch->midiOutLsolo, ch); close = new geButton(w()-88, enableLightning->y()+enableLightning->h()+84, 80, 20, "Close"); close->callback(cb_close, (void*)this); enableLightning->value(ch->midiOutL); enableLightning->callback(cb_enableLightning, (void*)this); set_modal(); gu_setFavicon(this); show(); } /* -------------------------------------------------------------------------- */ void gdMidiOutputSampleCh::cb_close(Fl_Widget *w, void *p) { ((gdMidiOutputSampleCh*)p)->__cb_close(); } /* -------------------------------------------------------------------------- */ void gdMidiOutputSampleCh::__cb_close() { ch->midiOutL = enableLightning->value(); do_callback(); } giada-0.14.5/src/gui/dialogs/midiIO/midiOutputSampleCh.h000066400000000000000000000030251322662744500227610ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef GD_MIDI_OUTPUT_SAMPLE_CH_H #define GD_MIDI_OUTPUT_SAMPLE_CH_H #include "midiOutputBase.h" class SampleChannel; class gdMidiOutputSampleCh : public gdMidiOutputBase { private: SampleChannel *ch; /* __cb_close override parent method, we need to do more stuff on close. */ static void cb_close (Fl_Widget *w, void *p); inline void __cb_close(); public: gdMidiOutputSampleCh(SampleChannel *ch); }; #endif giada-0.14.5/src/gui/dialogs/pluginChooser.cpp000066400000000000000000000101421322662744500212010ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifdef WITH_VST #include "../../glue/plugin.h" #include "../../utils/gui.h" #include "../../core/channel.h" #include "../../core/conf.h" #include "../../core/pluginHost.h" #include "../elems/plugin/pluginBrowser.h" #include "../elems/basics/button.h" #include "../elems/basics/choice.h" #include "../elems/basics/box.h" #include "pluginChooser.h" using namespace giada::m; using namespace giada::c; gdPluginChooser::gdPluginChooser(int X, int Y, int W, int H, int stackType, Channel *ch) : gdWindow(X, Y, W, H, "Available plugins"), ch(ch), stackType(stackType) { /* top area */ Fl_Group *group_top = new Fl_Group(8, 8, w()-16, 20); sortMethod = new geChoice(group_top->x() + 45, group_top->y(), 100, 20, "Sort by"); geBox *b1 = new geBox(sortMethod->x()+sortMethod->w(), group_top->y(), 100, 20); // spacer window border <-> menu group_top->resizable(b1); group_top->end(); /* center browser */ browser = new gePluginBrowser(8, 36, w()-16, h()-70); /* ok/cancel buttons */ Fl_Group *group_btn = new Fl_Group(8, browser->y()+browser->h()+8, w()-16, h()-browser->h()-16); geBox *b2 = new geBox(8, browser->y()+browser->h(), 100, 20); // spacer window border <-> buttons addBtn = new geButton(w()-88, group_btn->y(), 80, 20, "Add"); cancelBtn = new geButton(addBtn->x()-88, group_btn->y(), 80, 20, "Cancel"); group_btn->resizable(b2); group_btn->end(); end(); sortMethod->add("Name"); sortMethod->add("Category"); sortMethod->add("Manufacturer"); sortMethod->callback(cb_sort, (void*) this); sortMethod->value(conf::pluginSortMethod); addBtn->callback(cb_add, (void*) this); addBtn->shortcut(FL_Enter); cancelBtn->callback(cb_close, (void*) this); resizable(browser); gu_setFavicon(this); show(); } /* -------------------------------------------------------------------------- */ gdPluginChooser::~gdPluginChooser() { conf::pluginChooserX = x(); conf::pluginChooserY = y(); conf::pluginChooserW = w(); conf::pluginChooserH = h(); conf::pluginSortMethod = sortMethod->value(); } /* -------------------------------------------------------------------------- */ void gdPluginChooser::cb_close(Fl_Widget *v, void *p) { ((gdPluginChooser*)p)->__cb_close(); } void gdPluginChooser::cb_add(Fl_Widget *v, void *p) { ((gdPluginChooser*)p)->__cb_add(); } void gdPluginChooser::cb_sort(Fl_Widget *v, void *p) { ((gdPluginChooser*)p)->__cb_sort(); } /* -------------------------------------------------------------------------- */ void gdPluginChooser::__cb_close() { do_callback(); } /* -------------------------------------------------------------------------- */ void gdPluginChooser::__cb_sort() { pluginHost::sortPlugins(sortMethod->value()); browser->refresh(); } /* -------------------------------------------------------------------------- */ void gdPluginChooser::__cb_add() { int index = browser->value() - 3; // subtract header lines if (index < 0) return; plugin::addPlugin(ch, index, stackType); do_callback(); } #endif // #ifdef WITH_VST giada-0.14.5/src/gui/dialogs/pluginChooser.h000066400000000000000000000036621322662744500206570ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifdef WITH_VST #ifndef GD_PLUGIN_CHOOSER_H #define GD_PLUGIN_CHOOSER_H #include #include #include "window.h" class Channel; class geChoice; class geButton; class geButton; class gePluginBrowser; class gdPluginChooser : public gdWindow { private: Channel *ch; // ch == nullptr ? masterOut int stackType; geChoice *sortMethod; geButton *addBtn; geButton *cancelBtn; gePluginBrowser *browser; static void cb_close(Fl_Widget *w, void *p); static void cb_add (Fl_Widget *w, void *p); static void cb_sort (Fl_Widget *w, void *p); inline void __cb_close(); inline void __cb_add (); inline void __cb_sort (); public: gdPluginChooser(int x, int y, int w, int h, int stackType, Channel *ch=nullptr); ~gdPluginChooser(); }; #endif #endif // #ifdef WITH_VST giada-0.14.5/src/gui/dialogs/pluginList.cpp000066400000000000000000000256761322662744500205340ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifdef WITH_VST #include "../../utils/gui.h" #include "../../utils/fs.h" #include "../../core/conf.h" #include "../../core/const.h" #include "../../core/graphics.h" #include "../../core/pluginHost.h" #include "../../core/plugin.h" #include "../../core/mixer.h" #include "../../core/channel.h" #include "../../glue/plugin.h" #include "../../utils/log.h" #include "../../utils/string.h" #include "../elems/basics/boxtypes.h" #include "../elems/basics/idButton.h" #include "../elems/basics/statusButton.h" #include "../elems/basics/choice.h" #include "../elems/mainWindow/mainIO.h" #include "../elems/mainWindow/keyboard/channel.h" #include "pluginChooser.h" #include "pluginWindow.h" #include "pluginWindowGUI.h" #include "gd_mainWindow.h" #include "pluginList.h" extern gdMainWindow* G_MainWin; using std::string; using namespace giada::m; using namespace giada::c; gdPluginList::gdPluginList(int stackType, Channel* ch) : gdWindow(468, 204), ch(ch), stackType(stackType) { if (conf::pluginListX) resize(conf::pluginListX, conf::pluginListY, w(), h()); list = new Fl_Scroll(8, 8, 476, 188); list->type(Fl_Scroll::VERTICAL); list->scrollbar.color(G_COLOR_GREY_2); list->scrollbar.selection_color(G_COLOR_GREY_4); list->scrollbar.labelcolor(G_COLOR_LIGHT_1); list->scrollbar.slider(G_CUSTOM_BORDER_BOX); list->begin(); refreshList(); list->end(); end(); set_non_modal(); /* TODO - awful stuff... we should subclass into gdPluginListChannel and gdPluginListMaster */ if (stackType == pluginHost::MASTER_OUT) label("Master Out Plugins"); else if (stackType == pluginHost::MASTER_IN) label("Master In Plugins"); else { string l = "Channel " + gu_iToString(ch->index+1) + " Plugins"; copy_label(l.c_str()); } gu_setFavicon(this); show(); } /* -------------------------------------------------------------------------- */ gdPluginList::~gdPluginList() { conf::pluginListX = x(); conf::pluginListY = y(); } /* -------------------------------------------------------------------------- */ void gdPluginList::cb_addPlugin(Fl_Widget* v, void* p) { ((gdPluginList*)p)->cb_addPlugin(); } /* -------------------------------------------------------------------------- */ void gdPluginList::cb_refreshList(Fl_Widget* v, void* p) { /* note: this callback is fired by gdBrowser. Close its window first, * by calling the parent (pluginList) and telling it to delete its * subwindow (i.e. gdBrowser). */ gdWindow *child = (gdWindow*) v; if (child->getParent() != nullptr) (child->getParent())->delSubWindow(child); /* finally refresh plugin list: void *p is a pointer to gdPluginList. * This callback works even when you click 'x' to close the window... * well, who cares */ ((gdPluginList*)p)->refreshList(); ((gdPluginList*)p)->redraw(); } /* -------------------------------------------------------------------------- */ void gdPluginList::cb_addPlugin() { /* the usual callback that gdWindow adds to each subwindow in this case * is not enough, because when we close the browser the plugin list * must be redrawn. We have a special callback, cb_refreshList, which * we add to gdPluginChooser. It does exactly what we need. */ gdPluginChooser* pc = new gdPluginChooser(conf::pluginChooserX, conf::pluginChooserY, conf::pluginChooserW, conf::pluginChooserH, stackType, ch); addSubWindow(pc); pc->callback(cb_refreshList, (void*)this); // 'this' refers to gdPluginList } /* -------------------------------------------------------------------------- */ void gdPluginList::refreshList() { /* delete the previous list */ list->clear(); list->scroll_to(0, 0); /* add new buttons, as many as the plugin in pluginHost::stack + 1, * the 'add new' button. Warning: if ch == nullptr we are working with * master in/master out stacks. */ int numPlugins = pluginHost::countPlugins(stackType, ch); int i = 0; while (ix(), list->y()-list->yposition()+(i*24), 800); list->add(gdp); i++; } int addPlugY = numPlugins == 0 ? 90 : list->y()-list->yposition()+(i*24); addPlugin = new geButton(8, addPlugY, 452, 20, "-- add new plugin --"); addPlugin->callback(cb_addPlugin, (void*)this); list->add(addPlugin); /* if num(plugins) > 7 make room for the side scrollbar. * Scrollbar.width = 20 + 4(margin) */ if (i>7) size(492, h()); else size(468, h()); redraw(); /* set 'full' flag to FX button */ /* TODO - awful stuff... we should subclass into gdPluginListChannel and gdPluginListMaster */ if (stackType == pluginHost::MASTER_OUT) { G_MainWin->mainIO->setMasterFxOutFull( pluginHost::countPlugins(stackType, ch) > 0); } else if (stackType == pluginHost::MASTER_IN) { G_MainWin->mainIO->setMasterFxInFull( pluginHost::countPlugins(stackType, ch) > 0); } else { ch->guiChannel->fx->status = pluginHost::countPlugins(stackType, ch) > 0; ch->guiChannel->fx->redraw(); } } /* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */ gdPlugin::gdPlugin(gdPluginList* gdp, Plugin* p, int X, int Y, int W) : Fl_Group(X, Y, W, 20), pParent(gdp), pPlugin (p) { begin(); button = new geIdButton(8, y(), 220, 20); program = new geChoice(button->x()+button->w()+4, y(), 132, 20); bypass = new geIdButton(program->x()+program->w()+4, y(), 20, 20); shiftUp = new geIdButton(bypass->x()+bypass->w()+4, y(), 20, 20, "", fxShiftUpOff_xpm, fxShiftUpOn_xpm); shiftDown = new geIdButton(shiftUp->x()+shiftUp->w()+4, y(), 20, 20, "", fxShiftDownOff_xpm, fxShiftDownOn_xpm); remove = new geIdButton(shiftDown->x()+shiftDown->w()+4, y(), 20, 20, "", fxRemoveOff_xpm, fxRemoveOn_xpm); end(); button->copy_label(pPlugin->getName().c_str()); button->callback(cb_openPluginWindow, (void*)this); program->callback(cb_setProgram, (void*)this); for (int i=0; igetNumPrograms(); i++) program->add(gu_removeFltkChars(pPlugin->getProgramName(i)).c_str()); if (program->size() == 0) { program->add("-- no programs --\0"); program->deactivate(); } else program->value(pPlugin->getCurrentProgram()); bypass->callback(cb_setBypass, (void*)this); bypass->type(FL_TOGGLE_BUTTON); bypass->value(pPlugin->isBypassed() ? 0 : 1); shiftUp->callback(cb_shiftUp, (void*)this); shiftDown->callback(cb_shiftDown, (void*)this); remove->callback(cb_removePlugin, (void*)this); } /* -------------------------------------------------------------------------- */ void gdPlugin::cb_removePlugin (Fl_Widget* v, void* p) { ((gdPlugin*)p)->cb_removePlugin(); } void gdPlugin::cb_openPluginWindow(Fl_Widget* v, void* p) { ((gdPlugin*)p)->cb_openPluginWindow(); } void gdPlugin::cb_setBypass (Fl_Widget* v, void* p) { ((gdPlugin*)p)->cb_setBypass(); } void gdPlugin::cb_shiftUp (Fl_Widget* v, void* p) { ((gdPlugin*)p)->cb_shiftUp(); } void gdPlugin::cb_shiftDown (Fl_Widget* v, void* p) { ((gdPlugin*)p)->cb_shiftDown(); } void gdPlugin::cb_setProgram (Fl_Widget* v, void* p) { ((gdPlugin*)p)->cb_setProgram(); } /* -------------------------------------------------------------------------- */ void gdPlugin::cb_shiftUp() { /*nothing to do if there's only one plugin */ if (pluginHost::countPlugins(pParent->stackType, pParent->ch) == 1) return; int pluginIndex = pluginHost::getPluginIndex(pPlugin->getId(), pParent->stackType, pParent->ch); if (pluginIndex == 0) // first of the stack, do nothing return; plugin::swapPlugins(pParent->ch, pluginIndex, pluginIndex-1, pParent->stackType); pParent->refreshList(); } /* -------------------------------------------------------------------------- */ void gdPlugin::cb_shiftDown() { /*nothing to do if there's only one plugin */ if (pluginHost::countPlugins(pParent->stackType, pParent->ch) == 1) return; unsigned pluginIndex = pluginHost::getPluginIndex(pPlugin->getId(), pParent->stackType, pParent->ch); unsigned stackSize = (pluginHost::getStack(pParent->stackType, pParent->ch))->size(); if (pluginIndex == stackSize-1) // last one in the stack, do nothing return; plugin::swapPlugins(pParent->ch, pluginIndex, pluginIndex+1, pParent->stackType); pParent->refreshList(); } /* -------------------------------------------------------------------------- */ void gdPlugin::cb_removePlugin() { /* any subwindow linked to the plugin must be destroyed first */ pParent->delSubWindow(pPlugin->getId()); plugin::freePlugin(pParent->ch, pPlugin->getId(), pParent->stackType); pParent->refreshList(); } /* -------------------------------------------------------------------------- */ void gdPlugin::cb_openPluginWindow() { /* the new pluginWindow has id = id_plugin + 1, because id=0 is reserved * for the parent window 'add plugin'. */ int pwid = pPlugin->getId() + 1; gdWindow* w; if (pPlugin->hasEditor()) { if (pPlugin->isEditorOpen()) { gu_log("[gdPlugin::__cb_openPluginWindow] Plug-in has editor but it's already visible\n"); pParent->getChild(pwid)->show(); // Raise it to top return; } gu_log("[gdPlugin::__cb_openPluginWindow] Plug-in has editor, window id=%d\n", pwid); w = new gdPluginWindowGUI(pPlugin); } else { w = new gdPluginWindow(pPlugin); gu_log("[gdPlugin::__cb_openPluginWindow] Plug-in has no editor, window id=%d\n", pwid); } if (pParent->hasWindow(pwid)) pParent->delSubWindow(pwid); w->setId(pwid); pParent->addSubWindow(w); } /* -------------------------------------------------------------------------- */ void gdPlugin::cb_setBypass() { pPlugin->toggleBypass(); } /* -------------------------------------------------------------------------- */ void gdPlugin::cb_setProgram() { //pPlugin->setCurrentProgram(program->value()); plugin::setProgram(pPlugin, program->value()); } #endif // #ifdef WITH_VST giada-0.14.5/src/gui/dialogs/pluginList.h000066400000000000000000000053031322662744500201620ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifdef WITH_VST #ifndef GD_PLUGINLIST_H #define GD_PLUGINLIST_H #include #include #include "window.h" class Plugin; class Channel; class geButton; class gdPluginList; class geIdButton; class geChoice; class gdPluginList : public gdWindow { private: geButton *addPlugin; Fl_Scroll *list; static void cb_addPlugin(Fl_Widget *v, void *p); void cb_addPlugin(); public: Channel *ch; // ch == nullptr ? masterOut int stackType; gdPluginList(int stackType, Channel *ch=nullptr); ~gdPluginList(); /* special callback, passed to browser. When closed (i.e. plugin * has been selected) the same browser will refresh this window. */ static void cb_refreshList(Fl_Widget*, void*); void refreshList(); }; /* -------------------------------------------------------------------------- */ class gdPlugin : public Fl_Group { private: gdPluginList *pParent; Plugin *pPlugin; static void cb_removePlugin(Fl_Widget *v, void *p); static void cb_openPluginWindow(Fl_Widget *v, void *p); static void cb_setBypass(Fl_Widget *v, void *p); static void cb_shiftUp(Fl_Widget *v, void *p); static void cb_shiftDown(Fl_Widget *v, void *p); static void cb_setProgram(Fl_Widget *v, void *p); void cb_removePlugin(); void cb_openPluginWindow(); void cb_setBypass(); void cb_shiftUp(); void cb_shiftDown(); void cb_setProgram(); public: geIdButton *button; geChoice *program; geIdButton *bypass; geIdButton *shiftUp; geIdButton *shiftDown; geIdButton *remove; gdPlugin(gdPluginList *gdp, Plugin *p, int x, int y, int w); }; #endif #endif // #ifdef WITH_VST giada-0.14.5/src/gui/dialogs/pluginWindow.cpp000066400000000000000000000056731322662744500210630ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifdef WITH_VST #include #include "../../utils/gui.h" #include "../../core/plugin.h" #include "../../core/const.h" #include "../elems/basics/liquidScroll.h" #include "../elems/plugin/pluginParameter.h" #include "pluginWindow.h" gdPluginWindow::gdPluginWindow(Plugin* p) : gdWindow(450, 156), m_plugin(p) { set_non_modal(); m_list = new geLiquidScroll(G_GUI_OUTER_MARGIN, G_GUI_OUTER_MARGIN, w()-(G_GUI_OUTER_MARGIN*2), h()-(G_GUI_OUTER_MARGIN*2)); m_list->type(Fl_Scroll::VERTICAL_ALWAYS); m_list->begin(); int labelWidth = getLabelWidth(); int numParams = m_plugin->getNumParameters(); for (int i=0; iy() + (i * (G_GUI_UNIT + G_GUI_INNER_MARGIN)); int pw = m_list->w() - m_list->scrollbar_size() - (G_GUI_OUTER_MARGIN*3); new gePluginParameter(i, m_plugin, m_list->x(), py, pw, labelWidth); } m_list->end(); end(); label(m_plugin->getName().c_str()); size_range(450, (G_GUI_UNIT + (G_GUI_OUTER_MARGIN*2))); resizable(m_list); gu_setFavicon(this); show(); } /* -------------------------------------------------------------------------- */ void gdPluginWindow::updateParameter(int index, bool changeSlider) { static_cast(m_list->child(index))->update(changeSlider); } void gdPluginWindow::updateParameters(bool changeSlider) { for (int i=0; igetNumParameters(); i++) { static_cast(m_list->child(i))->update(changeSlider); } } /* -------------------------------------------------------------------------- */ int gdPluginWindow::getLabelWidth() const { int width = 0; int numParams = m_plugin->getNumParameters(); for (int i=0; igetParameterName(i).c_str(), wl, hl); if (wl > width) width = wl; } return width; } #endif // #ifdef WITH_VST giada-0.14.5/src/gui/dialogs/pluginWindow.h000066400000000000000000000031021322662744500205110ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifdef WITH_VST #ifndef GD_PLUGIN_WINDOW_H #define GD_PLUGIN_WINDOW_H #include "window.h" class Plugin; class geBox; class geSlider; class geLiquidScroll; class gdPluginWindow : public gdWindow { private: Plugin* m_plugin; geLiquidScroll* m_list; int getLabelWidth() const; public: gdPluginWindow(Plugin* p); void updateParameter(int index, bool changeSlider=false); void updateParameters(bool changeSlider=false); }; #endif #endif // #ifdef WITH_VST giada-0.14.5/src/gui/dialogs/pluginWindowGUI.cpp000066400000000000000000000061701322662744500214210ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifdef WITH_VST #include #include "../../utils/log.h" #include "../../utils/gui.h" #include "../../core/pluginHost.h" #include "../../core/plugin.h" #include "../../core/const.h" #include "pluginWindowGUI.h" #ifdef __APPLE__ #import "../../utils/cocoa.h" // objective-c #endif using namespace giada::m; gdPluginWindowGUI::gdPluginWindowGUI(Plugin *pPlugin) : gdWindow(450, 300), pPlugin(pPlugin) { show(); #ifndef __APPLE__ Fl::check(); #endif gu_log("[gdPluginWindowGUI] opening GUI, this=%p, xid=%p\n", (void*) this, (void*) fl_xid(this)); #if defined(__APPLE__) void *cocoaWindow = (void*) fl_xid(this); cocoa_setWindowSize(cocoaWindow, pPlugin->getEditorW(), pPlugin->getEditorH()); pPlugin->showEditor(cocoa_getViewFromWindow(cocoaWindow)); #else pPlugin->showEditor((void*) fl_xid(this)); #endif int pluginW = pPlugin->getEditorW(); int pluginH = pPlugin->getEditorH(); resize((Fl::w() - pluginW) / 2, (Fl::h() - pluginH) / 2, pluginW, pluginH); Fl::add_timeout(G_GUI_PLUGIN_RATE, cb_refresh, (void*) this); copy_label(pPlugin->getName().c_str()); } /* -------------------------------------------------------------------------- */ void gdPluginWindowGUI::cb_close(Fl_Widget *v, void *p) { ((gdPluginWindowGUI*)p)->__cb_close(); } void gdPluginWindowGUI::cb_refresh(void *data) { ((gdPluginWindowGUI*)data)->__cb_refresh(); } /* -------------------------------------------------------------------------- */ void gdPluginWindowGUI::__cb_close() { Fl::remove_timeout(cb_refresh); pPlugin->closeEditor(); gu_log("[gdPluginWindowGUI::__cb_close] GUI closed, this=%p\n", (void*) this); } /* -------------------------------------------------------------------------- */ void gdPluginWindowGUI::__cb_refresh() { pluginHost::runDispatchLoop(); Fl::repeat_timeout(G_GUI_PLUGIN_RATE, cb_refresh, (void*) this); } /* -------------------------------------------------------------------------- */ gdPluginWindowGUI::~gdPluginWindowGUI() { __cb_close(); } #endif // #ifdef WITH_VST giada-0.14.5/src/gui/dialogs/pluginWindowGUI.h000066400000000000000000000032741322662744500210700ustar00rootroot00000000000000 /* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * gd_pluginWindowGUI * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifdef WITH_VST #ifndef GD_PLUGIN_WINDOW_GUI_H #define GD_PLUGIN_WINDOW_GUI_H #include #include #include "window.h" #if defined(__APPLE__) #include #endif class Plugin; class gdPluginWindowGUI : public gdWindow { private: Plugin *pPlugin; static void cb_close (Fl_Widget *v, void *p); static void cb_refresh (void *data); inline void __cb_close (); inline void __cb_refresh(); public: gdPluginWindowGUI(Plugin *pPlugin); ~gdPluginWindowGUI(); }; #endif // include guard #endif // #ifdef WITH_VST giada-0.14.5/src/gui/dialogs/sampleEditor.cpp000066400000000000000000000245271322662744500210240ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include #include #include #include #include "../../glue/channel.h" #include "../../glue/sampleEditor.h" #include "../../core/waveFx.h" #include "../../core/conf.h" #include "../../core/const.h" #include "../../core/graphics.h" #include "../../core/sampleChannel.h" #include "../../core/mixer.h" #include "../../core/wave.h" #include "../../utils/gui.h" #include "../../utils/string.h" #include "../elems/basics/button.h" #include "../elems/basics/input.h" #include "../elems/basics/choice.h" #include "../elems/basics/dial.h" #include "../elems/basics/box.h" #include "../elems/basics/check.h" #include "../elems/sampleEditor/waveform.h" #include "../elems/sampleEditor/waveTools.h" #include "../elems/sampleEditor/volumeTool.h" #include "../elems/sampleEditor/boostTool.h" #include "../elems/sampleEditor/panTool.h" #include "../elems/sampleEditor/pitchTool.h" #include "../elems/sampleEditor/rangeTool.h" #include "../elems/sampleEditor/shiftTool.h" #include "../elems/mainWindow/keyboard/channel.h" #include "gd_warnings.h" #include "sampleEditor.h" using std::string; using namespace giada::m; using namespace giada::c; gdSampleEditor::gdSampleEditor(SampleChannel* ch) : gdWindow(640, 480), ch(ch) { Fl_Group* upperBar = createUpperBar(); waveTools = new geWaveTools(G_GUI_OUTER_MARGIN, upperBar->y()+upperBar->h()+G_GUI_OUTER_MARGIN, w()-16, h()-128, ch); Fl_Group* bottomBar = createBottomBar(G_GUI_OUTER_MARGIN, waveTools->y()+waveTools->h()+G_GUI_OUTER_MARGIN, h()-waveTools->h()-upperBar->h()-32); add(upperBar); add(waveTools); add(bottomBar); resizable(waveTools); gu_setFavicon(this); set_non_modal(); copy_label(ch->getName().c_str()); size_range(720, 480); if (conf::sampleEditorX) resize(conf::sampleEditorX, conf::sampleEditorY, conf::sampleEditorW, conf::sampleEditorH); show(); } /* -------------------------------------------------------------------------- */ gdSampleEditor::~gdSampleEditor() { conf::sampleEditorX = x(); conf::sampleEditorY = y(); conf::sampleEditorW = w(); conf::sampleEditorH = h(); conf::sampleEditorGridVal = atoi(grid->text()); conf::sampleEditorGridOn = snap->value(); sampleEditor::setPreview(ch, G_PREVIEW_NONE); } /* -------------------------------------------------------------------------- */ Fl_Group* gdSampleEditor::createUpperBar() { Fl_Group* g = new Fl_Group(G_GUI_OUTER_MARGIN, G_GUI_OUTER_MARGIN, w()-16, G_GUI_UNIT); g->begin(); grid = new geChoice(g->x(), g->y(), 50, G_GUI_UNIT); snap = new geCheck(grid->x()+grid->w()+4, g->y()+3, 12, 12, "Snap"); sep1 = new geBox(snap->x()+snap->w()+4, g->y(), 506, G_GUI_UNIT); zoomOut = new geButton(sep1->x()+sep1->w()+4, g->y(), G_GUI_UNIT, G_GUI_UNIT, "", zoomOutOff_xpm, zoomOutOn_xpm); zoomIn = new geButton(zoomOut->x()+zoomOut->w()+4, g->y(), G_GUI_UNIT, G_GUI_UNIT, "", zoomInOff_xpm, zoomInOn_xpm); g->end(); g->resizable(sep1); grid->add("(off)"); grid->add("2"); grid->add("3"); grid->add("4"); grid->add("6"); grid->add("8"); grid->add("16"); grid->add("32"); grid->add("64"); if (conf::sampleEditorGridVal == 0) grid->value(0); else grid->value(grid->find_item(gu_iToString(conf::sampleEditorGridVal).c_str())); grid->callback(cb_changeGrid, (void*)this); snap->value(conf::sampleEditorGridOn); snap->callback(cb_enableSnap, (void*)this); /* TODO - redraw grid if != (off) */ zoomOut->callback(cb_zoomOut, (void*)this); zoomIn->callback(cb_zoomIn, (void*)this); return g; } /* -------------------------------------------------------------------------- */ Fl_Group* gdSampleEditor::createOpTools(int x, int y, int h) { Fl_Group* g = new Fl_Group(x, y, 572, h); g->begin(); g->resizable(0); volumeTool = new geVolumeTool(g->x(), g->y(), ch); boostTool = new geBoostTool(volumeTool->x()+volumeTool->w()+4, g->y(), ch); panTool = new gePanTool(boostTool->x()+boostTool->w()+4, g->y(), ch); pitchTool = new gePitchTool(g->x(), panTool->y()+panTool->h()+8, ch); rangeTool = new geRangeTool(g->x(), pitchTool->y()+pitchTool->h()+8, ch); shiftTool = new geShiftTool(rangeTool->x()+rangeTool->w()+4, pitchTool->y()+pitchTool->h()+8, ch); reload = new geButton(g->x()+g->w()-70, shiftTool->y(), 70, 20, "Reload"); g->end(); if (ch->wave->isLogical()) // Logical samples (aka takes) cannot be reloaded. reload->deactivate(); reload->callback(cb_reload, (void*)this); return g; } /* -------------------------------------------------------------------------- */ Fl_Group* gdSampleEditor::createPreviewBox(int x, int y, int h) { Fl_Group* g = new Fl_Group(x, y, 110, h); g->begin(); rewind = new geButton(g->x(), g->y()+(g->h()/2)-12, 25, 25, "", rewindOff_xpm, rewindOn_xpm); play = new geButton(rewind->x()+rewind->w()+4, g->y()+(g->h()/2)-12, 25, 25, "", play_xpm, pause_xpm); loop = new geCheck(play->x()+play->w()+6, g->y()+(g->h()/2)-6, 12, 12, "Loop"); g->end(); play->callback(cb_togglePreview, (void*)this); rewind->callback(cb_rewindPreview, (void*)this); ch->setOnEndPreviewCb([this] { play->value(0); }); return g; } /* -------------------------------------------------------------------------- */ Fl_Group* gdSampleEditor::createInfoBox(int x, int y, int h) { Fl_Group* g = new Fl_Group(x, y, 400, h); g->begin(); info = new geBox(g->x(), g->y(), g->w(), g->h()); g->end(); info->align(FL_ALIGN_LEFT | FL_ALIGN_INSIDE | FL_ALIGN_TOP); updateInfo(); return g; } /* -------------------------------------------------------------------------- */ Fl_Group* gdSampleEditor::createBottomBar(int x, int y, int h) { Fl_Group* g = new Fl_Group(8, waveTools->y()+waveTools->h()+8, w()-16, h); g->begin(); Fl_Group* previewBox = createPreviewBox(g->x(), g->y(), g->h()); geBox* divisor1 = new geBox(previewBox->x()+previewBox->w()+8, g->y(), 1, g->h()); divisor1->box(FL_BORDER_BOX); Fl_Group* opTools = createOpTools(divisor1->x()+divisor1->w()+12, g->y(), g->h()); geBox* divisor2 = new geBox(opTools->x()+opTools->w()+8, g->y(), 1, g->h()); divisor2->box(FL_BORDER_BOX); createInfoBox(divisor2->x()+divisor2->w()+8, g->y(), g->h()); g->end(); g->resizable(0); return g; } /* -------------------------------------------------------------------------- */ void gdSampleEditor::cb_reload (Fl_Widget* w, void* p) { ((gdSampleEditor*)p)->cb_reload(); } void gdSampleEditor::cb_zoomIn (Fl_Widget* w, void* p) { ((gdSampleEditor*)p)->cb_zoomIn(); } void gdSampleEditor::cb_zoomOut (Fl_Widget* w, void* p) { ((gdSampleEditor*)p)->cb_zoomOut(); } void gdSampleEditor::cb_changeGrid (Fl_Widget* w, void* p) { ((gdSampleEditor*)p)->cb_changeGrid(); } void gdSampleEditor::cb_enableSnap (Fl_Widget* w, void* p) { ((gdSampleEditor*)p)->cb_enableSnap(); } void gdSampleEditor::cb_togglePreview(Fl_Widget* w, void* p) { ((gdSampleEditor*)p)->cb_togglePreview(); } void gdSampleEditor::cb_rewindPreview(Fl_Widget* w, void* p) { ((gdSampleEditor*)p)->cb_rewindPreview(); } /* -------------------------------------------------------------------------- */ void gdSampleEditor::cb_enableSnap() { waveTools->waveform->setSnap(!waveTools->waveform->getSnap()); } /* -------------------------------------------------------------------------- */ void gdSampleEditor::cb_togglePreview() { if (play->value()) sampleEditor::setPreview(ch, G_PREVIEW_NONE); else sampleEditor::setPreview(ch, loop->value() ? G_PREVIEW_LOOP : G_PREVIEW_NORMAL); } void gdSampleEditor::cb_rewindPreview() { sampleEditor::rewindPreview(ch); } /* -------------------------------------------------------------------------- */ void gdSampleEditor::cb_reload() { /* TODO - move to glue::sampleEditor */ if (!gdConfirmWin("Warning", "Reload sample: are you sure?")) return; if (glue_loadChannel(ch, ch->wave->getPath()) != G_RES_OK) return; glue_setBoost(ch, G_DEFAULT_BOOST); glue_setPitch(ch, G_DEFAULT_PITCH); glue_setPanning(ch, 0.5f); panTool->refresh(); boostTool->refresh(); waveTools->waveform->stretchToWindow(); waveTools->updateWaveform(); sampleEditor::setBeginEnd(ch, 0, ch->wave->getSize()); redraw(); } /* -------------------------------------------------------------------------- */ void gdSampleEditor::cb_zoomIn() { waveTools->waveform->setZoom(-1); waveTools->redraw(); } /* -------------------------------------------------------------------------- */ void gdSampleEditor::cb_zoomOut() { waveTools->waveform->setZoom(0); waveTools->redraw(); } /* -------------------------------------------------------------------------- */ void gdSampleEditor::cb_changeGrid() { waveTools->waveform->setGridLevel(atoi(grid->text())); } /* -------------------------------------------------------------------------- */ void gdSampleEditor::updateInfo() { string bitDepth = ch->wave->getBits() != 0 ? gu_iToString(ch->wave->getBits()) : "(unknown)"; string infoText = "File: " + ch->wave->getPath() + "\n" "Size: " + gu_iToString(ch->wave->getSize()) + " frames\n" "Duration: " + gu_iToString(ch->wave->getDuration()) + " seconds\n" "Bit depth: " + bitDepth + "\n" "Frequency: " + gu_iToString(ch->wave->getRate()) + " Hz\n"; info->copy_label(infoText.c_str()); } giada-0.14.5/src/gui/dialogs/sampleEditor.h000066400000000000000000000052721322662744500204650ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef GD_EDITOR_H #define GD_EDITOR_H #include "window.h" class SampleChannel; class geButton; class geWaveTools; class geVolumeTool; class geBoostTool; class gePanTool; class gePitchTool; class geRangeTool; class geSampleTool; class geShiftTool; class geChoice; class geCheck; class geBox; class geButton; class gdSampleEditor : public gdWindow { friend class geWaveform; private: Fl_Group* createUpperBar(); Fl_Group* createBottomBar(int x, int y, int h); Fl_Group* createPreviewBox(int x, int y, int h); Fl_Group* createOpTools(int x, int y, int h); Fl_Group* createInfoBox(int x, int y, int h); static void cb_reload (Fl_Widget* w, void* p); static void cb_zoomIn (Fl_Widget* w, void* p); static void cb_zoomOut (Fl_Widget* w, void* p); static void cb_changeGrid(Fl_Widget* w, void* p); static void cb_enableSnap(Fl_Widget* w, void* p); static void cb_togglePreview(Fl_Widget* w, void* p); static void cb_rewindPreview(Fl_Widget* w, void* p); void cb_reload(); void cb_zoomIn(); void cb_zoomOut(); void cb_changeGrid(); void cb_enableSnap(); void cb_togglePreview(); void cb_rewindPreview(); public: gdSampleEditor(SampleChannel* ch); ~gdSampleEditor(); void updateInfo(); geChoice* grid; geCheck* snap; geBox* sep1; geButton* zoomIn; geButton* zoomOut; geWaveTools* waveTools; geVolumeTool* volumeTool; geBoostTool* boostTool; gePanTool* panTool; gePitchTool* pitchTool; geRangeTool* rangeTool; geShiftTool* shiftTool; geButton* reload; geButton* play; geButton* rewind; geCheck* loop; geBox* info; SampleChannel* ch; }; #endif giada-0.14.5/src/gui/dialogs/window.cpp000066400000000000000000000114011322662744500176660ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include "../../utils/log.h" #include "window.h" void __cb_window_closer(Fl_Widget *v, void *p) { delete (Fl_Window*) p; } /* -------------------------------------------------------------------------- */ gdWindow::gdWindow(int x, int y, int w, int h, const char *title, int id) : Fl_Double_Window(x, y, w, h, title), id(id), parent(nullptr) { } /* -------------------------------------------------------------------------- */ gdWindow::gdWindow(int w, int h, const char *title, int id) : Fl_Double_Window(w, h, title), id(id), parent(nullptr) { } /* -------------------------------------------------------------------------- */ gdWindow::~gdWindow() { /* delete all subwindows in order to empty the stack */ for (unsigned i=0; igetParent() != nullptr) (child->getParent())->delSubWindow(child); } /* -------------------------------------------------------------------------- */ void gdWindow::addSubWindow(gdWindow *w) { /** TODO - useless: delete ---------------------------------------- */ for (unsigned i=0; igetId() == subWindows.at(i)->getId()) { //gu_log("[gdWindow] window %p (id=%d) exists, not added (and deleted)\n", (void*)w, w->getId()); delete w; return; } /** --------------------------------------------------------------- */ w->setParent(this); w->callback(cb_closeChild); // you can pass params: w->callback(cb_closeChild, (void*)params) subWindows.push_back(w); //debug(); } /* -------------------------------------------------------------------------- */ void gdWindow::delSubWindow(gdWindow *w) { for (unsigned i=0; igetId() == subWindows.at(i)->getId()) { delete subWindows.at(i); subWindows.erase(subWindows.begin() + i); //debug(); return; } //debug(); } /* -------------------------------------------------------------------------- */ void gdWindow::delSubWindow(int id) { for (unsigned i=0; igetId() == id) { delete subWindows.at(i); subWindows.erase(subWindows.begin() + i); //debug(); return; } //debug(); } /* -------------------------------------------------------------------------- */ int gdWindow::getId() { return id; } void gdWindow::setId(int id) { this->id = id; } /* -------------------------------------------------------------------------- */ void gdWindow::debug() { gu_log("---- window stack (id=%d): ----\n", getId()); for (unsigned i=0; igetId()); gu_log("----\n"); } /* -------------------------------------------------------------------------- */ gdWindow *gdWindow::getParent() { return parent; } void gdWindow::setParent(gdWindow *w) { parent = w; } /* -------------------------------------------------------------------------- */ bool gdWindow::hasWindow(int id) { for (unsigned i=0; igetId()) return true; return false; } /* -------------------------------------------------------------------------- */ gdWindow *gdWindow::getChild(int id) { for (unsigned i=0; igetId()) return subWindows.at(i); return nullptr; } giada-0.14.5/src/gui/dialogs/window.h000066400000000000000000000037561322662744500173510ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef GD_WINDOW_H #define GD_WINDOW_H #include #include /* cb_window_closer * callback for when closing windows. Deletes the widget (delete). */ void __cb_window_closer(Fl_Widget* v, void* p); class gdWindow : public Fl_Double_Window { protected: std::vector subWindows; int id; gdWindow* parent; public: gdWindow(int x, int y, int w, int h, const char* title=0, int id=0); gdWindow(int w, int h, const char* title=0, int id=0); ~gdWindow(); static void cb_closeChild(Fl_Widget* v, void* p); void addSubWindow(gdWindow* w); void delSubWindow(gdWindow* w); void delSubWindow(int id); int getId(); void setId(int id); void debug(); void setParent(gdWindow* w); gdWindow* getParent(); gdWindow* getChild(int id); /* hasWindow * true if the window with id 'id' exists in the stack. */ bool hasWindow(int id); }; #endif giada-0.14.5/src/gui/elems/000077500000000000000000000000001322662744500153415ustar00rootroot00000000000000giada-0.14.5/src/gui/elems/actionEditor/000077500000000000000000000000001322662744500177655ustar00rootroot00000000000000giada-0.14.5/src/gui/elems/actionEditor/action.cpp000066400000000000000000000166271322662744500217620ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include #include "../../../core/const.h" #include "../../../core/mixer.h" #include "../../../core/sampleChannel.h" #include "../../../utils/log.h" #include "../../dialogs/gd_actionEditor.h" #include "actionEditor.h" #include "action.h" using namespace giada::m; /** TODO - index is useless? * TODO - pass a record::action pointer and let geAction compute values */ geAction::geAction(int X, int Y, int H, int frame_a, unsigned index, gdActionEditor *parent, SampleChannel *ch, bool record, char type) : Fl_Box (X, Y, MIN_WIDTH, H), selected (false), index (index), parent (parent), ch (ch), type (type), frame_a (frame_a), onRightEdge(false), onLeftEdge (false) { /* bool 'record' defines how to understand the action. * record = false: don't record it, just show it. It happens when you * open the editor with some actions to be shown. * * record = true: record it AND show it. It happens when you click on * an empty area in order to add a new actions. First you record it * (addAction()) then you show it (FLTK::Draw()) */ if (record) addAction(); /* in order to show a singlepress action we must compute the frame_b. We * do that after the possible recording, otherwise we don't know which * key_release is associated. */ if (ch->mode == SINGLE_PRESS && type == G_ACTION_KEYPRESS) { recorder::action *a2 = nullptr; recorder::getNextAction(ch->index, G_ACTION_KEYREL, frame_a, &a2); if (a2) { frame_b = a2->frame; w((frame_b - frame_a)/parent->zoom); } else gu_log("[geActionEditor] frame_b not found! [%d:???]\n", frame_a); /* a singlepress action narrower than 8 pixel is useless. So check it. * Warning: if an action is 8 px narrow, it has no body space to drag * it. It's up to the user to zoom in and drag it. */ if (w() < MIN_WIDTH) size(MIN_WIDTH, h()); } } /* -------------------------------------------------------------------------- */ void geAction::draw() { int color; if (selected) /// && geActionEditor !disabled color = G_COLOR_LIGHT_2; else color = G_COLOR_LIGHT_1; if (ch->mode == SINGLE_PRESS) { fl_rectf(x(), y(), w(), h(), (Fl_Color) color); } else { if (type == G_ACTION_KILL) fl_rect(x(), y(), MIN_WIDTH, h(), (Fl_Color) color); else { fl_rectf(x(), y(), MIN_WIDTH, h(), (Fl_Color) color); if (type == G_ACTION_KEYPRESS) fl_rectf(x()+3, y()+h()-11, 2, 8, G_COLOR_GREY_4); else if (type == G_ACTION_KEYREL) fl_rectf(x()+3, y()+3, 2, 8, G_COLOR_GREY_4); } } } /* -------------------------------------------------------------------------- */ int geAction::handle(int e) { /* ret = 0 sends the event to the parent window. */ int ret = 0; switch (e) { case FL_ENTER: { selected = true; ret = 1; redraw(); break; } case FL_LEAVE: { fl_cursor(FL_CURSOR_DEFAULT, FL_WHITE, FL_BLACK); selected = false; ret = 1; redraw(); break; } case FL_MOVE: { /* handling of the two margins, left & right. 4 pixels are good enough */ if (ch->mode == SINGLE_PRESS) { onLeftEdge = false; onRightEdge = false; if (Fl::event_x() >= x() && Fl::event_x() < x()+4) { onLeftEdge = true; fl_cursor(FL_CURSOR_WE, FL_WHITE, FL_BLACK); } else if (Fl::event_x() >= x()+w()-4 && Fl::event_x() <= x()+w()) { onRightEdge = true; fl_cursor(FL_CURSOR_WE, FL_WHITE, FL_BLACK); } else fl_cursor(FL_CURSOR_DEFAULT, FL_WHITE, FL_BLACK); } } } return ret; } /* -------------------------------------------------------------------------- */ void geAction::addAction() { /* always check frame parity */ if (frame_a % 2 != 0) frame_a++; /* anatomy of an action * ____[#######]_____ (a) is the left margin, G_ACTION_KEYPRESS. (b) is * a b the right margin, the G_ACTION_KEYREL. This is the * theory behind the singleshot.press actions; for any other kind the * (b) is just a graphical and meaningless point. */ if (ch->mode == SINGLE_PRESS) { recorder::rec(parent->chan->index, G_ACTION_KEYPRESS, frame_a); recorder::rec(parent->chan->index, G_ACTION_KEYREL, frame_a+4096); //gu_log("action added, [%d, %d]\n", frame_a, frame_a+4096); } else { recorder::rec(parent->chan->index, parent->getActionType(), frame_a); //gu_log("action added, [%d]\n", frame_a); } parent->chan->hasActions = true; recorder::sortActions(); index++; // important! } /* -------------------------------------------------------------------------- */ void geAction::delAction() { /* if SINGLE_PRESS you must delete both the keypress and the keyrelease * actions. */ if (ch->mode == SINGLE_PRESS) { recorder::deleteAction(parent->chan->index, frame_a, G_ACTION_KEYPRESS, false, &mixer::mutex_recs); recorder::deleteAction(parent->chan->index, frame_b, G_ACTION_KEYREL, false, &mixer::mutex_recs); } else recorder::deleteAction(parent->chan->index, frame_a, type, false, &mixer::mutex_recs); parent->chan->hasActions = recorder::hasActions(parent->chan->index); /* restore the initial cursor shape, in case you delete an action and * the double arrow (for resizing) is displayed */ fl_cursor(FL_CURSOR_DEFAULT, FL_WHITE, FL_BLACK); } /* -------------------------------------------------------------------------- */ void geAction::moveAction(int frame_a) { /* easy one: delete previous action and record the new ones. As usual, * SINGLE_PRESS requires two jobs. If frame_a is valid, use that frame * value. */ delAction(); if (frame_a != -1) this->frame_a = frame_a; else this->frame_a = xToFrame_a(); /* always check frame parity */ if (this->frame_a % 2 != 0) this->frame_a++; recorder::rec(parent->chan->index, type, this->frame_a); if (ch->mode == SINGLE_PRESS) { frame_b = xToFrame_b(); recorder::rec(parent->chan->index, G_ACTION_KEYREL, frame_b); } parent->chan->hasActions = true; recorder::sortActions(); } /* -------------------------------------------------------------------------- */ int geAction::absx() { return x() - parent->ac->x(); } /* -------------------------------------------------------------------------- */ int geAction::xToFrame_a() { return (absx()) * parent->zoom; } /* -------------------------------------------------------------------------- */ int geAction::xToFrame_b() { return (absx() + w()) * parent->zoom; } giada-0.14.5/src/gui/elems/actionEditor/action.h000066400000000000000000000044461322662744500214230ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef GE_ACTION_H #define GE_ACTION_H #include class gdActionEditor; class SampleChannel; class geAction : public Fl_Box { private: bool selected; unsigned index; gdActionEditor *parent; // pointer to parent (geActionEditor) SampleChannel *ch; char type; // type of action public: geAction(int x, int y, int h, int frame_a, unsigned index, gdActionEditor *parent, SampleChannel *ch, bool record, char type); void draw(); int handle(int e); void addAction(); void delAction(); /* moveAction * shift the action on the x-axis and update Recorder. If frame_a != -1 * use the new frame in input (used while snapping) */ void moveAction(int frame_a=-1); /* absx * x() is relative to scrolling position. absx() returns the absolute * x value of the action, from the leftmost edge. */ int absx(); /* xToFrame_a,b * return the real frames of x() position */ int xToFrame_a(); int xToFrame_b(); int frame_a; // initial frame (KEYPRESS for singlemode.press) int frame_b; // terminal frame (KEYREL for singlemode.press, null for others) bool onRightEdge; bool onLeftEdge; static const int MIN_WIDTH = 8; }; #endif giada-0.14.5/src/gui/elems/actionEditor/actionEditor.cpp000066400000000000000000000303521322662744500231200ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include #include "../../../core/clock.h" #include "../../../core/sampleChannel.h" #include "../../dialogs/gd_mainWindow.h" #include "../../dialogs/gd_actionEditor.h" #include "../mainWindow/keyboard/keyboard.h" #include "action.h" #include "gridTool.h" #include "actionEditor.h" extern gdMainWindow *G_MainWin; using namespace giada::m; geActionEditor::geActionEditor(int x, int y, gdActionEditor *pParent, SampleChannel *ch) : geBaseActionEditor(x, y, 200, 40, pParent), ch (ch), selected (nullptr) { size(pParent->totalWidth, h()); /* add actions when the window opens. Their position is zoom-based; * each frame is / 2 because we don't care about stereo infos. */ for (unsigned i=0; ichan->index); - that are covered by the grey area (> G_Mixer.totalFrames); - of type G_ACTION_KILL in a SINGLE_PRESS channel. They cannot be recorded in such mode, but they can exist if you change from another mode to singlepress; - of type G_ACTION_KEYREL in a SINGLE_PRESS channel. It's up to geAction to find the other piece (namely frame_b) - not of types G_ACTION_KEYPRESS | G_ACTION_KEYREL | G_ACTION_KILL */ if ((action->chan != pParent->chan->index) || (recorder::frames.at(i) > clock::getTotalFrames()) || (action->type == G_ACTION_KILL && ch->mode == SINGLE_PRESS) || (action->type == G_ACTION_KEYREL && ch->mode == SINGLE_PRESS) || (action->type & ~(G_ACTION_KEYPRESS | G_ACTION_KEYREL | G_ACTION_KILL)) ) continue; int ax = x + (action->frame / pParent->zoom); geAction *a = new geAction( ax, // x y + 4, // y h() - 8, // h action->frame, // frame_a i, // n. of recordings pParent, // pointer to the pParent window ch, // pointer to SampleChannel false, // record = false: don't record it, we are just displaying it! action->type); // type of action add(a); } } end(); // mandatory when you add widgets to a fl_group, otherwise mega malfunctions } /* -------------------------------------------------------------------------- */ geAction *geActionEditor::getSelectedAction() { for (int i=0; ix(); int action_w = ((geAction*)child(i))->w(); if (Fl::event_x() >= action_x && Fl::event_x() <= action_x + action_w) return (geAction*)child(i); } return nullptr; } /* -------------------------------------------------------------------------- */ void geActionEditor::updateActions() { /* when zooming, don't delete and re-add actions, just MOVE them. This * function shifts the action by a zoom factor. Those singlepress are * stretched, as well */ geAction *a; for (int i=0; iframe_a / pParent->zoom); if (ch->mode == SINGLE_PRESS) { int newW = ((a->frame_b - a->frame_a) / pParent->zoom); if (newW < geAction::MIN_WIDTH) newW = geAction::MIN_WIDTH; a->resize(newX, a->y(), newW, a->h()); } else a->resize(newX, a->y(), geAction::MIN_WIDTH, a->h()); } } /* -------------------------------------------------------------------------- */ void geActionEditor::draw() { /* draw basic boundaries (+ beat bars) and hide the unused area. Then * draw the children (the actions) */ baseDraw(); /* print label */ fl_color(G_COLOR_GREY_4); fl_font(FL_HELVETICA, 12); if (active()) fl_draw("start/stop", x()+4, y(), w(), h(), (Fl_Align) (FL_ALIGN_LEFT | FL_ALIGN_CENTER)); /// FIXME h() is too much! else fl_draw("start/stop (disabled)", x()+4, y(), w(), h(), (Fl_Align) (FL_ALIGN_LEFT | FL_ALIGN_CENTER)); /// FIXME h() is too much! draw_children(); } /* -------------------------------------------------------------------------- */ int geActionEditor::handle(int e) { int ret = Fl_Group::handle(e); /* do nothing if the widget is deactivated. It could happen for loopmode * channels */ if (!active()) return 1; switch (e) { case FL_DRAG: { if (selected == nullptr) { // if you drag an empty area ret = 1; break; } /* if onLeftEdge o onRightEdge are true it means that you're resizing * an action. Otherwise move the widget. */ if (selected->onLeftEdge || selected->onRightEdge) { /* some checks: a) cannot resize an action < N pixels, b) no beyond zero, * c) no beyond bar maxwidth. Checks for overlap are done in FL_RELEASE */ if (selected->onRightEdge) { int aw = Fl::event_x()-selected->x(); int ah = selected->h(); if (Fl::event_x() < selected->x()+geAction::MIN_WIDTH) aw = geAction::MIN_WIDTH; else if (Fl::event_x() > pParent->coverX) aw = pParent->coverX-selected->x(); selected->size(aw, ah); } else { int ax = Fl::event_x(); int ay = selected->y(); int aw = selected->x()-Fl::event_x()+selected->w(); int ah = selected->h(); if (Fl::event_x() < x()) { ax = x(); aw = selected->w()+selected->x()-x(); } else if (Fl::event_x() > selected->x()+selected->w()-geAction::MIN_WIDTH) { ax = selected->x()+selected->w()-geAction::MIN_WIDTH; aw = geAction::MIN_WIDTH; } selected->resize(ax, ay, aw, ah); } } /* move the widget around */ else { int real_x = Fl::event_x() - actionPickPoint; if (real_x < x()) // don't go beyond the left border selected->position(x(), selected->y()); else if (real_x+selected->w() > pParent->coverX+x()) // don't go beyond the right border selected->position(pParent->coverX+x()-selected->w(), selected->y()); else { if (pParent->gridTool->isOn()) { int snpx = pParent->gridTool->getSnapPoint(real_x-x()) + x() -1; selected->position(snpx, selected->y()); } else selected->position(real_x, selected->y()); } } redraw(); ret = 1; break; } case FL_PUSH: { if (Fl::event_button1()) { /* avoid at all costs two overlapping actions. We use 'selected' because * the selected action can be reused in FL_DRAG, in case you want to move * it. */ selected = getSelectedAction(); if (selected == nullptr) { /* avoid click on grey area */ if (Fl::event_x() >= pParent->coverX) { ret = 1; break; } /* snap function, if enabled */ int ax = Fl::event_x(); int fx = (ax - x()) * pParent->zoom; if (pParent->gridTool->isOn()) { ax = pParent->gridTool->getSnapPoint(ax-x()) + x() -1; fx = pParent->gridTool->getSnapFrame(ax-x()); /* with snap=on an action can fall onto another */ if (actionCollides(fx)) { ret = 1; break; } } geAction *a = new geAction( ax, // x y()+4, // y h()-8, // h fx, // frame_a recorder::frames.size()-1, // n. of actions recorded pParent, // pParent window pointer ch, // pointer to SampleChannel true, // record = true: record it! pParent->getActionType()); // type of action add(a); G_MainWin->keyboard->setChannelWithActions((geSampleChannel*)ch->guiChannel); // mainWindow update redraw(); ret = 1; } else { actionOriginalX = selected->x(); actionOriginalW = selected->w(); actionPickPoint = Fl::event_x() - selected->x(); ret = 1; // for dragging } } else if (Fl::event_button3()) { geAction *a = getSelectedAction(); if (a != nullptr) { a->delAction(); remove(a); delete a; G_MainWin->keyboard->setChannelWithActions((geSampleChannel*)pParent->chan->guiChannel); redraw(); ret = 1; } } break; } case FL_RELEASE: { if (selected == nullptr) { ret = 1; break; } /* noChanges = true when you click on an action without doing anything * (dragging or moving it). */ bool noChanges = false; if (actionOriginalX == selected->x()) noChanges = true; if (ch->mode == SINGLE_PRESS && actionOriginalX+actionOriginalW != selected->x()+selected->w()) noChanges = false; if (noChanges) { ret = 1; selected = nullptr; break; } /* step 1: check if the action doesn't overlap with another one. * In case of overlap the moved action returns to the original X * value ("actionOriginalX"). */ bool overlap = false; for (int i=0; ix(); int action_w = ((geAction*)child(i))->w(); if (ch->mode == SINGLE_PRESS) { /* when 2 segments overlap? * start = the highest value between the two starting points * end = the lowest value between the two ending points * if start < end then there's an overlap of end-start pixels. */ int start = action_x >= selected->x() ? action_x : selected->x(); int end = action_x+action_w < selected->x()+selected->w() ? action_x+action_w : selected->x()+selected->w(); if (start < end) { selected->resize(actionOriginalX, selected->y(), actionOriginalW, selected->h()); redraw(); overlap = true; // one overlap: stop checking } } else { if (selected->x() == action_x) { selected->position(actionOriginalX, selected->y()); redraw(); overlap = true; // one overlap: stop checking } } } /* step 2: no overlap? then update the coordinates of the action, ie * delete the previous rec and create a new one. * Anyway the selected action becomes nullptr, because when you release * the mouse button the dragging process ends. */ if (!overlap) { if (pParent->gridTool->isOn()) { int f = pParent->gridTool->getSnapFrame(selected->absx()); selected->moveAction(f); } else selected->moveAction(); } selected = nullptr; ret = 1; break; } } return ret; } /* -------------------------------------------------------------------------- */ bool geActionEditor::actionCollides(int frame) { /* if SINGLE_PRESS we check that the tail (frame_b) of the action doesn't * overlap the head (frame) of the new one. First the general case, yet. */ bool collision = false; for (int i=0; iframe_a == frame) collision = true; if (ch->mode == SINGLE_PRESS) { for (int i=0; iframe_b && frame >= c->frame_a) collision = true; } } return collision; } giada-0.14.5/src/gui/elems/actionEditor/actionEditor.h000066400000000000000000000044031322662744500225630ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef GE_ACTION_EDITOR_H #define GE_ACTION_EDITOR_H #include "baseActionEditor.h" class geAction; class SampleChannel; class geActionEditor : public geBaseActionEditor { private: SampleChannel *ch; /* getSelectedAction * get the action under the mouse. nullptr if nothing found. */ geAction *getSelectedAction(); /* selected * pointer to the selected action. Useful when dragging around. */ geAction *selected; /* actionOriginalX, actionOriginalW * x and w of the action, when moved. Useful for checking if the action * overlaps another one: in that case the moved action returns to * actionOriginalX (and to actionOriginalW if resized). */ int actionOriginalX; int actionOriginalW; /* actionPickPoint * the precise x point in which the action has been picked with the mouse, * before a dragging action. */ int actionPickPoint; /* actionCollides * true if an action collides with another. Used while adding new points * with snap active.*/ bool actionCollides(int frame); public: geActionEditor(int x, int y, gdActionEditor *pParent, SampleChannel *ch); void draw(); int handle(int e); void updateActions(); }; #endif giada-0.14.5/src/gui/elems/actionEditor/baseActionEditor.cpp000066400000000000000000000055621322662744500237200ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * geBaseActionEditor * Parent class of any widget inside the action editor. * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include #include "../../../core/mixer.h" #include "../../../core/const.h" #include "../../dialogs/gd_actionEditor.h" #include "gridTool.h" #include "baseActionEditor.h" geBaseActionEditor::geBaseActionEditor(int x, int y, int w, int h, gdActionEditor *pParent) : Fl_Group(x, y, w, h), pParent(pParent) {} /* -------------------------------------------------------------------------- */ geBaseActionEditor::~geBaseActionEditor() {} /* -------------------------------------------------------------------------- */ void geBaseActionEditor::baseDraw(bool clear) { /* clear the screen */ if (clear) fl_rectf(x(), y(), w(), h(), G_COLOR_GREY_1); /* draw the container */ fl_color(G_COLOR_GREY_4); fl_rect(x(), y(), w(), h()); /* grid drawing, if > 1 */ if (pParent->gridTool->getValue() > 1) { fl_color(fl_rgb_color(54, 54, 54)); fl_line_style(FL_DASH, 0, nullptr); for (int i=0; i<(int) pParent->gridTool->points.size(); i++) { int px = pParent->gridTool->points.at(i)+x()-1; fl_line(px, y()+1, px, y()+h()-2); } fl_line_style(0); } /* bars and beats drawing */ fl_color(G_COLOR_GREY_4); for (int i=0; i<(int) pParent->gridTool->beats.size(); i++) { int px = pParent->gridTool->beats.at(i)+x()-1; fl_line(px, y()+1, px, y()+h()-2); } fl_color(G_COLOR_LIGHT_1); for (int i=0; i<(int) pParent->gridTool->bars.size(); i++) { int px = pParent->gridTool->bars.at(i)+x()-1; fl_line(px, y()+1, px, y()+h()-2); } /* cover unused area. Avoid drawing cover if width == 0 (i.e. beats * are 32) */ int coverWidth = pParent->totalWidth-pParent->coverX; if (coverWidth != 0) fl_rectf(pParent->coverX+x(), y()+1, coverWidth, h()-2, G_COLOR_GREY_4); } giada-0.14.5/src/gui/elems/actionEditor/baseActionEditor.h000066400000000000000000000030761322662744500233630ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ge_actionWidget * * parent class of any tool inside the action editor. * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef GE_BASE_ACTION_EDITOR_H #define GE_BASE_ACTION_EDITOR_H #include class gdActionEditor; class geBaseActionEditor : public Fl_Group { protected: gdActionEditor *pParent; void baseDraw(bool clear=true); public: virtual void updateActions() = 0; geBaseActionEditor(int x, int y, int w, int h, gdActionEditor *pParent); ~geBaseActionEditor(); }; #endif giada-0.14.5/src/gui/elems/actionEditor/basePianoItem.cpp000066400000000000000000000035611322662744500232160ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include "pianoRoll.h" #include "basePianoItem.h" geBasePianoItem::geBasePianoItem(int x, int y, int w, gdActionEditor *pParent) : Fl_Box (x, y, w, gePianoRoll::CELL_H), pParent (pParent), selected(false) { } /* -------------------------------------------------------------------------- */ int geBasePianoItem::handle(int e) { int ret = 0; switch (e) { case FL_ENTER: selected = true; redraw(); ret = 1; break; case FL_LEAVE: selected = false; redraw(); ret = 1; break; } return ret; } /* -------------------------------------------------------------------------- */ int geBasePianoItem::getY(int note) { return (gePianoRoll::MAX_KEYS * gePianoRoll::CELL_H) - (note * gePianoRoll::CELL_H); } giada-0.14.5/src/gui/elems/actionEditor/basePianoItem.h000066400000000000000000000031321322662744500226550ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef GE_BASE_PIANO_ITEM_H #define GE_BASE_PIANO_ITEM_H #include #include "../../../core/recorder.h" class gdActionEditor; class geBasePianoItem : public Fl_Box { protected: geBasePianoItem(int x, int y, int w, gdActionEditor *pParent); /* getY * from a note, return the y position on piano roll */ int getY(int note); gdActionEditor *pParent; bool selected; public: virtual void reposition(int pianoRollX) = 0; int handle(int e) override; }; #endif giada-0.14.5/src/gui/elems/actionEditor/envelopeEditor.cpp000066400000000000000000000245631322662744500234670ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * envelopeEditor * * Parent class of any envelope controller, from volume to VST parameter * automations. * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include #include "../../../core/channel.h" #include "../../../core/recorder.h" #include "../../../core/mixer.h" #include "../../../core/clock.h" #include "../../dialogs/gd_actionEditor.h" #include "../../dialogs/gd_mainWindow.h" #include "../mainWindow/keyboard/keyboard.h" #include "gridTool.h" #include "envelopeEditor.h" extern gdMainWindow *G_MainWin; using namespace giada::m; geEnvelopeEditor::geEnvelopeEditor(int x, int y, gdActionEditor *pParent, int type, int range, const char *l) : geBaseActionEditor(x, y, 200, 80, pParent), l (l), type (type), range (range), selectedPoint (-1), draggedPoint (-1) { size(pParent->totalWidth, h()); } /* ------------------------------------------------------------------ */ geEnvelopeEditor::~geEnvelopeEditor() { clearPoints(); } /* ------------------------------------------------------------------ */ void geEnvelopeEditor::addPoint(int frame, int iValue, float fValue, int px, int py) { point p; p.frame = frame; p.iValue = iValue; p.fValue = fValue; p.x = px; p.y = py; points.push_back(p); } /* ------------------------------------------------------------------ */ void geEnvelopeEditor::updateActions() { for (unsigned i=0; izoom; } /* ------------------------------------------------------------------ */ void geEnvelopeEditor::draw() { baseDraw(); /* print label */ fl_color(G_COLOR_GREY_4); fl_font(FL_HELVETICA, 12); fl_draw(l, x()+4, y(), 80, h(), (Fl_Align) (FL_ALIGN_LEFT)); int pxOld = x()-3; int pyOld = y()+1; int pxNew = 0; int pyNew = 0; fl_color(G_COLOR_LIGHT_1); for (unsigned i=0; i 0) fl_line(pxOld+3, pyOld+3, pxNew+3, pyNew+3); pxOld = pxNew; pyOld = pyNew; } } /* ------------------------------------------------------------------ */ int geEnvelopeEditor::handle(int e) { /* Adding an action: no further checks required, just record it on frame * mx*pParent->zoom. Deleting action is trickier: find the active * point and derive from it the corresponding frame. */ int ret = 0; int mx = Fl::event_x()-x(); // mouse x int my = Fl::event_y()-y(); // mouse y switch (e) { case FL_ENTER: { ret = 1; break; } case FL_MOVE: { selectedPoint = getSelectedPoint(); redraw(); ret = 1; break; } case FL_LEAVE: { draggedPoint = -1; selectedPoint = -1; redraw(); ret = 1; break; } case FL_PUSH: { /* left click on point: drag * right click on point: delete * left click on void: add */ if (Fl::event_button1()) { if (selectedPoint != -1) { draggedPoint = selectedPoint; } else { /* top & border fix */ if (my > h()-8) my = h()-8; if (mx > pParent->coverX-x()) mx = pParent->coverX-x(); if (range == G_RANGE_FLOAT) { /* if this is the first point ever, add other two points at the beginning * and the end of the range */ if (points.size() == 0) { addPoint(0, 0, 1.0f, 0, 1); recorder::rec(pParent->chan->index, type, 0, 0, 1.0f); addPoint(clock::getTotalFrames(), 0, 1.0f, pParent->coverX, 1); recorder::rec(pParent->chan->index, type, clock::getTotalFrames(), 0, 1.0f); pParent->chan->hasActions = true; } /* line between 2 points y = (x-a) / (b-a); a = h() - 8; b = 1 */ int frame = mx * pParent->zoom; float value = (my - h() + 8) / (float) (1 - h() + 8); addPoint(frame, 0, value, mx, my); recorder::rec(pParent->chan->index, type, frame, 0, value); pParent->chan->hasActions = true; recorder::sortActions(); sortPoints(); } else { /// TODO } G_MainWin->keyboard->setChannelWithActions((geSampleChannel*)pParent->chan->guiChannel); // update mainWindow redraw(); } } else { /* right click on point 0 or point size-1 deletes the entire envelope */ if (selectedPoint != -1) { if (selectedPoint == 0 || (unsigned) selectedPoint == points.size()-1) { recorder::clearAction(pParent->chan->index, type); pParent->chan->hasActions = recorder::hasActions(pParent->chan->index); points.clear(); } else { recorder::deleteAction(pParent->chan->index, points.at(selectedPoint).frame, type, false, &mixer::mutex_recs); pParent->chan->hasActions = recorder::hasActions(pParent->chan->index); recorder::sortActions(); points.erase(points.begin() + selectedPoint); } G_MainWin->keyboard->setChannelWithActions((geSampleChannel*)pParent->chan->guiChannel); // update mainWindow redraw(); } } ret = 1; break; } case FL_RELEASE: { if (draggedPoint != -1) { if (points.at(draggedPoint).x == previousXPoint) { //gu_log("nothing to do\n"); } else { int newFrame = points.at(draggedPoint).x * pParent->zoom; /* x edge correction */ if (newFrame < 0) newFrame = 0; else if (newFrame > clock::getTotalFrames()) newFrame = clock::getTotalFrames(); /* vertical line check */ int vp = verticalPoint(points.at(draggedPoint)); if (vp == 1) newFrame -= 256; else if (vp == -1) newFrame += 256; /* delete previous point and record a new one */ recorder::deleteAction(pParent->chan->index, points.at(draggedPoint).frame, type, false, &mixer::mutex_recs); pParent->chan->hasActions = recorder::hasActions(pParent->chan->index); if (range == G_RANGE_FLOAT) { float value = (points.at(draggedPoint).y - h() + 8) / (float) (1 - h() + 8); recorder::rec(pParent->chan->index, type, newFrame, 0, value); pParent->chan->hasActions = true; } else { /// TODO } recorder::sortActions(); points.at(draggedPoint).frame = newFrame; draggedPoint = -1; selectedPoint = -1; } } ret = 1; break; } case FL_DRAG: { if (draggedPoint != -1) { /* y constraint */ if (my > h()-8) points.at(draggedPoint).y = h()-8; else if (my < 1) points.at(draggedPoint).y = 1; else points.at(draggedPoint).y = my; /* x constraint * constrain the point between two ends (leftBorder-point, point-point, * point-rightBorder). First & last points cannot be shifted on x */ if (draggedPoint == 0) points.at(draggedPoint).x = x()-8; else if ((unsigned) draggedPoint == points.size()-1) points.at(draggedPoint).x = pParent->coverX; else { int prevPoint = points.at(draggedPoint-1).x; int nextPoint = points.at(draggedPoint+1).x; if (mx <= prevPoint) points.at(draggedPoint).x = prevPoint; else if (mx >= nextPoint) points.at(draggedPoint).x = nextPoint; //else // points.at(draggedPoint).x = mx; else { if (pParent->gridTool->isOn()) points.at(draggedPoint).x = pParent->gridTool->getSnapPoint(mx)-1; else points.at(draggedPoint).x = mx; } } redraw(); } ret = 1; break; } } return ret; } /* ------------------------------------------------------------------ */ int geEnvelopeEditor::verticalPoint(const point &p) { for (unsigned i=0; i points.at(i).x) std::swap(points.at(j), points.at(i)); } /* ------------------------------------------------------------------ */ int geEnvelopeEditor::getSelectedPoint() { /* point is a 7x7 dot */ for (unsigned i=0; i= points.at(i).x+x()-4 && Fl::event_x() <= points.at(i).x+x()+4 && Fl::event_y() >= points.at(i).y+y() && Fl::event_y() <= points.at(i).y+y()+7) return i; } return -1; } /* ------------------------------------------------------------------ */ void geEnvelopeEditor::fill() { points.clear(); for (unsigned i=0; itype == type && a->chan == pParent->chan->index) { if (range == G_RANGE_FLOAT) addPoint( a->frame, // frame 0, // int value (unused) a->fValue, // float value a->frame / pParent->zoom, // x ((1-h()+8)*a->fValue)+h()-8); // y = (b-a)x + a (line between two points) // else: TODO } } } giada-0.14.5/src/gui/elems/actionEditor/envelopeEditor.h000066400000000000000000000056411322662744500231300ustar00rootroot00000000000000/* --------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * parent class of any envelope controller, from volume to VST parameter * automations. * * --------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * ------------------------------------------------------------------ */ #ifndef GE_ENVELOPE_EDITOR_H #define GE_ENVELOPE_EDITOR_H #include #include "baseActionEditor.h" class geEnvelopeEditor : public geBaseActionEditor { const char *l; // internal label int type; // type of action int range; /* point * a single dot in the graph. x = relative frame, y = relative value */ struct point { int frame; int iValue; float fValue; int x; int y; }; /* points * array of points, filled by fillPoints() */ std::vector points; /* selectedPoint * which point we are selecting? */ int selectedPoint; /* draggedPoint * which point we are dragging? */ int draggedPoint; /* previousXPoint * x coordinate of point at time t-1. Used to check effective shifts */ int previousXPoint; void draw(); int handle(int e); int getSelectedPoint(); void sortPoints(); /* verticalPoint * check if two points form a vertical line. In that case the frame value * would be the same and recorder would go crazy, so shift by a small value * of frames to create a minimal fadein/fadeout level. Return 0: no * vertical points; return 1: vertical with the next one, return -1: vertical * with the previous one. */ int verticalPoint(const point &p); public: geEnvelopeEditor(int x, int y, gdActionEditor *pParent, int type, int range, const char *l); ~geEnvelopeEditor(); /* addPoint * add a point made of frame+value to internal points[]. */ void addPoint(int frame, int iValue=0, float fValue=0.0f, int x=-1, int y=-1); void updateActions(); /* fill * parse recorder's stack and fill the widget with points. It's up to * the caller to call this method as initialization. */ void fill(); inline void clearPoints() { points.clear(); } }; #endif giada-0.14.5/src/gui/elems/actionEditor/gridTool.cpp000066400000000000000000000122341322662744500222560ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ------------------------------------------------------------------------------ * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * --------------------------------------------------------------------------- */ #include #include "../../../core/conf.h" #include "../../../core/const.h" #include "../../../core/clock.h" #include "../../dialogs/gd_actionEditor.h" #include "../basics/choice.h" #include "../basics/check.h" #include "actionEditor.h" #include "gridTool.h" using namespace giada::m; geGridTool::geGridTool(int x, int y, gdActionEditor *parent) : Fl_Group(x, y, 80, 20), parent(parent) { gridType = new geChoice(x, y, 40, 20); gridType->add("1"); gridType->add("2"); gridType->add("3"); gridType->add("4"); gridType->add("6"); gridType->add("8"); gridType->add("16"); gridType->add("32"); gridType->value(0); gridType->callback(cb_changeType, (void*)this); active = new geCheck (x+44, y+4, 12, 12); gridType->value(conf::actionEditorGridVal); active->value(conf::actionEditorGridOn); end(); } /* -------------------------------------------------------------------------- */ geGridTool::~geGridTool() { conf::actionEditorGridVal = gridType->value(); conf::actionEditorGridOn = active->value(); } /* -------------------------------------------------------------------------- */ void geGridTool::cb_changeType(Fl_Widget *w, void *p) { ((geGridTool*)p)->__cb_changeType(); } /* -------------------------------------------------------------------------- */ void geGridTool::__cb_changeType() { calc(); parent->redraw(); } /* -------------------------------------------------------------------------- */ bool geGridTool::isOn() { return active->value(); } /* -------------------------------------------------------------------------- */ int geGridTool::getValue() { switch (gridType->value()) { case 0: return 1; case 1: return 2; case 2: return 3; case 3: return 4; case 4: return 6; case 5: return 8; case 6: return 16; case 7: return 32; } return 0; } /* -------------------------------------------------------------------------- */ void geGridTool::calc() { points.clear(); frames.clear(); bars.clear(); beats.clear(); /* find beats, bars and grid. The method is the same of the waveform in sample * editor. Take totalwidth (the width in pixel of the area to draw), knowing * that totalWidth = totalFrames / zoom. Then, for each pixel of totalwidth, * put a concentrate of each block (which is totalFrames / zoom) */ int j = 0; int fpgc = floor(clock::getFramesPerBeat() / getValue()); // frames per grid cell for (int i=1; itotalWidth; i++) { // if i=0, step=0 -> useless cycle int step = parent->zoom*i; while (j < step && j < clock::getTotalFrames()) { if (j % fpgc == 0) { points.push_back(i); frames.push_back(j); } if (j % clock::getFramesPerBeat() == 0) beats.push_back(i); if (j % clock::getFramesPerBar() == 0 && i != 1) bars.push_back(i); if (j == clock::getTotalFrames() - 1) parent->coverX = i; j++; } j = step; } /* fix coverX if == 0, which means G_Mixer.beats == G_MAX_BEATS */ if (clock::getBeats() == G_MAX_BEATS) parent->coverX = parent->totalWidth; } /* -------------------------------------------------------------------------- */ int geGridTool::getSnapPoint(int v) { if (v == 0) return 0; for (int i=0; i<(int)points.size(); i++) { if (i == (int) points.size()-1) return points.at(i); int gp = points.at(i); int gpn = points.at(i+1); if (v >= gp && v < gpn) return gp; } return v; // default value } /* -------------------------------------------------------------------------- */ int geGridTool::getSnapFrame(int v) { v *= parent->zoom; // transformation pixel -> frame for (int i=0; i<(int)frames.size(); i++) { if (i == (int) frames.size()-1) return frames.at(i); int gf = frames.at(i); // grid frame int gfn = frames.at(i+1); // grid frame next if (v >= gf && v < gfn) { /* which one is the closest? gf < v < gfn */ if ((gfn - v) < (v - gf)) return gfn; else return gf; } } return v; // default value } /* -------------------------------------------------------------------------- */ int geGridTool::getCellSize() { return (parent->coverX - parent->ac->x()) / clock::getBeats() / getValue(); } giada-0.14.5/src/gui/elems/actionEditor/gridTool.h000066400000000000000000000040241322662744500217210ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef GE_GRID_TOOL_H #define GE_GRID_TOOL_H #include #include class geChoice; class geCheck; class gdActionEditor; class geGridTool : public Fl_Group { private: geChoice *gridType; geCheck *active; gdActionEditor *parent; static void cb_changeType(Fl_Widget *w, void *p); inline void __cb_changeType(); public: geGridTool(int x, int y, gdActionEditor *parent); ~geGridTool(); int getValue(); bool isOn(); void calc(); /* getSnapPoint * given a cursor position in input, return the x coordinates of the * nearest snap point (in pixel, clean, ie. not x()-shifted) */ int getSnapPoint(int v); int getSnapFrame(int v); /* getCellSize * return the size in pixel of a single cell of the grid. */ int getCellSize(); std::vector points; // points of the grid std::vector frames; // frames of the grid std::vector bars; std::vector beats; }; #endif giada-0.14.5/src/gui/elems/actionEditor/muteEditor.cpp000066400000000000000000000245041322662744500226170ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include #include "../../../core/recorder.h" #include "../../../core/mixer.h" #include "../../../core/channel.h" #include "../../../core/clock.h" #include "../../../glue/main.h" #include "../../../utils/log.h" #include "../../dialogs/gd_actionEditor.h" #include "../../dialogs/gd_mainWindow.h" #include "../mainWindow/keyboard/keyboard.h" #include "gridTool.h" #include "muteEditor.h" extern gdMainWindow *G_MainWin; using namespace giada::m; geMuteEditor::geMuteEditor(int x, int y, gdActionEditor *pParent) : geBaseActionEditor(x, y, 200, 80, pParent), draggedPoint (-1), selectedPoint (-1) { size(pParent->totalWidth, h()); extractPoints(); } /* ------------------------------------------------------------------ */ void geMuteEditor::draw() { baseDraw(); /* print label */ fl_color(G_COLOR_GREY_4); fl_font(FL_HELVETICA, 12); fl_draw("mute", x()+4, y(), w(), h(), (Fl_Align) (FL_ALIGN_LEFT | FL_ALIGN_CENTER)); /* draw "on" and "off" labels. Must stay in background */ fl_color(G_COLOR_GREY_4); fl_font(FL_HELVETICA, 9); fl_draw("on", x()+4, y(), w(), h(), (Fl_Align) (FL_ALIGN_LEFT | FL_ALIGN_TOP)); fl_draw("off", x()+4, y()+h()-14, w(), h(), (Fl_Align) (FL_ALIGN_LEFT | FL_ALIGN_TOP)); /* draw on-off points. On = higher rect, off = lower rect. It always * starts with a note_off */ fl_color(G_COLOR_LIGHT_1); int pxOld = x()+1; int pxNew = 0; int py = y()+h()-5; int pyDot = py-6; for (unsigned i=0; icoverX+x()-1, py); } /* ------------------------------------------------------------------ */ void geMuteEditor::extractPoints() { points.clear(); /* actions are already sorted by recorder::sortActions() */ for (unsigned i=0; ichan == pParent->chan->index) { if (recorder::global.at(i).at(j)->type & (G_ACTION_MUTEON | G_ACTION_MUTEOFF)) { point p; p.frame = recorder::frames.at(i); p.type = recorder::global.at(i).at(j)->type; p.x = p.frame / pParent->zoom; points.push_back(p); //gu_log("[geMuteEditor::extractPoints] point found, type=%d, frame=%d\n", p.type, p.frame); } } } } } /* ------------------------------------------------------------------ */ void geMuteEditor::updateActions() { for (unsigned i=0; izoom; } /* ------------------------------------------------------------------ */ int geMuteEditor::handle(int e) { int ret = 0; int mouseX = Fl::event_x()-x(); switch (e) { case FL_ENTER: { ret = 1; break; } case FL_MOVE: { selectedPoint = getSelectedPoint(); redraw(); ret = 1; break; } case FL_LEAVE: { draggedPoint = -1; selectedPoint = -1; redraw(); ret = 1; break; } case FL_PUSH: { /* left click on point: drag * right click on point: delete * left click on void: add */ if (Fl::event_button1()) { if (selectedPoint != -1) { draggedPoint = selectedPoint; previousXPoint = points.at(selectedPoint).x; } else { /* click on the grey area leads to nowhere */ if (mouseX > pParent->coverX) { ret = 1; break; } /* click in the middle of a long mute_on (between two points): new actions * must be added in reverse: first mute_off then mute_on. Let's find the * next point from here. */ unsigned nextPoint = points.size(); for (unsigned i=0; izoom; int frame_b = frame_a+2048; if (pParent->gridTool->isOn()) { frame_a = pParent->gridTool->getSnapFrame(mouseX); frame_b = pParent->gridTool->getSnapFrame(mouseX + pParent->gridTool->getCellSize()); /* with snap=on a point can fall onto another */ if (pointCollides(frame_a) || pointCollides(frame_b)) { ret = 1; break; } } /* ensure frame parity */ if (frame_a % 2 != 0) frame_a++; if (frame_b % 2 != 0) frame_b++; /* avoid overflow: frame_b must be within the sequencer range. In that * case shift the ON-OFF block */ if (frame_b >= clock::getTotalFrames()) { frame_b = clock::getTotalFrames(); frame_a = frame_b-2048; } if (nextPoint % 2 != 0) { recorder::rec(pParent->chan->index, G_ACTION_MUTEOFF, frame_a); recorder::rec(pParent->chan->index, G_ACTION_MUTEON, frame_b); } else { recorder::rec(pParent->chan->index, G_ACTION_MUTEON, frame_a); recorder::rec(pParent->chan->index, G_ACTION_MUTEOFF, frame_b); } pParent->chan->hasActions = true; recorder::sortActions(); G_MainWin->keyboard->setChannelWithActions((geSampleChannel*)pParent->chan->guiChannel); // update mainWindow extractPoints(); redraw(); } } else { /* delete points pair */ if (selectedPoint != -1) { unsigned a; unsigned b; if (points.at(selectedPoint).type == G_ACTION_MUTEOFF) { a = selectedPoint-1; b = selectedPoint; } else { a = selectedPoint; b = selectedPoint+1; } //gu_log("selected: a=%d, b=%d >>> frame_a=%d, frame_b=%d\n", // a, b, points.at(a).frame, points.at(b).frame); recorder::deleteAction(pParent->chan->index, points.at(a).frame, points.at(a).type, false, &mixer::mutex_recs); // false = don't check vals recorder::deleteAction(pParent->chan->index, points.at(b).frame, points.at(b).type, false, &mixer::mutex_recs); // false = don't check vals pParent->chan->hasActions = recorder::hasActions(pParent->chan->index); recorder::sortActions(); G_MainWin->keyboard->setChannelWithActions((geSampleChannel*)pParent->chan->guiChannel); // update mainWindow extractPoints(); redraw(); } } ret = 1; break; } case FL_RELEASE: { if (draggedPoint != -1) { if (points.at(draggedPoint).x == previousXPoint) { //gu_log("nothing to do\n"); } else { int newFrame = points.at(draggedPoint).x * pParent->zoom; recorder::deleteAction(pParent->chan->index, points.at(draggedPoint).frame, points.at(draggedPoint).type, false, &mixer::mutex_recs); // don't check values pParent->chan->hasActions = recorder::hasActions(pParent->chan->index); recorder::rec( pParent->chan->index, points.at(draggedPoint).type, newFrame); pParent->chan->hasActions = true; recorder::sortActions(); points.at(draggedPoint).frame = newFrame; } } draggedPoint = -1; selectedPoint = -1; ret = 1; break; } case FL_DRAG: { if (draggedPoint != -1) { /* constrain the point between two ends (leftBorder-point, * point-point, point-rightBorder) */ int prevPoint; int nextPoint; if (draggedPoint == 0) { prevPoint = 0; nextPoint = points.at(draggedPoint+1).x - 1; if (pParent->gridTool->isOn()) nextPoint -= pParent->gridTool->getCellSize(); } else if ((unsigned) draggedPoint == points.size()-1) { prevPoint = points.at(draggedPoint-1).x + 1; nextPoint = pParent->coverX-x(); if (pParent->gridTool->isOn()) prevPoint += pParent->gridTool->getCellSize(); } else { prevPoint = points.at(draggedPoint-1).x + 1; nextPoint = points.at(draggedPoint+1).x - 1; if (pParent->gridTool->isOn()) { prevPoint += pParent->gridTool->getCellSize(); nextPoint -= pParent->gridTool->getCellSize(); } } if (mouseX <= prevPoint) points.at(draggedPoint).x = prevPoint; else if (mouseX >= nextPoint) points.at(draggedPoint).x = nextPoint; else if (pParent->gridTool->isOn()) points.at(draggedPoint).x = pParent->gridTool->getSnapPoint(mouseX)-1; else points.at(draggedPoint).x = mouseX; redraw(); } ret = 1; break; } } return ret; } /* ------------------------------------------------------------------ */ bool geMuteEditor::pointCollides(int frame) { for (unsigned i=0; i= points.at(i).x+x()-3 && Fl::event_x() <= points.at(i).x+x()+3) return i; } return -1; } giada-0.14.5/src/gui/elems/actionEditor/muteEditor.h000066400000000000000000000047251322662744500222670ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef GE_MUTE_TOOL_H #define GE_MUTE_TOOL_H #include #include "baseActionEditor.h" class gdActionEditor; class geMuteEditor : public geBaseActionEditor { private: /* point * a single dot in the graph. */ struct point { int frame; char type; int x; }; /* points * array of on/off points, in frames */ std::vector points; /* draggedPoint * which point we are dragging? */ int draggedPoint; /* selectedPoint * which point we are selecting? */ int selectedPoint; /* previousXPoint * x coordinate of point at time t-1. Used to check effective shifts */ int previousXPoint; /* extractPoints * va a leggere l'array di azioni di Recorder ed estrae tutti i punti * interessanti mute_on o mute_off. Li mette poi nel vector points. */ void extractPoints(); /* getSelectedPoint * ritorna l'indice di points[] in base al punto selezionato (quello * con il mouse hover). Ritorna -1 se non trova niente. */ int getSelectedPoint(); /* pointCollides * true if a point collides with another. Used while adding new points * with snap active.*/ bool pointCollides(int frame); public: geMuteEditor(int x, int y, gdActionEditor *pParent); void draw(); int handle(int e); /* updateActions * calculates new points affected by the zoom. Call this one after * each zoom update. */ void updateActions(); }; #endif giada-0.14.5/src/gui/elems/actionEditor/noteEditor.cpp000066400000000000000000000046171322662744500226150ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include #include "../../../core/const.h" #include "../../../core/conf.h" #include "../../../utils/log.h" #include "../../dialogs/gd_actionEditor.h" #include "pianoItem.h" #include "pianoRoll.h" #include "noteEditor.h" using namespace giada::m; geNoteEditor::geNoteEditor(int x, int y, gdActionEditor *pParent) : Fl_Scroll(x, y, 200, 422), pParent (pParent) { size(pParent->totalWidth, conf::pianoRollH); pianoRoll = new gePianoRoll(x, y, pParent->totalWidth, pParent); } /* -------------------------------------------------------------------------- */ geNoteEditor::~geNoteEditor() { clear(); conf::pianoRollH = h(); conf::pianoRollY = pianoRoll->y(); } /* -------------------------------------------------------------------------- */ void geNoteEditor::updateActions() { pianoRoll->updateActions(); } /* -------------------------------------------------------------------------- */ void geNoteEditor::draw() { pianoRoll->size(this->w(), pianoRoll->h()); /// <--- not optimal /* clear background */ fl_rectf(x(), y(), w(), h(), G_COLOR_GREY_1); /* clip pianoRoll to pianoRollContainer size */ fl_push_clip(x(), y(), w(), h()); draw_child(*pianoRoll); fl_pop_clip(); fl_color(G_COLOR_GREY_4); fl_line_style(0); fl_rect(x(), y(), pParent->totalWidth, h()); } giada-0.14.5/src/gui/elems/actionEditor/noteEditor.h000066400000000000000000000027161322662744500222600ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef GE_NOTE_EDITOR_H #define GE_NOTE_EDITOR_H #include class gdActionEditor; class gePianoRoll; class geNoteEditor : public Fl_Scroll { private: gdActionEditor *pParent; gePianoRoll *pianoRoll; public: geNoteEditor(int x, int y, gdActionEditor *parent); ~geNoteEditor(); void draw(); void updateActions(); }; #endif giada-0.14.5/src/gui/elems/actionEditor/pianoItem.cpp000066400000000000000000000156131322662744500224240ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include "../../../core/kernelMidi.h" #include "../../../core/const.h" #include "../../../core/mixer.h" #include "../../../core/channel.h" #include "../../../core/clock.h" #include "../../../core/midiChannel.h" #include "../../dialogs/gd_actionEditor.h" #include "noteEditor.h" #include "pianoRoll.h" #include "gridTool.h" #include "pianoItem.h" using namespace giada::m; gePianoItem::gePianoItem(int X, int Y, int rel_x, int rel_y, recorder::action a, recorder::action b, gdActionEditor* pParent) : geBasePianoItem(X, Y, MIN_WIDTH, pParent), a (a), b (b), changed (false) { int newX = rel_x + (a.frame / pParent->zoom); int newY = rel_y + getY(kernelMidi::getB2(a.iValue)); int newW = (b.frame - a.frame) / pParent->zoom; resize(newX, newY, newW, h()); } /* -------------------------------------------------------------------------- */ void gePianoItem::reposition(int pianoRollX) { int newX = pianoRollX + (a.frame / pParent->zoom); int newW = ((b.frame - a.frame) / pParent->zoom); if (newW < MIN_WIDTH) newW = MIN_WIDTH; resize(newX, y(), newW, h()); redraw(); } /* -------------------------------------------------------------------------- */ bool gePianoItem::overlap() { /* when 2 segments overlap? * start = the highest value between the two starting points * end = the lowest value between the two ending points * if start < end then there's an overlap of end-start pixels. */ geNoteEditor* noteEditor = static_cast(parent()); for (int i=0; ichildren(); i++) { gePianoItem* pItem = static_cast(noteEditor->child(i)); /* don't check against itself and with different y positions */ if (pItem == this || pItem->y() != y()) continue; int start = pItem->x() >= x() ? pItem->x() : x(); int end = pItem->x()+pItem->w() < x()+w() ? pItem->x()+pItem->w() : x()+w(); if (start < end) return true; } return false; } /* -------------------------------------------------------------------------- */ void gePianoItem::draw() { int _w = w() > MIN_WIDTH ? w() : MIN_WIDTH; fl_rectf(x(), y()+2, _w, h()-3, (Fl_Color) selected ? G_COLOR_LIGHT_1 : G_COLOR_LIGHT_1); } /* -------------------------------------------------------------------------- */ void gePianoItem::removeAction() { MidiChannel* ch = static_cast(pParent->chan); recorder::deleteAction(ch->index, a.frame, G_ACTION_MIDI, true, &mixer::mutex_recs, a.iValue, 0.0); recorder::deleteAction(ch->index, b.frame, G_ACTION_MIDI, true, &mixer::mutex_recs, b.iValue, 0.0); /* Send a note-off in case we are deleting it in a middle of a key_on/key_off sequence. */ ch->sendMidi(b.iValue); ch->hasActions = recorder::hasActions(ch->index); } /* -------------------------------------------------------------------------- */ int gePianoItem::handle(int e) { int ret = 0; switch (e) { case FL_ENTER: { selected = true; ret = 1; redraw(); break; } case FL_LEAVE: { fl_cursor(FL_CURSOR_DEFAULT, FL_WHITE, FL_BLACK); selected = false; ret = 1; redraw(); break; } case FL_MOVE: { onLeftEdge = false; onRightEdge = false; if (Fl::event_x() >= x() && Fl::event_x() < x()+HANDLE_WIDTH) { onLeftEdge = true; fl_cursor(FL_CURSOR_WE, FL_WHITE, FL_BLACK); } else if (Fl::event_x() >= x()+w()-HANDLE_WIDTH && Fl::event_x() <= x()+w()) { onRightEdge = true; fl_cursor(FL_CURSOR_WE, FL_WHITE, FL_BLACK); } else fl_cursor(FL_CURSOR_DEFAULT, FL_WHITE, FL_BLACK); ret = 1; break; } case FL_PUSH: { push_x = Fl::event_x() - x(); old_x = x(); old_w = w(); if (Fl::event_button3()) { fl_cursor(FL_CURSOR_DEFAULT, FL_WHITE, FL_BLACK); removeAction(); hide(); // for Windows Fl::delete_widget(this); static_cast(parent())->redraw(); } ret = 1; break; } case FL_DRAG: { changed = true; geNoteEditor *pr = static_cast(parent()); int coverX = pParent->coverX + pr->x(); // relative coverX int nx, ny, nw; if (onLeftEdge) { nx = Fl::event_x(); ny = y(); nw = x()-Fl::event_x()+w(); if (nx < pr->x()) { nx = pr->x(); nw = w()+x()-pr->x(); } else if (nx > x()+w()-MIN_WIDTH) { nx = x()+w()-MIN_WIDTH; nw = MIN_WIDTH; } resize(nx, ny, nw, h()); } else if (onRightEdge) { nw = Fl::event_x()-x(); if (Fl::event_x() < x()+MIN_WIDTH) nw = MIN_WIDTH; else if (Fl::event_x() > coverX) nw = coverX-x(); size(nw, h()); } else { nx = Fl::event_x() - push_x; if (nx < pr->x()+1) nx = pr->x()+1; else if (nx+w() > coverX) nx = coverX-w(); /* snapping */ if (pParent->gridTool->isOn()) nx = pParent->gridTool->getSnapPoint(nx-pr->x()) + pr->x() - 1; position(nx, y()); } /* update screen */ redraw(); static_cast(parent())->redraw(); ret = 1; break; } case FL_RELEASE: { gePianoRoll* pianoRoll = static_cast(parent()); /* Delete and record the action, only if it doesn't overlap with another existing one. */ if (overlap()) { resize(old_x, y(), old_w, h()); redraw(); } else if (changed) { removeAction(); int note = pianoRoll->yToNote(getRelY()); int frame_a = getRelX() * pParent->zoom; int frame_b = (getRelX()+w()) * pParent->zoom; pianoRoll->recordAction(note, frame_a, frame_b); changed = false; } pianoRoll->redraw(); ret = 1; break; } } return ret; } /* -------------------------------------------------------------------------- */ int gePianoItem::getRelY() { return y() - parent()->y(); } /* -------------------------------------------------------------------------- */ int gePianoItem::getRelX() { return x() - parent()->x(); } giada-0.14.5/src/gui/elems/actionEditor/pianoItem.h000066400000000000000000000045061322662744500220700ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef GE_PIANO_ITEM_H #define GE_PIANO_ITEM_H #include "../../../core/recorder.h" #include "../../../core/midiEvent.h" #include "basePianoItem.h" class gdActionEditor; class gePianoItem : public geBasePianoItem { private: struct giada::m::recorder::action a; struct giada::m::recorder::action b; int push_x; /* changed If Item has been moved or resized: re-recording needed. */ bool changed; /* onLeft, RightEdge If cursor is on a widget's edge. */ bool onLeftEdge; bool onRightEdge; /* old_x, old_w Store previous width and position while dragging and moving, in order to restore it if overlap. */ int old_x, old_w; /* getRelX/Y Returns x/y point of this item, relative to piano roll (and not to entire screen). */ int getRelY(); int getRelX(); /* overlap Checks if this item don't overlap with another one. */ bool overlap(); public: static const int MIN_WIDTH = 10; static const int HANDLE_WIDTH = 5; gePianoItem(int x, int y, int rel_x, int rel_y, struct giada::m::recorder::action a, struct giada::m::recorder::action b, gdActionEditor* pParent); void draw() override; int handle(int e) override; void reposition(int pianoRollX) override; void removeAction(); }; #endif giada-0.14.5/src/gui/elems/actionEditor/pianoItemOrphaned.cpp000066400000000000000000000056711322662744500241100ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include "../../../core/const.h" #include "../../../core/kernelMidi.h" #include "../../../core/recorder.h" #include "../../../core/channel.h" #include "../../../core/midiChannel.h" #include "../../../core/mixer.h" #include "../../dialogs/gd_actionEditor.h" #include "pianoRoll.h" #include "noteEditor.h" #include "pianoItemOrphaned.h" using namespace giada::m; gePianoItemOrphaned::gePianoItemOrphaned(int x, int y, int xRel, int yRel, recorder::action action, gdActionEditor* pParent) : geBasePianoItem(x, y, WIDTH, pParent) { note = kernelMidi::getB2(action.iValue); frame = action.frame; event = action.iValue; int newX = xRel + (frame / pParent->zoom); int newY = yRel + getY(note); resize(newX, newY, w(), h()); } /* -------------------------------------------------------------------------- */ void gePianoItemOrphaned::reposition(int pianoRollX) { int newX = pianoRollX + (frame / pParent->zoom); resize(newX, y(), WIDTH, h()); redraw(); } /* -------------------------------------------------------------------------- */ int gePianoItemOrphaned::handle(int e) { int ret = geBasePianoItem::handle(e); if (e == FL_PUSH) { remove(); ret = 1; } return ret; } /* -------------------------------------------------------------------------- */ void gePianoItemOrphaned::remove() { MidiChannel *ch = static_cast(pParent->chan); recorder::deleteAction(ch->index, frame, G_ACTION_MIDI, true, &mixer::mutex_recs, event, 0.0); hide(); // for Windows Fl::delete_widget(this); ch->hasActions = recorder::hasActions(ch->index); static_cast(parent())->redraw(); } /* -------------------------------------------------------------------------- */ void gePianoItemOrphaned::draw() { fl_rect(x(), y()+2, WIDTH, h()-3, (Fl_Color) selected ? G_COLOR_LIGHT_1 : G_COLOR_LIGHT_1); } giada-0.14.5/src/gui/elems/actionEditor/pianoItemOrphaned.h000066400000000000000000000032241322662744500235450ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef GE_PIANO_ITEM_ORPHANED_H #define GE_PIANO_ITEM_ORPHANED_H #include "../../../core/recorder.h" #include "basePianoItem.h" class gdActionEditor; class gePianoItemOrphaned : public geBasePianoItem { private: int note; int frame; int event; public: static const int WIDTH = 12; gePianoItemOrphaned(int x, int y, int xRel, int yRel, struct giada::m::recorder::action action, gdActionEditor* pParent); void draw() override; int handle(int e) override; void reposition(int pianoRollX) override; void remove(); }; #endif giada-0.14.5/src/gui/elems/actionEditor/pianoRoll.cpp000066400000000000000000000176621322662744500224440ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include "../../../core/conf.h" #include "../../../core/const.h" #include "../../../core/mixer.h" #include "../../../core/clock.h" #include "../../../core/channel.h" #include "../../../core/recorder.h" #include "../../../core/kernelMidi.h" #include "../../../utils/log.h" #include "../../../utils/string.h" #include "../../../glue/recorder.h" #include "../../dialogs/gd_actionEditor.h" #include "pianoItem.h" #include "pianoItemOrphaned.h" #include "noteEditor.h" #include "pianoRoll.h" using std::string; using std::vector; using namespace giada; gePianoRoll::gePianoRoll(int X, int Y, int W, gdActionEditor* pParent) : geBaseActionEditor(X, Y, W, 40, pParent) { resizable(nullptr); // don't resize children (i.e. pianoItem) size(W, (MAX_KEYS+1) * CELL_H); // 128 MIDI channels * CELL_H height if (m::conf::pianoRollY == -1) position(x(), y()-(h()/2)); // center else position(x(), m::conf::pianoRollY); drawSurface1(); drawSurface2(); build(); } /* -------------------------------------------------------------------------- */ void gePianoRoll::build() { using namespace m::recorder; clear(); int channel = pParent->chan->index; int maxFrame = m::clock::getTotalFrames(); vector actions = c::recorder::getMidiActions(channel, maxFrame); for (Composite composite : actions) { m::MidiEvent e1 = composite.a1.iValue; m::MidiEvent e2 = composite.a2.iValue; gu_log("[gePianoRoll] ((0x%X, 0x%X, f=%d) - (0x%X, 0x%X, f=%d))\n", e1.getStatus(), e1.getNote(), composite.a1.frame, e2.getStatus(), e2.getNote(), composite.a2.frame ); if (composite.a2.frame != -1) add(new gePianoItem(0, 0, x(), y(), composite.a1, composite.a2, pParent)); else add(new gePianoItemOrphaned(0, 0, x(), y(), composite.a1, pParent)); } redraw(); } /* -------------------------------------------------------------------------- */ void gePianoRoll::drawSurface1() { surface1 = fl_create_offscreen(CELL_W, h()); fl_begin_offscreen(surface1); /* warning: only w() and h() come from this widget, x and y coordinates * are absolute, since we are writing in a memory chunk */ fl_rectf(0, 0, CELL_W, h(), G_COLOR_GREY_1); fl_line_style(FL_DASH, 0, nullptr); fl_font(FL_HELVETICA, G_GUI_FONT_SIZE_BASE); int octave = MAX_OCTAVES; for (int i=1; i<=MAX_KEYS+1; i++) { /* print key note label. C C# D D# E F F# G G# A A# B */ string note = gu_iToString(octave); switch (i % KEYS) { case (int) Notes::G: fl_rectf(0, i*CELL_H, CELL_W, CELL_H, G_COLOR_GREY_2); note += " G"; break; case (int) Notes::FS: note += " F#"; break; case (int) Notes::F: note += " F"; break; case (int) Notes::E: fl_rectf(0, i*CELL_H, CELL_W, CELL_H, G_COLOR_GREY_2); note += " E"; break; case (int) Notes::DS: note += " D#"; break; case (int) Notes::D: fl_rectf(0, i*CELL_H, CELL_W, CELL_H, G_COLOR_GREY_2); note += " D"; break; case (int) Notes::CS: note += " C#"; break; case (int) Notes::C: note += " C"; octave--; break; case (int) Notes::B: fl_rectf(0, i*CELL_H, CELL_W, CELL_H, G_COLOR_GREY_2); note += " B"; break; case (int) Notes::AS: note += " A#"; break; case (int) Notes::A: fl_rectf(0, i*CELL_H, CELL_W, CELL_H, G_COLOR_GREY_2); note += " A"; break; case (int) Notes::GS: note += " G#"; break; } /* Print note name */ fl_color(G_COLOR_GREY_3); fl_draw(note.c_str(), 4, ((i-1)*CELL_H)+1, CELL_W, CELL_H, (Fl_Align) (FL_ALIGN_LEFT | FL_ALIGN_CENTER)); /* Print horizontal line */ if (i < MAX_KEYS+1) fl_line(0, i*CELL_H, CELL_W, +i*CELL_H); } fl_line_style(0); fl_end_offscreen(); } /* -------------------------------------------------------------------------- */ void gePianoRoll::drawSurface2() { surface2 = fl_create_offscreen(CELL_W, h()); fl_begin_offscreen(surface2); fl_rectf(0, 0, CELL_W, h(), G_COLOR_GREY_1); fl_color(G_COLOR_GREY_3); fl_line_style(FL_DASH, 0, nullptr); for (int i=1; i<=MAX_KEYS+1; i++) { switch (i % KEYS) { case (int) Notes::G: case (int) Notes::E: case (int) Notes::D: case (int) Notes::B: case (int) Notes::A: fl_rectf(0, i*CELL_H, CELL_W, CELL_H, G_COLOR_GREY_2); break; } if (i < MAX_KEYS+1) { fl_color(G_COLOR_GREY_3); fl_line(0, i*CELL_H, CELL_W, i*CELL_H); } } fl_line_style(0); fl_end_offscreen(); } /* -------------------------------------------------------------------------- */ void gePianoRoll::draw() { fl_copy_offscreen(x(), y(), CELL_W, h(), surface1, 0, 0); #if defined(__APPLE__) for (int i=36; itotalWidth; i+=36) /// TODO: i < pParent->coverX is faster fl_copy_offscreen(x()+i, y(), CELL_W, h(), surface2, 1, 0); #else for (int i=CELL_W; itotalWidth; i+=CELL_W) /// TODO: i < pParent->coverX is faster fl_copy_offscreen(x()+i, y(), CELL_W, h(), surface2, 0, 0); #endif baseDraw(false); draw_children(); } /* -------------------------------------------------------------------------- */ int gePianoRoll::handle(int e) { int ret = Fl_Group::handle(e); switch (e) { case FL_PUSH: { /* avoid click on grey area */ if (Fl::event_x() >= pParent->coverX) { ret = 1; break; } push_y = Fl::event_y() - y(); if (Fl::event_button1()) { /* ax is driven by grid, ay by the height in px of each note. */ int ax = Fl::event_x(); int ay = Fl::event_y(); /* Vertical snap. */ int edge = (ay-y()) % CELL_H; if (edge != 0) ay -= edge; /* If there are no pianoItems below the mouse, add a new one. */ gePianoItem* pianoItem = dynamic_cast(Fl::belowmouse()); if (pianoItem == nullptr) recordAction(yToNote(ay-y()), (ax-x()) * pParent->zoom); } ret = 1; break; } case FL_DRAG: { if (Fl::event_button3()) { geNoteEditor* prc = static_cast(parent()); position(x(), Fl::event_y() - push_y); if (y() > prc->y()) position(x(), prc->y()); else if (y() < prc->y()+prc->h()-h()) position(x(), prc->y()+prc->h()-h()); prc->redraw(); } ret = 1; break; } case FL_MOUSEWHEEL: { // nothing to do, just avoid small internal scroll ret = 1; break; } } return ret; } /* -------------------------------------------------------------------------- */ void gePianoRoll::recordAction(int note, int frame_a, int frame_b) { c::recorder::recordMidiAction(pParent->chan->index, note, frame_a, frame_b); pParent->chan->hasActions = true; build(); } /* -------------------------------------------------------------------------- */ int gePianoRoll::yToNote(int y) { return gePianoRoll::MAX_KEYS - (y / gePianoRoll::CELL_H); } /* -------------------------------------------------------------------------- */ void gePianoRoll::updateActions() { for (int k=0; k(child(k))->reposition(x()); } giada-0.14.5/src/gui/elems/actionEditor/pianoRoll.h000066400000000000000000000047511322662744500221040ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef GE_PIANO_ROLL_H #define GE_PIANO_ROLL_H #include #include "baseActionEditor.h" class gdActionEditor; class gePianoRoll : public geBaseActionEditor { private: enum class Notes { G = 1, FS = 2, F = 3, E = 4, DS = 5, D = 6, CS = 7, C = 8, B = 9, AS = 10, A = 11, GS = 0 }; int push_y; Fl_Offscreen surface1; // notes, no repeat Fl_Offscreen surface2; // lines, x-repeat /* drawSurface* Generates a complex drawing in memory first and copy it to the screen at a later point in time. Fl_Offscreen surface holds the necessary data. The first call creates an offscreen surface of CELL_W pixel wide containing note values. The second call creates another offscreen surface of CELL_W pixels wide containing the rest of the piano roll. The latter will then be tiled during the ::draw() call. */ void drawSurface1(); void drawSurface2(); void build(); public: static const int MAX_KEYS = 127; static const int MAX_OCTAVES = 9; static const int KEYS = 12; static const int CELL_H = 18; static const int CELL_W = 40; gePianoRoll(int x, int y, int w, gdActionEditor* pParent); void draw() override; int handle(int e) override; /* updateActions Repositions existing actions after a zoom gesture. */ void updateActions() override; void recordAction(int note, int frame_a, int frame_b=0); int yToNote(int y); }; #endif giada-0.14.5/src/gui/elems/basics/000077500000000000000000000000001322662744500166055ustar00rootroot00000000000000giada-0.14.5/src/gui/elems/basics/baseButton.cpp000066400000000000000000000042731322662744500214250ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * geBaseButton * Base class for every button widget. * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include #include "baseButton.h" geBaseButton::geBaseButton(int x, int y, int w, int h, const char *l) : Fl_Button(x, y, w, h, l) { initLabel = l ? l : ""; } /* -------------------------------------------------------------------------- */ void geBaseButton::trimLabel() { if (initLabel.empty()) return; std::string out; if (w() > 20) { out = initLabel; int len = initLabel.size(); while (fl_width(out.c_str(), out.size()) > w()) { out = initLabel.substr(0, len) + "..."; len--; } } else { out = ""; } copy_label(out.c_str()); } /* -------------------------------------------------------------------------- */ void geBaseButton::label(const char *l) { Fl_Button::label(l); initLabel = l; trimLabel(); } const char *geBaseButton::label() { return Fl_Button::label(); } /* -------------------------------------------------------------------------- */ void geBaseButton::resize(int X, int Y, int W, int H) { trimLabel(); Fl_Button::resize(X, Y, W, H); } giada-0.14.5/src/gui/elems/basics/baseButton.h000066400000000000000000000030361322662744500210660ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * geBaseButton * Base class for every button widget. * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef GE_BASE_BUTTON_H #define GE_BASE_BUTTON_H #include #include class geBaseButton : public Fl_Button { private: std::string initLabel; void trimLabel(); public: geBaseButton(int x, int y, int w, int h, const char *l=0); void resize(int x, int y, int w, int h) override; void label(const char *l); const char *label(); }; #endif giada-0.14.5/src/gui/elems/basics/box.cpp000066400000000000000000000026201322662744500201010ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include "../../../core/const.h" #include "box.h" geBox::geBox(int x, int y, int w, int h, const char *l, Fl_Align al) : Fl_Box(x, y, w, h) { copy_label(l); labelsize(G_GUI_FONT_SIZE_BASE); box(FL_NO_BOX); labelcolor(G_COLOR_LIGHT_2); if (al != 0) align(al | FL_ALIGN_INSIDE); } giada-0.14.5/src/gui/elems/basics/box.h000066400000000000000000000024541322662744500175530ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef GE_BOX_H #define GE_BOX_H #include class geBox : public Fl_Box { public: geBox(int x, int y, int w, int h, const char *l=0, Fl_Align al=FL_ALIGN_CENTER); }; #endif giada-0.14.5/src/gui/elems/basics/boxtypes.cpp000066400000000000000000000032531322662744500211710ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * boxtypes * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include #include "../../../core/const.h" #include "boxtypes.h" void g_customBorderBox(int x, int y, int w, int h, Fl_Color c) { fl_color(c); fl_rectf(x, y, w, h); fl_color(G_COLOR_GREY_4); fl_rect(x, y, w, h); } void g_customUpBox(int x, int y, int w, int h, Fl_Color c) { fl_color(G_COLOR_GREY_2); fl_rectf(x, y, w, h); fl_color(G_COLOR_GREY_2); fl_rect(x, y, w, h); } void g_customDownBox(int x, int y, int w, int h, Fl_Color c) { fl_color(c); fl_rectf(x, y, w, h); fl_color(G_COLOR_GREY_2); fl_rect(x, y, w, h); } giada-0.14.5/src/gui/elems/basics/boxtypes.h000066400000000000000000000030561322662744500206370ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * boxtypes * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef GE_BOXTYPES_H #define GE_BOXTYPES_H #include #define G_CUSTOM_BORDER_BOX FL_FREE_BOXTYPE #define G_CUSTOM_UP_BOX (Fl_Boxtype)(FL_FREE_BOXTYPE + 1) #define G_CUSTOM_DOWN_BOX (Fl_Boxtype)(FL_FREE_BOXTYPE + 3) void g_customBorderBox(int x, int y, int w, int h, Fl_Color c); void g_customUpBox (int x, int y, int w, int h, Fl_Color c); void g_customDownBox (int x, int y, int w, int h, Fl_Color c); #endif giada-0.14.5/src/gui/elems/basics/button.cpp000066400000000000000000000046071322662744500206330ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * geButton * A regular button. * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include #include "../../../core/const.h" #include "button.h" geButton::geButton(int x, int y, int w, int h, const char *L, const char **imgOff, const char **imgOn) : geBaseButton(x, y, w, h, L), imgOff (imgOff), imgOn (imgOn), bgColor0 (G_COLOR_GREY_2), bgColor1 (G_COLOR_GREY_4), bdColor (G_COLOR_GREY_4), txtColor (G_COLOR_LIGHT_2) { } /* -------------------------------------------------------------------------- */ void geButton::draw() { if (!active()) txtColor = bdColor; else txtColor = G_COLOR_LIGHT_2; fl_rect(x(), y(), w(), h(), bdColor); // borders if (value()) { // -- clicked if (imgOn != nullptr) fl_draw_pixmap(imgOn, x()+1, y()+1); else fl_rectf(x(), y(), w(), h(), bgColor1); // covers the border } else { // -- not clicked fl_rectf(x()+1, y()+1, w()-2, h()-2, bgColor0); // bg inside the border if (imgOff != nullptr) fl_draw_pixmap(imgOff, x()+1, y()+1); } if (!active()) fl_color(FL_INACTIVE_COLOR); fl_color(txtColor); fl_font(FL_HELVETICA, G_GUI_FONT_SIZE_BASE); fl_draw(label(), x()+2, y(), w()-2, h(), FL_ALIGN_CENTER); } giada-0.14.5/src/gui/elems/basics/button.h000066400000000000000000000031421322662744500202710ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * geButton * A regular button. * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef GE_BUTTON_H #define GE_BUTTON_H #include "baseButton.h" class geButton : public geBaseButton { public: geButton(int x, int y, int w, int h, const char *L=0, const char **imgOff=nullptr, const char **imgOn=nullptr); void draw() override; const char **imgOff; const char **imgOn; Fl_Color bgColor0; // background not clicked Fl_Color bgColor1; // background clicked Fl_Color bdColor; // border Fl_Color txtColor; // text }; #endif giada-0.14.5/src/gui/elems/basics/check.cpp000066400000000000000000000036671322662744500204020ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include #include "../../../core/const.h" #include "check.h" geCheck::geCheck(int x, int y, int w, int h, const char *l) : Fl_Check_Button(x, y, w, h, l) { } /* -------------------------------------------------------------------------- */ void geCheck::draw() { int color = !active() ? FL_INACTIVE_COLOR : G_COLOR_GREY_4; if (value()) { fl_rect(x(), y(), 12, 12, (Fl_Color) color); fl_rectf(x(), y(), 12, 12, (Fl_Color) color); } else { fl_rectf(x(), y(), 12, 12, FL_BACKGROUND_COLOR); fl_rect(x(), y(), 12, 12, (Fl_Color) color); } fl_rectf(x()+20, y(), w(), h(), FL_BACKGROUND_COLOR); // clearer fl_font(FL_HELVETICA, G_GUI_FONT_SIZE_BASE); fl_color(!active() ? FL_INACTIVE_COLOR : G_COLOR_LIGHT_2); fl_draw(label(), x()+20, y(), w(), h(), (Fl_Align) (FL_ALIGN_LEFT | FL_ALIGN_TOP)); } giada-0.14.5/src/gui/elems/basics/check.h000066400000000000000000000024711322662744500200370ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef GE_CHECK_H #define GE_CHECK_H #include class geCheck : public Fl_Check_Button { public: geCheck(int x, int y, int w, int h, const char *l=0); void draw(); }; #endif giada-0.14.5/src/gui/elems/basics/choice.cpp000066400000000000000000000050541322662744500205470ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include #include #include "../../../core/const.h" #include "choice.h" geChoice::geChoice(int x, int y, int w, int h, const char *l, bool ang) : Fl_Choice(x, y, w, h, l), angle(ang) { labelsize(G_GUI_FONT_SIZE_BASE); labelcolor(G_COLOR_LIGHT_2); box(FL_BORDER_BOX); textsize(G_GUI_FONT_SIZE_BASE); textcolor(G_COLOR_LIGHT_2); color(G_COLOR_GREY_2); } /* -------------------------------------------------------------------------- */ void geChoice::draw() { fl_rectf(x(), y(), w(), h(), G_COLOR_GREY_2); // bg fl_rect(x(), y(), w(), h(), (Fl_Color) G_COLOR_GREY_4); // border if (angle) fl_polygon(x()+w()-8, y()+h()-1, x()+w()-1, y()+h()-8, x()+w()-1, y()+h()-1); /* pick up the text() from the selected item (value()) and print it in * the box and avoid overflows */ fl_color(!active() ? G_COLOR_GREY_4 : G_COLOR_LIGHT_2); if (value() != -1) { if (fl_width(text(value())) < w()-8) { fl_draw(text(value()), x(), y(), w(), h(), FL_ALIGN_CENTER); } else { std::string tmp = text(value()); int size = tmp.size(); while (fl_width(tmp.c_str()) >= w()-16) { tmp.resize(size); size--; } tmp += "..."; fl_draw(tmp.c_str(), x(), y(), w(), h(), FL_ALIGN_CENTER); } } } /* -------------------------------------------------------------------------- */ void geChoice::showItem(const char *c) { value(find_index(c)); } giada-0.14.5/src/gui/elems/basics/choice.h000066400000000000000000000025631322662744500202160ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef GE_CHOICE_H #define GE_CHOICE_H #include class geChoice : public Fl_Choice { public: geChoice(int X,int Y,int W,int H,const char *L=0, bool angle=true); void draw(); void showItem(const char *c); bool angle; int id; }; #endif giada-0.14.5/src/gui/elems/basics/dial.cpp000066400000000000000000000036601322662744500202270ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include #include "../../../core/const.h" #include "dial.h" geDial::geDial(int x, int y, int w, int h, const char *l) : Fl_Dial(x, y, w, h, l) { labelsize(G_GUI_FONT_SIZE_BASE); labelcolor(G_COLOR_LIGHT_2); align(FL_ALIGN_LEFT); type(FL_FILL_DIAL); angles(0, 360); color(G_COLOR_GREY_2); // background selection_color(G_COLOR_GREY_4); // selection } /* -------------------------------------------------------------------------- */ void geDial::draw() { double angle = (angle2()-angle1())*(value()-minimum())/(maximum()-minimum()) + angle1(); fl_color(G_COLOR_GREY_2); fl_pie(x(), y(), w(), h(), 270-angle1(), angle > angle1() ? 360+270-angle : 270-360-angle); fl_color(G_COLOR_GREY_4); fl_arc(x(), y(), w(), h(), 0, 360); fl_pie(x(), y(), w(), h(), 270-angle, 270-angle1()); } giada-0.14.5/src/gui/elems/basics/dial.h000066400000000000000000000024431322662744500176720ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef GE_DIAL_H #define GE_DIAL_H #include class geDial : public Fl_Dial { public: geDial(int x, int y, int w, int h, const char *l=0); void draw(); }; #endif giada-0.14.5/src/gui/elems/basics/idButton.cpp000066400000000000000000000026141322662744500211040ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * geIdButton * Exactly as geButton but with a unique id. Used for the buttons in channels * and for FXs. * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include "idButton.h" geIdButton::geIdButton(int X, int Y, int W, int H, const char *L, const char **imgOff, const char **imgOn) : geButton(X, Y, W, H, L, imgOff, imgOn) { } giada-0.14.5/src/gui/elems/basics/idButton.h000066400000000000000000000027411322662744500205520ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * geIdButton * Exactly as geButton but with a unique id. Used for the buttons in channels * and for FXs. * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef GE_ID_BUTTON_H #define GE_ID_BUTTON_H #include "button.h" class geIdButton : public geButton { public: geIdButton(int X,int Y,int W,int H,const char *L=0, const char **imgOff=nullptr, const char **imgOn=nullptr); int key; int id; }; #endif giada-0.14.5/src/gui/elems/basics/input.cpp000066400000000000000000000031131322662744500204460ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include "../../../core/const.h" #include "boxtypes.h" #include "input.h" geInput::geInput(int x, int y, int w, int h, const char *l) : Fl_Input(x, y, w, h, l) { //Fl::set_boxtype(G_CUSTOM_BORDER_BOX, gDrawBox, 1, 1, 2, 2); box(G_CUSTOM_BORDER_BOX); labelsize(G_GUI_FONT_SIZE_BASE); labelcolor(G_COLOR_LIGHT_2); color(G_COLOR_BLACK); textcolor(G_COLOR_LIGHT_2); cursor_color(G_COLOR_LIGHT_2); selection_color(G_COLOR_GREY_4); textsize(G_GUI_FONT_SIZE_BASE); } giada-0.14.5/src/gui/elems/basics/input.h000066400000000000000000000024311322662744500201150ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef GE_INPUT_H #define GE_INPUT_H #include class geInput : public Fl_Input { public: geInput(int x, int y, int w, int h, const char *l=0); }; #endif giada-0.14.5/src/gui/elems/basics/liquidScroll.cpp000066400000000000000000000042071322662744500217620ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * gLiquidScroll * custom scroll that tells children to follow scroll's width when * resized. Thanks to Greg Ercolano from FLTK dev team. * http://seriss.com/people/erco/fltk/ * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include "../../../core/const.h" #include "boxtypes.h" #include "liquidScroll.h" geLiquidScroll::geLiquidScroll(int x, int y, int w, int h, const char* l) : Fl_Scroll(x, y, w, h, l) { type(Fl_Scroll::VERTICAL); scrollbar.color(G_COLOR_GREY_2); scrollbar.selection_color(G_COLOR_GREY_4); scrollbar.labelcolor(G_COLOR_LIGHT_1); scrollbar.slider(G_CUSTOM_BORDER_BOX); } /* -------------------------------------------------------------------------- */ void geLiquidScroll::resize(int X, int Y, int W, int H) { int nc = children()-2; // skip hscrollbar and vscrollbar for ( int t=0; tresize(c->x(), c->y(), W-24, c->h()); // W-24: leave room for scrollbar } init_sizes(); // tell scroll children changed in size Fl_Scroll::resize(X,Y,W,H); } giada-0.14.5/src/gui/elems/basics/liquidScroll.h000066400000000000000000000030331322662744500214230ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * gLiquidScroll * custom scroll that tells children to follow scroll's width when * resized. Thanks to Greg Ercolano from FLTK dev team. * http://seriss.com/people/erco/fltk/ * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef GE_LIQUID_SCROLL_H #define GE_LIQUID_SCROLL_H #include class geLiquidScroll : public Fl_Scroll { public: geLiquidScroll(int x, int y, int w, int h, const char *l=0); void resize(int x, int y, int w, int h); }; #endif giada-0.14.5/src/gui/elems/basics/progress.cpp000066400000000000000000000025401322662744500211560ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include "../../../core/const.h" #include "boxtypes.h" #include "progress.h" geProgress::geProgress(int x, int y, int w, int h, const char *l) : Fl_Progress(x, y, w, h, l) { color(G_COLOR_GREY_2, G_COLOR_GREY_4); box(G_CUSTOM_BORDER_BOX); } giada-0.14.5/src/gui/elems/basics/progress.h000066400000000000000000000024541322662744500206270ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef GE_PROGRESS_H #define GE_PROGRESS_H #include class geProgress : public Fl_Progress { public: geProgress(int x, int y, int w, int h, const char *l=0); }; #endif giada-0.14.5/src/gui/elems/basics/radio.cpp000066400000000000000000000036121322662744500204110ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include #include "../../../core/const.h" #include "radio.h" geRadio::geRadio(int x, int y, int w, int h, const char *l) : Fl_Radio_Button(x, y, w, h, l) { } /* -------------------------------------------------------------------------- */ void geRadio::draw() { int color = !active() ? FL_INACTIVE_COLOR : G_COLOR_GREY_4; if (value()) { fl_rect(x(), y(), 12, 12, (Fl_Color) color); fl_rectf(x(), y(), 12, 12, (Fl_Color) color); } else { fl_rectf(x(), y(), 12, 12, FL_BACKGROUND_COLOR); fl_rect(x(), y(), 12, 12, (Fl_Color) color); } fl_rectf(x()+20, y(), w(), h(), FL_BACKGROUND_COLOR); // clearer fl_font(FL_HELVETICA, G_GUI_FONT_SIZE_BASE); fl_color(G_COLOR_LIGHT_2); fl_draw(label(), x()+20, y(), w(), h(), (Fl_Align) (FL_ALIGN_LEFT | FL_ALIGN_TOP)); } giada-0.14.5/src/gui/elems/basics/radio.h000066400000000000000000000024701322662744500200570ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef GE_RADIO_H #define GE_RADIO_H #include class geRadio : public Fl_Radio_Button { public: geRadio(int x, int y, int w, int h, const char *l=0); void draw(); }; #endif giada-0.14.5/src/gui/elems/basics/resizerBar.cpp000066400000000000000000000131151322662744500214220ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * geResizerBar * 'resizer bar' between widgets Fl_Scroll. Thanks to Greg Ercolano from * FLTK dev team. http://seriss.com/people/erco/fltk/ * * Shows a resize cursor when hovered over. * Assumes: * - Parent is an Fl_Scroll * - All children of Fl_Scroll are m_vertically arranged * - The widget above us has a bottom edge touching our top edge * ie. (w->y()+w->h() == this->y()) * * When this widget is dragged: * - The widget above us (with a common edge) will be /resized/ * m_vertically * - All children below us will be /moved/ m_vertically * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include #include #include #include "../../../core/const.h" #include "resizerBar.h" geResizerBar::geResizerBar(int X, int Y, int W, int H, int minSize, bool type) : Fl_Box (X, Y, W, H), m_type (type), m_minSize(minSize), m_lastPos(0), m_hover (false) { if (m_type == VERTICAL) { m_origSize = H; labelsize(H); } else { m_origSize = W; labelsize(W); } align(FL_ALIGN_CENTER | FL_ALIGN_INSIDE); labelfont(FL_COURIER); visible_focus(0); } /* -------------------------------------------------------------------------- */ void geResizerBar::handleDrag(int diff) { Fl_Scroll* grp = static_cast(parent()); int top; int bot; if (m_type == VERTICAL) { top = y(); bot = y()+h(); } else { top = x(); bot = x()+w(); } // First pass: find widget directly above us with common edge // Possibly clamp 'diff' if widget would get too small.. for (int t=0; tchildren(); t++) { Fl_Widget* wd = grp->child(t); if (m_type == VERTICAL) { if ((wd->y()+wd->h()) == top) { // found widget directly above? if ((wd->h()+diff) < m_minSize) diff = wd->h() - m_minSize; // clamp wd->resize(wd->x(), wd->y(), wd->w(), wd->h()+diff); // change height break; // done with first pass } } else { if ((wd->x()+wd->w()) == top) { // found widget directly above? if ((wd->w()+diff) < m_minSize) diff = wd->w() - m_minSize; // clamp wd->resize(wd->x(), wd->y(), wd->w()+diff, wd->h()); // change height break; // done with first pass } } } // Second pass: find widgets below us, move based on clamped diff for (int t=0; tchildren(); t++) { Fl_Widget* wd = grp->child(t); if (m_type == VERTICAL) { if (wd->y() >= bot) // found widget below us? wd->resize(wd->x(), wd->y()+diff, wd->w(), wd->h()); // change position } else { if (wd->x() >= bot) wd->resize(wd->x()+diff, wd->y(), wd->w(), wd->h()); } } // Change our position last if (m_type == VERTICAL) resize(x(), y()+diff, w(), h()); else resize(x()+diff, y(), w(), h()); grp->init_sizes(); grp->redraw(); } /* -------------------------------------------------------------------------- */ void geResizerBar::draw() { Fl_Box::draw(); fl_rectf(x(), y(), w(), h(), m_hover ? G_COLOR_GREY_2 : G_COLOR_GREY_1); } /* -------------------------------------------------------------------------- */ int geResizerBar::handle(int e) { int ret = 0; int this_y; if (m_type == VERTICAL) this_y = Fl::event_y_root(); else this_y = Fl::event_x_root(); switch (e) { case FL_FOCUS: ret = 1; break; case FL_ENTER: ret = 1; fl_cursor(m_type == VERTICAL ? FL_CURSOR_NS : FL_CURSOR_WE); m_hover = true; redraw(); break; case FL_LEAVE: ret = 1; fl_cursor(FL_CURSOR_DEFAULT); m_hover = false; redraw(); break; case FL_PUSH: ret = 1; m_lastPos = this_y; break; case FL_DRAG: handleDrag(this_y-m_lastPos); m_lastPos = this_y; ret = 1; break; default: break; } return(Fl_Box::handle(e) | ret); } /* -------------------------------------------------------------------------- */ int geResizerBar::getMinSize() const { return m_minSize; } /* -------------------------------------------------------------------------- */ void geResizerBar::resize(int x, int y, int w, int h) { if (m_type == VERTICAL) Fl_Box::resize(x, y, w, m_origSize); // Height of resizer stays constant size else Fl_Box::resize(x, y, m_origSize, h); } giada-0.14.5/src/gui/elems/basics/resizerBar.h000066400000000000000000000043101322662744500210640ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * geResizerBar * 'resizer bar' between widgets Fl_Scroll. Thanks to Greg Ercolano from * FLTK dev team. http://seriss.com/people/erco/fltk/ * * Shows a resize cursor when hovered over. * Assumes: * - Parent is an Fl_Scroll * - All children of Fl_Scroll are vertically arranged * - The widget above us has a bottom edge touching our top edge * ie. (w->y()+w->h() == this->y()) * * When this widget is dragged: * - The widget above us (with a common edge) will be /resized/ * vertically * - All children below us will be /moved/ vertically * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef GE_RESIZER_BAR_H #define GE_RESIZER_BAR_H #include class geResizerBar : public Fl_Box { private: bool m_type; int m_origSize; int m_minSize; int m_lastPos; bool m_hover; void handleDrag(int diff); public: static const int HORIZONTAL = 0; static const int VERTICAL = 1; geResizerBar(int x, int y, int w, int h, int minSize, bool type=VERTICAL); int handle(int e) override; void draw() override; void resize(int x, int y, int w, int h) override; int getMinSize() const; }; #endif giada-0.14.5/src/gui/elems/basics/scroll.cpp000066400000000000000000000032501322662744500206070ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * geScroll * Custom scroll with nice scrollbars and something else. * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include "../../../core/const.h" #include "boxtypes.h" #include "scroll.h" geScroll::geScroll(int x, int y, int w, int h, int t) : Fl_Scroll(x, y, w, h) { type(t); scrollbar.color(G_COLOR_GREY_2); scrollbar.selection_color(G_COLOR_GREY_4); scrollbar.labelcolor(G_COLOR_LIGHT_1); scrollbar.slider(G_CUSTOM_BORDER_BOX); hscrollbar.color(G_COLOR_GREY_2); hscrollbar.selection_color(G_COLOR_GREY_4); hscrollbar.labelcolor(G_COLOR_LIGHT_1); hscrollbar.slider(G_CUSTOM_BORDER_BOX); } giada-0.14.5/src/gui/elems/basics/scroll.h000066400000000000000000000025621322662744500202610ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * geScroll * Custom scroll with nice scrollbars and something else. * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef GE_SCROLL_H #define GE_SCROLL_H #include class geScroll : public Fl_Scroll { public: geScroll(int x, int y, int w, int h, int type=Fl_Scroll::BOTH); }; #endif giada-0.14.5/src/gui/elems/basics/slider.cpp000066400000000000000000000027351322662744500206020ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include "../../../core/const.h" #include "boxtypes.h" #include "slider.h" geSlider::geSlider(int x, int y, int w, int h, const char *l) : Fl_Slider(x, y, w, h, l) { type(FL_HOR_FILL_SLIDER); labelsize(G_GUI_FONT_SIZE_BASE); align(FL_ALIGN_LEFT); labelcolor(G_COLOR_LIGHT_2); box(G_CUSTOM_BORDER_BOX); color(G_COLOR_GREY_2); selection_color(G_COLOR_GREY_4); } giada-0.14.5/src/gui/elems/basics/slider.h000066400000000000000000000024541322662744500202450ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef GE_SLIDER_H #define GE_SLIDER_H #include class geSlider : public Fl_Slider { public: geSlider(int x, int y, int w, int h, const char *l=0); int id; }; #endif giada-0.14.5/src/gui/elems/basics/statusButton.cpp000066400000000000000000000032111322662744500220250ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * geStatusButton * Simple geButton with a boolean 'status' parameter. * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include #include "../../../core/const.h" #include "statusButton.h" geStatusButton::geStatusButton(int x, int y, int w, int h, const char **imgOff, const char **imgOn) : geButton(x, y, w, h, nullptr, imgOff, imgOn), status (false) { } /* -------------------------------------------------------------------------- */ void geStatusButton::draw() { geButton::draw(); if (status) fl_draw_pixmap(imgOn, x()+1, y()+1, G_COLOR_GREY_4); } giada-0.14.5/src/gui/elems/basics/statusButton.h000066400000000000000000000027211322662744500214770ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * geStatusButton * Simple geButton with a boolean 'status' parameter. * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef GE_STATUS_BUTTON_H #define GE_STATUS_BUTTON_H #include "button.h" class geStatusButton : public geButton { public: geStatusButton(int x, int y, int w, int h, const char **imgOff=nullptr, const char **imgOn=nullptr); void draw() override; bool status; }; #endif giada-0.14.5/src/gui/elems/browser.cpp000066400000000000000000000117251322662744500175360ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include "../../core/const.h" #include "../../utils/string.h" #include "../dialogs/browser/browserBase.h" #include "basics/boxtypes.h" #include "browser.h" using std::string; geBrowser::geBrowser(int x, int y, int w, int h) : Fl_File_Browser(x, y, w, h), showHiddenFiles(false) { box(G_CUSTOM_BORDER_BOX); textsize(G_GUI_FONT_SIZE_BASE); textcolor(G_COLOR_LIGHT_2); selection_color(G_COLOR_GREY_4); color(G_COLOR_GREY_2); type(FL_SELECT_BROWSER); this->scrollbar.color(G_COLOR_GREY_2); this->scrollbar.selection_color(G_COLOR_GREY_4); this->scrollbar.labelcolor(G_COLOR_LIGHT_1); this->scrollbar.slider(G_CUSTOM_BORDER_BOX); this->hscrollbar.color(G_COLOR_GREY_2); this->hscrollbar.selection_color(G_COLOR_GREY_4); this->hscrollbar.labelcolor(G_COLOR_LIGHT_1); this->hscrollbar.slider(G_CUSTOM_BORDER_BOX); take_focus(); // let it have focus on startup } /* -------------------------------------------------------------------------- */ void geBrowser::toggleHiddenFiles() { showHiddenFiles = !showHiddenFiles; loadDir(currentDir); } /* -------------------------------------------------------------------------- */ void geBrowser::loadDir(const string &dir) { currentDir = dir; load(currentDir.c_str()); /* Clean up unwanted elements. Hide "../" first, it just screws up things. Also remove hidden files, if requested. */ for (int i=size(); i>=0; i--) { if (text(i) == nullptr) continue; if (strcmp(text(i), "../") == 0 || (!showHiddenFiles && strncmp(text(i), ".", 1) == 0)) remove(i); } } /* -------------------------------------------------------------------------- */ int geBrowser::handle(int e) { int ret = Fl_File_Browser::handle(e); switch (e) { case FL_FOCUS: case FL_UNFOCUS: ret = 1; // enables receiving Keyboard events break; case FL_KEYDOWN: // keyboard if (Fl::event_key(FL_Down)) select(value() + 1); else if (Fl::event_key(FL_Up)) select(value() - 1); else if (Fl::event_key(FL_Enter)) ((gdBrowserBase*) parent())->fireCallback(); ret = 1; break; case FL_PUSH: // mouse if (Fl::event_clicks() > 0) // double click ((gdBrowserBase*) parent())->fireCallback(); ret = 1; break; case FL_RELEASE: // mouse /* nasty trick to keep the selection on mouse release */ if (value() > 1) { select(value() - 1); select(value() + 1); } else { select(value() + 1); select(value() - 1); } ret = 1; break; } return ret; } /* -------------------------------------------------------------------------- */ string geBrowser::getCurrentDir() { return normalize(gu_getRealPath(currentDir)); } /* -------------------------------------------------------------------------- */ string geBrowser::getSelectedItem(bool fullPath) { if (!fullPath) // no full path requested? return the selected text return normalize(text(value())); else if (value() == 0) // no rows selected? return current directory return normalize(currentDir); else return normalize(gu_getRealPath(currentDir + G_SLASH + normalize(text(value())))); } /* -------------------------------------------------------------------------- */ void geBrowser::preselect(int pos, int line) { position(pos); select(line); } /* -------------------------------------------------------------------------- */ string geBrowser::normalize(const string &s) { string out = s; /* If string ends with G_SLASH, remove it. Don't do it if has length > 1, it means that the string is just '/'. Note: our crappy version of Clang doesn't seem to support std::string::back() */ #ifdef __APPLE__ if (out[out.length() - 1] == G_SLASH && out.length() > 1) #else if (out.back() == G_SLASH && out.length() > 1) #endif out = out.substr(0, out.size()-1); return out; } giada-0.14.5/src/gui/elems/browser.h000066400000000000000000000036501322662744500172010ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ge_browser * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef GE_BROWSER_H #define GE_BROWSER_H #include #include class geBrowser : public Fl_File_Browser { private: std::string currentDir; bool showHiddenFiles; /* normalize * Make sure the std::string never ends with a trailing slash. */ std::string normalize(const std::string &s); public: geBrowser(int x, int y, int w, int h); void toggleHiddenFiles(); /* init * Initialize browser and show 'dir' as initial directory. */ void loadDir(const std::string &dir); /* getSelectedItem * Return the full path or just the displayed name of the i-th selected item. * Always with the trailing slash! */ std::string getSelectedItem(bool fullPath=true); std::string getCurrentDir(); void preselect(int position, int line); int handle(int e); }; #endif giada-0.14.5/src/gui/elems/config/000077500000000000000000000000001322662744500166065ustar00rootroot00000000000000giada-0.14.5/src/gui/elems/config/tabAudio.cpp000066400000000000000000000334101322662744500210430ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include #include "../../../deps/rtaudio-mod/RtAudio.h" #include "../../../core/const.h" #include "../../../core/conf.h" #include "../../../core/kernelAudio.h" #include "../../../utils/string.h" #include "../../../gui/dialogs/gd_devInfo.h" #include "../basics/box.h" #include "../basics/choice.h" #include "../basics/check.h" #include "../basics/input.h" #include "../basics/button.h" #include "tabAudio.h" using std::string; using namespace giada::m; geTabAudio::geTabAudio(int X, int Y, int W, int H) : Fl_Group(X, Y, W, H, "Sound System") { begin(); soundsys = new geChoice(x()+114, y()+9, 250, 20, "System"); buffersize = new geChoice(x()+114, y()+37, 55, 20, "Buffer size"); samplerate = new geChoice(x()+309, y()+37, 55, 20, "Sample rate"); sounddevOut = new geChoice(x()+114, y()+65, 222, 20, "Output device"); devOutInfo = new geButton(x()+344, y()+65, 20, 20, "?"); channelsOut = new geChoice(x()+114, y()+93, 55, 20, "Output channels"); limitOutput = new geCheck (x()+177, y()+97, 55, 20, "Limit output"); sounddevIn = new geChoice(x()+114, y()+121, 222, 20, "Input device"); devInInfo = new geButton(x()+344, y()+121, 20, 20, "?"); channelsIn = new geChoice(x()+114, y()+149, 55, 20, "Input channels"); delayComp = new geInput (x()+309, y()+149, 55, 20, "Rec delay comp."); rsmpQuality = new geChoice(x()+114, y()+177, 250, 20, "Resampling"); new geBox(x(), rsmpQuality->y()+rsmpQuality->h()+8, w(), 92, "Restart Giada for the changes to take effect."); end(); labelsize(G_GUI_FONT_SIZE_BASE); soundsys->add("(none)"); #if defined(__linux__) if (kernelAudio::hasAPI(RtAudio::LINUX_ALSA)) soundsys->add("ALSA"); if (kernelAudio::hasAPI(RtAudio::UNIX_JACK)) soundsys->add("Jack"); if (kernelAudio::hasAPI(RtAudio::LINUX_PULSE)) soundsys->add("PulseAudio"); switch (conf::soundSystem) { case G_SYS_API_NONE: soundsys->showItem("(none)"); break; case G_SYS_API_ALSA: soundsys->showItem("ALSA"); break; case G_SYS_API_JACK: soundsys->showItem("Jack"); buffersize->deactivate(); samplerate->deactivate(); break; case G_SYS_API_PULSE: soundsys->showItem("PulseAudio"); break; } #elif defined(_WIN32) if (kernelAudio::hasAPI(RtAudio::WINDOWS_DS)) soundsys->add("DirectSound"); if (kernelAudio::hasAPI(RtAudio::WINDOWS_ASIO)) soundsys->add("ASIO"); if (kernelAudio::hasAPI(RtAudio::WINDOWS_WASAPI)) soundsys->add("WASAPI"); switch (conf::soundSystem) { case G_SYS_API_NONE: soundsys->showItem("(none)"); break; case G_SYS_API_DS: soundsys->showItem("DirectSound"); break; case G_SYS_API_ASIO: soundsys->showItem("ASIO"); break; case G_SYS_API_WASAPI: soundsys->showItem("WASAPI"); break; } #elif defined (__APPLE__) if (kernelAudio::hasAPI(RtAudio::MACOSX_CORE)) soundsys->add("CoreAudio"); switch (conf::soundSystem) { case G_SYS_API_NONE: soundsys->showItem("(none)"); break; case G_SYS_API_CORE: soundsys->showItem("CoreAudio"); break; } #endif soundsysInitValue = soundsys->value(); soundsys->callback(cb_deactivate_sounddev, (void*)this); sounddevIn->callback(cb_fetchInChans, this); sounddevOut->callback(cb_fetchOutChans, this); devOutInfo->callback(cb_showOutputInfo, this); devInInfo->callback(cb_showInputInfo, this); if (conf::soundSystem != G_SYS_API_NONE) { fetchSoundDevs(); fetchOutChans(sounddevOut->value()); fetchInChans(sounddevIn->value()); /* fill frequency dropdown menu */ /* TODO - add fetchFrequencies() */ int nfreq = kernelAudio::getTotalFreqs(sounddevOut->value()); for (int i=0; ivalue(), i); samplerate->add(gu_iToString(freq).c_str()); if (freq == conf::samplerate) samplerate->value(i); } } else { sounddevIn->deactivate(); sounddevOut->deactivate(); channelsIn->deactivate(); channelsOut->deactivate(); devOutInfo->deactivate(); devInInfo->deactivate(); samplerate->deactivate(); } buffersize->add("8"); buffersize->add("16"); buffersize->add("32"); buffersize->add("64"); buffersize->add("128"); buffersize->add("256"); buffersize->add("512"); buffersize->add("1024"); buffersize->add("2048"); buffersize->add("4096"); buffersize->showItem(gu_iToString(conf::buffersize).c_str()); rsmpQuality->add("Sinc best quality (very slow)"); rsmpQuality->add("Sinc medium quality (slow)"); rsmpQuality->add("Sinc basic quality (medium)"); rsmpQuality->add("Zero Order Hold (fast)"); rsmpQuality->add("Linear (very fast)"); rsmpQuality->value(conf::rsmpQuality); delayComp->value(gu_iToString(conf::delayComp).c_str()); delayComp->type(FL_INT_INPUT); delayComp->maximum_size(5); limitOutput->value(conf::limitOutput); } /* -------------------------------------------------------------------------- */ void geTabAudio::cb_deactivate_sounddev(Fl_Widget *w, void *p) { ((geTabAudio*)p)->__cb_deactivate_sounddev(); } void geTabAudio::cb_fetchInChans(Fl_Widget *w, void *p) { ((geTabAudio*)p)->__cb_fetchInChans(); } void geTabAudio::cb_fetchOutChans(Fl_Widget *w, void *p) { ((geTabAudio*)p)->__cb_fetchOutChans(); } void geTabAudio::cb_showInputInfo(Fl_Widget *w, void *p) { ((geTabAudio*)p)->__cb_showInputInfo(); } void geTabAudio::cb_showOutputInfo(Fl_Widget *w, void *p) { ((geTabAudio*)p)->__cb_showOutputInfo(); } /* -------------------------------------------------------------------------- */ void geTabAudio::__cb_fetchInChans() { fetchInChans(sounddevIn->value()); channelsIn->value(0); } /* -------------------------------------------------------------------------- */ void geTabAudio::__cb_fetchOutChans() { fetchOutChans(sounddevOut->value()); channelsOut->value(0); } /* -------------------------------------------------------------------------- */ void geTabAudio::__cb_showInputInfo() { unsigned dev = kernelAudio::getDeviceByName(sounddevIn->text(sounddevIn->value())); new gdDevInfo(dev); } /* -------------------------------------------------------------------------- */ void geTabAudio::__cb_showOutputInfo() { unsigned dev = kernelAudio::getDeviceByName(sounddevOut->text(sounddevOut->value())); new gdDevInfo(dev); } /* -------------------------------------------------------------------------- */ void geTabAudio::__cb_deactivate_sounddev() { /* if the user changes sound system (eg ALSA->JACK) device menu deactivates. * If it returns to the original sound system, we re-fill the list by * querying kernelAudio. Watch out if soundsysInitValue == 0: you don't want * to query kernelAudio for '(none)' soundsystem! */ if (soundsysInitValue == soundsys->value() && soundsysInitValue != 0) { sounddevOut->clear(); sounddevIn->clear(); fetchSoundDevs(); /* the '?' button is added by fetchSoundDevs */ fetchOutChans(sounddevOut->value()); sounddevOut->activate(); channelsOut->activate(); /* chan menus and '?' button are activated by fetchInChans(...) */ fetchInChans(sounddevIn->value()); sounddevIn->activate(); samplerate->activate(); } else { sounddevOut->deactivate(); sounddevOut->clear(); sounddevOut->add("-- restart to fetch device(s) --"); sounddevOut->value(0); channelsOut->deactivate(); devOutInfo->deactivate(); samplerate->deactivate(); sounddevIn->deactivate(); sounddevIn->clear(); sounddevIn->add("-- restart to fetch device(s) --"); sounddevIn->value(0); channelsIn->deactivate(); devInInfo->deactivate(); } } /* -------------------------------------------------------------------------- */ void geTabAudio::fetchInChans(int menuItem) { /* if menuItem==0 device in input is disabled. */ if (menuItem == 0) { devInInfo ->deactivate(); channelsIn->deactivate(); delayComp ->deactivate(); return; } devInInfo ->activate(); channelsIn->activate(); delayComp ->activate(); channelsIn->clear(); unsigned dev = kernelAudio::getDeviceByName(sounddevIn->text(sounddevIn->value())); unsigned chs = kernelAudio::getMaxInChans(dev); if (chs == 0) { channelsIn->add("none"); channelsIn->value(0); return; } for (unsigned i=0; iadd(tmp.c_str()); } channelsIn->value(conf::channelsIn); } /* -------------------------------------------------------------------------- */ void geTabAudio::fetchOutChans(int menuItem) { channelsOut->clear(); unsigned dev = kernelAudio::getDeviceByName(sounddevOut->text(sounddevOut->value())); unsigned chs = kernelAudio::getMaxOutChans(dev); if (chs == 0) { channelsOut->add("none"); channelsOut->value(0); return; } for (unsigned i=0; iadd(tmp.c_str()); } channelsOut->value(conf::channelsOut); } /* -------------------------------------------------------------------------- */ int geTabAudio::findMenuDevice(geChoice *m, int device) { if (device == -1) return 0; if (kernelAudio::getStatus() == false) return 0; for (int i=0; isize(); i++) { if (kernelAudio::getDeviceName(device) == "") continue; if (m->text(i) == nullptr) continue; if (m->text(i) == kernelAudio::getDeviceName(device)) return i; } return 0; } /* -------------------------------------------------------------------------- */ void geTabAudio::fetchSoundDevs() { if (kernelAudio::countDevices() == 0) { sounddevOut->add("-- no devices found --"); sounddevOut->value(0); sounddevIn->add("-- no devices found --"); sounddevIn->value(0); devInInfo ->deactivate(); devOutInfo->deactivate(); } else { devInInfo ->activate(); devOutInfo->activate(); /* input device may be disabled: now device number -1 is the disabled * one. KernelAudio knows how to handle it. */ sounddevIn->add("(disabled)"); for (unsigned i=0; i 0) sounddevOut->add(tmp.c_str()); if (kernelAudio::getMaxInChans(i) > 0) sounddevIn->add(tmp.c_str()); } /* we show the device saved in the configuration file. */ if (sounddevOut->size() == 0) { sounddevOut->add("-- no devices found --"); sounddevOut->value(0); devOutInfo->deactivate(); } else { int outMenuValue = findMenuDevice(sounddevOut, conf::soundDeviceOut); sounddevOut->value(outMenuValue); } if (sounddevIn->size() == 0) { sounddevIn->add("-- no devices found --"); sounddevIn->value(0); devInInfo->deactivate(); } else { int inMenuValue = findMenuDevice(sounddevIn, conf::soundDeviceIn); sounddevIn->value(inMenuValue); } } } /* -------------------------------------------------------------------------- */ void geTabAudio::save() { string text = soundsys->text(soundsys->value()); if (text == "(none)") { conf::soundSystem = G_SYS_API_NONE; return; } #if defined(__linux__) else if (text == "ALSA") conf::soundSystem = G_SYS_API_ALSA; else if (text == "Jack") conf::soundSystem = G_SYS_API_JACK; else if (text == "PulseAudio") conf::soundSystem = G_SYS_API_PULSE; #elif defined(_WIN32) else if (text == "DirectSound") conf::soundSystem = G_SYS_API_DS; else if (text == "ASIO") conf::soundSystem = G_SYS_API_ASIO; else if (text == "WASAPI") conf::soundSystem = G_SYS_API_WASAPI; #elif defined (__APPLE__) else if (text == "CoreAudio") conf::soundSystem = G_SYS_API_CORE; #endif /* use the device name to search into the drop down menu's */ conf::soundDeviceOut = kernelAudio::getDeviceByName(sounddevOut->text(sounddevOut->value())); conf::soundDeviceIn = kernelAudio::getDeviceByName(sounddevIn->text(sounddevIn->value())); conf::channelsOut = channelsOut->value(); conf::channelsIn = channelsIn->value(); conf::limitOutput = limitOutput->value(); conf::rsmpQuality = rsmpQuality->value(); /* if sounddevOut is disabled (because of system change e.g. alsa -> * jack) its value is equal to -1. Change it! */ if (conf::soundDeviceOut == -1) conf::soundDeviceOut = 0; int bufsize = atoi(buffersize->text()); if (bufsize % 2 != 0) bufsize++; if (bufsize < 8) bufsize = 8; if (bufsize > 8192) bufsize = 8192; conf::buffersize = bufsize; const Fl_Menu_Item *i = nullptr; i = samplerate->mvalue(); // mvalue() returns a pointer to the last menu item that was picked if (i) conf::samplerate = atoi(i->label()); conf::delayComp = atoi(delayComp->value()); } giada-0.14.5/src/gui/elems/config/tabAudio.h000066400000000000000000000044171322662744500205150ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef GE_TAB_AUDIO_H #define GE_TAB_AUDIO_H #include class geChoice; class geCheck; class geButton; class geInput; class geTabAudio : public Fl_Group { private: static void cb_deactivate_sounddev(Fl_Widget *w, void *p); static void cb_fetchInChans (Fl_Widget *w, void *p); static void cb_fetchOutChans (Fl_Widget *w, void *p); static void cb_showInputInfo (Fl_Widget *w, void *p); static void cb_showOutputInfo (Fl_Widget *w, void *p); inline void __cb_deactivate_sounddev(); inline void __cb_fetchInChans(); inline void __cb_fetchOutChans(); inline void __cb_showInputInfo(); inline void __cb_showOutputInfo(); void fetchSoundDevs(); void fetchInChans(int menuItem); void fetchOutChans(int menuItem); int findMenuDevice(geChoice *m, int device); int soundsysInitValue; public: geChoice *soundsys; geChoice *samplerate; geChoice *rsmpQuality; geChoice *sounddevIn; geButton *devInInfo; geChoice *channelsIn; geChoice *sounddevOut; geButton *devOutInfo; geChoice *channelsOut; geCheck *limitOutput; geChoice *buffersize; geInput *delayComp; geTabAudio(int x, int y, int w, int h); void save(); }; #endif giada-0.14.5/src/gui/elems/config/tabBehaviors.cpp000066400000000000000000000074521322662744500217330ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include #include "../../../core/const.h" #include "../../../core/conf.h" #include "../basics/box.h" #include "../basics/radio.h" #include "../basics/check.h" #include "tabBehaviors.h" using std::string; using namespace giada::m; geTabBehaviors::geTabBehaviors(int X, int Y, int W, int H) : Fl_Group(X, Y, W, H, "Behaviors") { begin(); Fl_Group *radioGrp_1 = new Fl_Group(x(), y()+10, w(), 70); // radio group for the mutex new geBox(x(), y()+10, 70, 25, "When a channel with recorded actions is halted:", FL_ALIGN_LEFT); recsStopOnChanHalt_1 = new geRadio(x()+25, y()+35, 280, 20, "stop it immediately"); recsStopOnChanHalt_0 = new geRadio(x()+25, y()+55, 280, 20, "play it until finished"); radioGrp_1->end(); Fl_Group *radioGrp_2 = new Fl_Group(x(), y()+70, w(), 70); // radio group for the mutex new geBox(x(), y()+80, 70, 25, "When the sequencer is halted:", FL_ALIGN_LEFT); chansStopOnSeqHalt_1 = new geRadio(x()+25, y()+105, 280, 20, "stop immediately all dynamic channels"); chansStopOnSeqHalt_0 = new geRadio(x()+25, y()+125, 280, 20, "play all dynamic channels until finished"); radioGrp_2->end(); treatRecsAsLoops = new geCheck(x(), y()+155, 280, 20, "Treat one shot channels with actions as loops"); inputMonitorDefaultOn = new geCheck(x(), y()+180, 280, 20, "New sample channels have input monitor on by default"); end(); labelsize(G_GUI_FONT_SIZE_BASE); conf::recsStopOnChanHalt == 1 ? recsStopOnChanHalt_1->value(1) : recsStopOnChanHalt_0->value(1); conf::chansStopOnSeqHalt == 1 ? chansStopOnSeqHalt_1->value(1) : chansStopOnSeqHalt_0->value(1); treatRecsAsLoops->value(conf::treatRecsAsLoops); inputMonitorDefaultOn->value(conf::inputMonitorDefaultOn); recsStopOnChanHalt_1->callback(cb_radio_mutex, (void*)this); recsStopOnChanHalt_0->callback(cb_radio_mutex, (void*)this); chansStopOnSeqHalt_1->callback(cb_radio_mutex, (void*)this); chansStopOnSeqHalt_0->callback(cb_radio_mutex, (void*)this); } /* -------------------------------------------------------------------------- */ void geTabBehaviors::cb_radio_mutex(Fl_Widget *w, void *p) { ((geTabBehaviors*)p)->__cb_radio_mutex(w); } /* -------------------------------------------------------------------------- */ void geTabBehaviors::__cb_radio_mutex(Fl_Widget *w) { ((Fl_Button *)w)->type(FL_RADIO_BUTTON); } /* -------------------------------------------------------------------------- */ void geTabBehaviors::save() { conf::recsStopOnChanHalt = recsStopOnChanHalt_1->value() == 1 ? 1 : 0; conf::chansStopOnSeqHalt = chansStopOnSeqHalt_1->value() == 1 ? 1 : 0; conf::treatRecsAsLoops = treatRecsAsLoops->value() == 1 ? 1 : 0; conf::inputMonitorDefaultOn = inputMonitorDefaultOn->value() == 1 ? 1 : 0; } giada-0.14.5/src/gui/elems/config/tabBehaviors.h000066400000000000000000000032031322662744500213660ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef GE_TAB_BEHAVIORS_H #define GE_TAB_BEHAVIORS_H #include class geRadio; class geCheck; class geTabBehaviors : public Fl_Group { private: static void cb_radio_mutex (Fl_Widget *w, void *p); inline void __cb_radio_mutex(Fl_Widget *w); public: geRadio *recsStopOnChanHalt_1; geRadio *recsStopOnChanHalt_0; geRadio *chansStopOnSeqHalt_1; geRadio *chansStopOnSeqHalt_0; geCheck *treatRecsAsLoops; geCheck *inputMonitorDefaultOn; geTabBehaviors(int x, int y, int w, int h); void save(); }; #endif giada-0.14.5/src/gui/elems/config/tabMidi.cpp000066400000000000000000000157421322662744500206740ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include #include "../../../core/const.h" #ifdef G_OS_MAC #include #else #include #endif #include "../../../core/conf.h" #include "../../../core/midiMapConf.h" #include "../../../core/kernelMidi.h" #include "../../../utils/gui.h" #include "../basics/box.h" #include "../basics/choice.h" #include "../basics/check.h" #include "tabMidi.h" using std::string; using namespace giada::m; geTabMidi::geTabMidi(int X, int Y, int W, int H) : Fl_Group(X, Y, W, H, "MIDI") { begin(); system = new geChoice(x()+w()-250, y()+9, 250, 20, "System"); portOut = new geChoice(x()+w()-250, system->y()+system->h()+8, 250, 20, "Output port"); portIn = new geChoice(x()+w()-250, portOut->y()+portOut->h()+8, 250, 20, "Input port"); noNoteOff = new geCheck (x()+w()-250, portIn->y()+portIn->h()+8, 230, 20, "Device does not send NoteOff"); midiMap = new geChoice(x()+w()-250, noNoteOff->y()+noNoteOff->h(), 250, 20, "Output Midi Map"); sync = new geChoice(x()+w()-250, midiMap->y()+midiMap->h()+8, 250, 20, "Sync"); new geBox(x(), sync->y()+sync->h()+8, w(), h()-150, "Restart Giada for the changes to take effect."); end(); labelsize(G_GUI_FONT_SIZE_BASE); system->callback(cb_changeSystem, (void*)this); fetchSystems(); fetchOutPorts(); fetchInPorts(); fetchMidiMaps(); noNoteOff->value(conf::noNoteOff); sync->add("(disabled)"); sync->add("MIDI Clock (master)"); sync->add("MTC (master)"); if (conf::midiSync == MIDI_SYNC_NONE) sync->value(0); else if (conf::midiSync == MIDI_SYNC_CLOCK_M) sync->value(1); else if (conf::midiSync == MIDI_SYNC_MTC_M) sync->value(2); systemInitValue = system->value(); } /* -------------------------------------------------------------------------- */ void geTabMidi::fetchOutPorts() { if (kernelMidi::countOutPorts() == 0) { portOut->add("-- no ports found --"); portOut->value(0); portOut->deactivate(); } else { portOut->add("(disabled)"); for (unsigned i=0; iadd(gu_removeFltkChars(kernelMidi::getOutPortName(i)).c_str()); portOut->value(conf::midiPortOut+1); // +1 because midiPortOut=-1 is '(disabled)' } } /* -------------------------------------------------------------------------- */ void geTabMidi::fetchInPorts() { if (kernelMidi::countInPorts() == 0) { portIn->add("-- no ports found --"); portIn->value(0); portIn->deactivate(); } else { portIn->add("(disabled)"); for (unsigned i=0; iadd(gu_removeFltkChars(kernelMidi::getInPortName(i)).c_str()); portIn->value(conf::midiPortIn+1); // +1 because midiPortIn=-1 is '(disabled)' } } /* -------------------------------------------------------------------------- */ void geTabMidi::fetchMidiMaps() { if (midimap::maps.size() == 0) { midiMap->add("(no MIDI maps available)"); midiMap->value(0); midiMap->deactivate(); return; } for (unsigned i=0; iadd(imap); if (conf::midiMapPath == imap) midiMap->value(i); } /* Preselect the 0 midimap if nothing is selected but midimaps exist. */ if (midiMap->value() == -1 && midimap::maps.size() > 0) midiMap->value(0); } /* -------------------------------------------------------------------------- */ void geTabMidi::save() { string text = system->text(system->value()); if (text == "ALSA") conf::midiSystem = RtMidi::LINUX_ALSA; else if (text == "Jack") conf::midiSystem = RtMidi::UNIX_JACK; else if (text == "Multimedia MIDI") conf::midiSystem = RtMidi::WINDOWS_MM; else if (text == "OSX Core MIDI") conf::midiSystem = RtMidi::MACOSX_CORE; conf::midiPortOut = portOut->value()-1; // -1 because midiPortOut=-1 is '(disabled)' conf::midiPortIn = portIn->value()-1; // -1 because midiPortIn=-1 is '(disabled)' conf::noNoteOff = noNoteOff->value(); conf::midiMapPath = midimap::maps.size() == 0 ? "" : midiMap->text(midiMap->value()); if (sync->value() == 0) conf::midiSync = MIDI_SYNC_NONE; else if (sync->value() == 1) conf::midiSync = MIDI_SYNC_CLOCK_M; else if (sync->value() == 2) conf::midiSync = MIDI_SYNC_MTC_M; } /* -------------------------------------------------------------------------- */ void geTabMidi::fetchSystems() { #if defined(__linux__) if (kernelMidi::hasAPI(RtMidi::LINUX_ALSA)) system->add("ALSA"); if (kernelMidi::hasAPI(RtMidi::UNIX_JACK)) system->add("Jack"); #elif defined(_WIN32) if (kernelMidi::hasAPI(RtMidi::WINDOWS_MM)) system->add("Multimedia MIDI"); #elif defined (__APPLE__) system->add("OSX Core MIDI"); #endif switch (conf::midiSystem) { case RtMidi::LINUX_ALSA: system->showItem("ALSA"); break; case RtMidi::UNIX_JACK: system->showItem("Jack"); break; case RtMidi::WINDOWS_MM: system->showItem("Multimedia MIDI"); break; case RtMidi::MACOSX_CORE: system->showItem("OSX Core MIDI"); break; default: system->value(0); break; } } /* -------------------------------------------------------------------------- */ void geTabMidi::cb_changeSystem(Fl_Widget *w, void *p) { ((geTabMidi*)p)->__cb_changeSystem(); } /* -------------------------------------------------------------------------- */ void geTabMidi::__cb_changeSystem() { /* if the user changes MIDI device (eg ALSA->JACK) device menu deactivates. * If it returns to the original system, we re-fill the list by * querying kernelMidi. */ if (systemInitValue == system->value()) { portOut->clear(); fetchOutPorts(); portOut->activate(); portIn->clear(); fetchInPorts(); portIn->activate(); noNoteOff->activate(); sync->activate(); } else { portOut->deactivate(); portOut->clear(); portOut->add("-- restart to fetch device(s) --"); portOut->value(0); portIn->deactivate(); portIn->clear(); portIn->add("-- restart to fetch device(s) --"); portIn->value(0); noNoteOff->deactivate(); sync->deactivate(); } } giada-0.14.5/src/gui/elems/config/tabMidi.h000066400000000000000000000032171322662744500203330ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef GE_TAB_MIDI_H #define GE_TAB_MIDI_H #include class geChoice; class geCheck; class geTabMidi : public Fl_Group { private: void fetchSystems(); void fetchOutPorts(); void fetchInPorts(); void fetchMidiMaps(); static void cb_changeSystem (Fl_Widget *w, void *p); inline void __cb_changeSystem(); int systemInitValue; public: geChoice *system; geChoice *portOut; geChoice *portIn; geCheck *noNoteOff; geChoice *midiMap; geChoice *sync; geTabMidi(int x, int y, int w, int h); void save(); }; #endif giada-0.14.5/src/gui/elems/config/tabMisc.cpp000066400000000000000000000040471322662744500207010ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include "../../../core/const.h" #include "../../../core/conf.h" #include "../basics/choice.h" #include "tabMisc.h" using namespace giada::m; geTabMisc::geTabMisc(int X, int Y, int W, int H) : Fl_Group(X, Y, W, H, "Misc") { begin(); debugMsg = new geChoice(x()+w()-230, y()+9, 230, 20, "Debug messages"); end(); debugMsg->add("(disabled)"); debugMsg->add("To standard output"); debugMsg->add("To file"); labelsize(G_GUI_FONT_SIZE_BASE); switch (conf::logMode) { case LOG_MODE_MUTE: debugMsg->value(0); break; case LOG_MODE_STDOUT: debugMsg->value(1); break; case LOG_MODE_FILE: debugMsg->value(2); break; } } /* -------------------------------------------------------------------------- */ void geTabMisc::save() { switch(debugMsg->value()) { case 0: conf::logMode = LOG_MODE_MUTE; break; case 1: conf::logMode = LOG_MODE_STDOUT; break; case 2: conf::logMode = LOG_MODE_FILE; break; } } giada-0.14.5/src/gui/elems/config/tabMisc.h000066400000000000000000000025111322662744500203400ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef GE_TAB_MISC_H #define GE_TAB_MISC_H #include class geChoice; class geTabMisc : public Fl_Group { public: geChoice *debugMsg; geTabMisc(int x, int y, int w, int h); void save(); }; #endif giada-0.14.5/src/gui/elems/config/tabPlugins.cpp000066400000000000000000000103451322662744500214250ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifdef WITH_VST #include #include #include "../../../core/const.h" #include "../../../core/conf.h" #include "../../../core/graphics.h" #include "../../../core/pluginHost.h" #include "../../../glue/plugin.h" #include "../../../utils/string.h" #include "../../../utils/fs.h" #include "../../../utils/gui.h" #include "../../dialogs/window.h" #include "../../dialogs/gd_mainWindow.h" #include "../../dialogs/browser/browserDir.h" #include "../basics/box.h" #include "../basics/radio.h" #include "../basics/check.h" #include "../basics/input.h" #include "../basics/button.h" #include "tabPlugins.h" extern gdMainWindow* G_MainWin; using std::string; using namespace giada::m; geTabPlugins::geTabPlugins(int X, int Y, int W, int H) : Fl_Group(X, Y, W, H, "Plugins") { m_browse = new geButton(x()+w()-G_GUI_UNIT, y()+9, G_GUI_UNIT, G_GUI_UNIT, "", zoomInOff_xpm, zoomInOn_xpm); m_folderPath = new geInput(m_browse->x()-258, y()+9, 250, G_GUI_UNIT); m_scanButton = new geButton(x()+w()-120, m_folderPath->y()+m_folderPath->h()+8, 120, G_GUI_UNIT); m_info = new geBox(x(), m_scanButton->y()+m_scanButton->h()+8, w(), 242); end(); labelsize(G_GUI_FONT_SIZE_BASE); m_info->label("Scan in progress. Please wait..."); m_info->hide(); m_folderPath->value(conf::pluginPath.c_str()); m_folderPath->label("Plugins folder"); m_browse->callback(cb_browse, (void*) this); m_scanButton->callback(cb_scan, (void*) this); refreshCount(); } /* -------------------------------------------------------------------------- */ void geTabPlugins::refreshCount() { string scanLabel = "Scan (" + gu_iToString(pluginHost::countAvailablePlugins()) + " found)"; m_scanButton->label(scanLabel.c_str()); } /* -------------------------------------------------------------------------- */ void geTabPlugins::cb_scan(Fl_Widget* w, void* p) { ((geTabPlugins*)p)->cb_scan(); } void geTabPlugins::cb_browse(Fl_Widget* w, void* p) { ((geTabPlugins*)p)->cb_browse(); } /* -------------------------------------------------------------------------- */ void geTabPlugins::cb_browse() { gdBrowserDir* browser = new gdBrowserDir(0, 0, 800, 600, "Add plug-ins directory", conf::patchPath, giada::c::plugin::setPluginPathCb); static_cast(top_window())->addSubWindow(browser); } /* -------------------------------------------------------------------------- */ void geTabPlugins::cb_scan() { std::function callback = [this] (float progress) { string l = "Scan in progress (" + gu_iToString((int)(progress*100)) + "%). Please wait..."; m_info->label(l.c_str()); Fl::wait(); }; m_info->show(); pluginHost::scanDirs(m_folderPath->value(), callback); pluginHost::saveList(gu_getHomePath() + G_SLASH + "plugins.xml"); m_info->hide(); refreshCount(); } /* -------------------------------------------------------------------------- */ void geTabPlugins::save() { conf::pluginPath = m_folderPath->value(); } /* -------------------------------------------------------------------------- */ void geTabPlugins::refreshVstPath() { m_folderPath->value(conf::pluginPath.c_str()); m_folderPath->redraw(); } #endif // WITH_VST giada-0.14.5/src/gui/elems/config/tabPlugins.h000066400000000000000000000032101322662744500210630ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef GE_TAB_PLUGINS_H #define GE_TAB_PLUGINS_H #ifdef WITH_VST #include class geInput; class geButton; class geBox; class geTabPlugins : public Fl_Group { private: geInput* m_folderPath; geButton* m_browse; geButton* m_scanButton; geBox* m_info; static void cb_scan(Fl_Widget* w, void* p); static void cb_browse(Fl_Widget* w, void* p); void cb_scan(); void cb_browse(); void refreshCount(); public: geTabPlugins(int x, int y, int w, int h); void save(); void refreshVstPath(); }; #endif #endif giada-0.14.5/src/gui/elems/mainWindow/000077500000000000000000000000001322662744500174555ustar00rootroot00000000000000giada-0.14.5/src/gui/elems/mainWindow/beatMeter.cpp000066400000000000000000000044551322662744500221010ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * beatMeter * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include #include "../../../core/const.h" #include "../../../core/mixer.h" #include "../../../core/clock.h" #include "beatMeter.h" using namespace giada::m; geBeatMeter::geBeatMeter(int x, int y, int w, int h, const char *L) : Fl_Box(x, y, w, h, L) {} /* -------------------------------------------------------------------------- */ void geBeatMeter::draw() { int cursorW = w() / G_MAX_BEATS; int greyX = clock::getBeats() * cursorW; fl_rect(x(), y(), w(), h(), G_COLOR_GREY_4); // border fl_rectf(x()+1, y()+1, w()-2, h()-2, FL_BACKGROUND_COLOR); // bg fl_rectf(x()+(clock::getCurrentBeat()*cursorW)+3, y()+3, cursorW-5, h()-6, G_COLOR_LIGHT_1); // cursor /* beat cells */ fl_color(G_COLOR_GREY_4); for (int i=1; i<=clock::getBeats(); i++) fl_line(x()+cursorW*i, y()+1, x()+cursorW*i, y()+h()-2); /* bar line */ fl_color(G_COLOR_LIGHT_1); int delta = clock::getBeats() / clock::getBars(); for (int i=1; i. * * -------------------------------------------------------------------------- */ #ifndef GE_BEAT_METER_H #define GE_BEAT_METER_H #include class geBeatMeter : public Fl_Box { public: geBeatMeter(int X,int Y,int W,int H,const char *L=0); void draw(); }; #endif giada-0.14.5/src/gui/elems/mainWindow/keyboard/000077500000000000000000000000001322662744500212555ustar00rootroot00000000000000giada-0.14.5/src/gui/elems/mainWindow/keyboard/channel.cpp000066400000000000000000000152541322662744500234000ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include #include "../../../../core/const.h" #include "../../../../core/channel.h" #include "../../../../core/graphics.h" #include "../../../../core/pluginHost.h" #include "../../../../utils/gui.h" #include "../../../../glue/channel.h" #include "../../../dialogs/gd_mainWindow.h" #include "../../../dialogs/pluginList.h" #include "../../basics/idButton.h" #include "../../basics/dial.h" #include "../../basics/statusButton.h" #include "column.h" #include "channelStatus.h" #include "channelButton.h" #include "channel.h" extern gdMainWindow* G_MainWin; using namespace giada::m; geChannel::geChannel(int X, int Y, int W, int H, int type, Channel* ch) : Fl_Group(X, Y, W, H, nullptr), ch (ch), type (type) { } /* -------------------------------------------------------------------------- */ void geChannel::cb_arm(Fl_Widget* v, void* p) { ((geChannel*)p)->cb_arm(); } void geChannel::cb_mute(Fl_Widget* v, void* p) { ((geChannel*)p)->cb_mute(); } void geChannel::cb_solo(Fl_Widget* v, void* p) { ((geChannel*)p)->cb_solo(); } void geChannel::cb_changeVol(Fl_Widget* v, void* p) { ((geChannel*)p)->cb_changeVol(); } #ifdef WITH_VST void geChannel::cb_openFxWindow(Fl_Widget* v, void* p) { ((geChannel*)p)->cb_openFxWindow(); } #endif /* -------------------------------------------------------------------------- */ void geChannel::cb_arm() { glue_toggleArm(ch, true); } /* -------------------------------------------------------------------------- */ void geChannel::cb_mute() { glue_toggleMute(ch); } /* -------------------------------------------------------------------------- */ void geChannel::cb_solo() { solo->value() ? glue_setSoloOn(ch) : glue_setSoloOff(ch); } /* -------------------------------------------------------------------------- */ void geChannel::cb_changeVol() { glue_setVolume(ch, vol->value()); } /* -------------------------------------------------------------------------- */ #ifdef WITH_VST void geChannel::cb_openFxWindow() { gu_openSubWindow(G_MainWin, new gdPluginList(pluginHost::CHANNEL, ch), WID_FX_LIST); } #endif /* -------------------------------------------------------------------------- */ int geChannel::keyPress(int e) { return handleKey(e, ch->key); } /* -------------------------------------------------------------------------- */ int geChannel::getColumnIndex() { return static_cast(parent())->getIndex(); } /* -------------------------------------------------------------------------- */ void geChannel::blink() { if (gu_getBlinker() > 6) mainButton->setPlayMode(); else mainButton->setDefaultMode(); } /* -------------------------------------------------------------------------- */ void geChannel::setColorsByStatus(int playStatus, int recStatus) { switch (playStatus) { case STATUS_OFF: case STATUS_EMPTY: mainButton->setDefaultMode(); button->imgOn = channelPlay_xpm; button->imgOff = channelStop_xpm; button->redraw(); break; case STATUS_PLAY: mainButton->setPlayMode(); button->imgOn = channelStop_xpm; button->imgOff = channelPlay_xpm; button->redraw(); break; case STATUS_WAIT: blink(); break; case STATUS_ENDING: mainButton->setEndingMode(); break; } switch (recStatus) { case REC_WAITING: blink(); break; case REC_ENDING: mainButton->setEndingMode(); break; } } /* -------------------------------------------------------------------------- */ void geChannel::packWidgets() { /* Count visible widgets and resize mainButton according to how many widgets are visible. */ int visibles = 0; for (int i=0; isize(MIN_ELEM_W, child(i)->h()); // also normalize widths if (child(i)->visible()) visibles++; } mainButton->size(w() - ((visibles - 1) * (MIN_ELEM_W + G_GUI_INNER_MARGIN)), // -1: exclude itself mainButton->h()); /* Reposition everything else */ for (int i=1, p=0; ivisible()) continue; for (int k=i-1; k>=0; k--) // Get the first visible item prior to i if (child(k)->visible()) { p = k; break; } child(i)->position(child(p)->x() + child(p)->w() + G_GUI_INNER_MARGIN, child(i)->y()); } init_sizes(); // Resets the internal array of widget sizes and positions } /* -------------------------------------------------------------------------- */ int geChannel::handleKey(int e, int key) { int ret; if (e == FL_KEYDOWN && button->value()) // key already pressed! skip it ret = 1; else if (Fl::event_key() == key && !button->value()) { button->take_focus(); // move focus to this button button->value((e == FL_KEYDOWN || e == FL_SHORTCUT) ? 1 : 0); // change the button's state button->do_callback(); // invoke the button's callback ret = 1; } else ret = 0; if (Fl::event_key() == key) button->value((e == FL_KEYDOWN || e == FL_SHORTCUT) ? 1 : 0); // change the button's state return ret; } /* -------------------------------------------------------------------------- */ void geChannel::changeSize(int H) { size(w(), H); int Y = y() + (H / 2 - (G_GUI_UNIT / 2)); button->resize(x(), Y, w(), G_GUI_UNIT); arm->resize(x(), Y, w(), G_GUI_UNIT); mainButton->resize(x(), y(), w(), H); mute->resize(x(), Y, w(), G_GUI_UNIT); solo->resize(x(), Y, w(), G_GUI_UNIT); vol->resize(x(), Y, w(), G_GUI_UNIT); #ifdef WITH_VST fx->resize(x(), Y, w(), G_GUI_UNIT); #endif } /* -------------------------------------------------------------------------- */ int geChannel::getSize() { return h(); } giada-0.14.5/src/gui/elems/mainWindow/keyboard/channel.h000066400000000000000000000071361322662744500230450ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef GE_CHANNEL_H #define GE_CHANNEL_H #include class Channel; class geIdButton; class geChannelStatus; class geButton; class geChannelButton; class geDial; #ifdef WITH_VST class geStatusButton; #endif class geChannel : public Fl_Group { protected: /* Define some breakpoints for dynamic resize. BREAK_DELTA: base amount of pixels to shrink sampleButton. */ #ifdef WITH_VST static const int BREAK_READ_ACTIONS = 240; static const int BREAK_MODE_BOX = 216; static const int BREAK_FX = 192; static const int BREAK_ARM = 168; #else static const int BREAK_READ_ACTIONS = 216; static const int BREAK_MODE_BOX = 192; static const int BREAK_ARM = 168; #endif static const int MIN_ELEM_W = 20; static void cb_arm(Fl_Widget* v, void* p); static void cb_mute(Fl_Widget* v, void* p); static void cb_solo(Fl_Widget* v, void* p); static void cb_changeVol(Fl_Widget* v, void* p); #ifdef WITH_VST static void cb_openFxWindow(Fl_Widget* v, void* p); #endif void cb_mute(); void cb_arm(); void cb_solo(); void cb_changeVol(); #ifdef WITH_VST void cb_openFxWindow(); #endif /* blink * blink button when channel is in wait/ending status. */ void blink(); /* setColorByStatus * update colors depending on channel status. */ void setColorsByStatus(int playStatus, int recStatus); /* handleKey * method wrapped by virtual handle(int e). */ int handleKey(int e, int key); /* packWidgets Spread widgets across available space. */ void packWidgets(); public: geChannel(int x, int y, int w, int h, int type, Channel* ch); /* reset * reset channel to initial status. */ virtual void reset() = 0; /* update * update the label of sample button and everything else such as 'R' * button, key box and so on, according to global values. */ virtual void update() = 0; /* refresh * update graphics. */ virtual void refresh() = 0; /* changeSize Changes channel's size according to a template (x1, x2, ...). */ virtual void changeSize(int h); /* keypress * what to do when the corresponding key is pressed. */ int keyPress(int event); /* getColumnIndex * return the numeric index of the column in which this channel is * located. */ int getColumnIndex(); int getSize(); Channel* ch; geIdButton* button; geChannelStatus* status; geButton* arm; geChannelButton* mainButton; geButton* mute; geButton* solo; geDial* vol; #ifdef WITH_VST geStatusButton* fx; #endif int type; }; #endif giada-0.14.5/src/gui/elems/mainWindow/keyboard/channelButton.cpp000066400000000000000000000060451322662744500245720ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include #include "../../../../core/const.h" #include "../../../../utils/string.h" #include "channelButton.h" using std::string; geChannelButton::geChannelButton(int x, int y, int w, int h, const char* l) : geButton(x, y, w, h, l), m_key ("") { } /* -------------------------------------------------------------------------- */ void geChannelButton::setKey(const string& k) { m_key = k; } /* -------------------------------------------------------------------------- */ void geChannelButton::setKey(int k) { if (k == 0) m_key = ""; else m_key = static_cast(k); // FIXME - What about unicode/utf-8? } /* -------------------------------------------------------------------------- */ void geChannelButton::draw() { geButton::draw(); if (m_key == "") return; /* draw background */ fl_rectf(x()+1, y()+1, 18, h()-2, bgColor0); /* draw m_key */ fl_color(G_COLOR_LIGHT_2); fl_font(FL_HELVETICA, 11); fl_draw(m_key.c_str(), x(), y(), 18, h(), FL_ALIGN_CENTER); } /* -------------------------------------------------------------------------- */ void geChannelButton::setInputRecordMode() { bgColor0 = G_COLOR_RED; } /* -------------------------------------------------------------------------- */ void geChannelButton::setActionRecordMode() { bgColor0 = G_COLOR_BLUE; txtColor = G_COLOR_LIGHT_2; } /* -------------------------------------------------------------------------- */ void geChannelButton::setDefaultMode(const char* l) { bgColor0 = G_COLOR_GREY_2; bdColor = G_COLOR_GREY_4; txtColor = G_COLOR_LIGHT_2; if (l) label(l); } /* -------------------------------------------------------------------------- */ void geChannelButton::setPlayMode() { bgColor0 = G_COLOR_LIGHT_1; bdColor = G_COLOR_LIGHT_1; txtColor = G_COLOR_GREY_1; } /* -------------------------------------------------------------------------- */ void geChannelButton::setEndingMode() { bgColor0 = G_COLOR_GREY_4; } giada-0.14.5/src/gui/elems/mainWindow/keyboard/channelButton.h000066400000000000000000000031031322662744500242270ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef GE_CHANNEL_BUTTON_H #define GE_CHANNEL_BUTTON_H #include "../../basics/button.h" class geChannelButton : public geButton { private: std::string m_key; public: geChannelButton(int x, int y, int w, int h, const char* l=0); void draw() override; void setKey(const std::string& k); void setKey(int k); void setPlayMode(); void setEndingMode(); void setDefaultMode(const char* l=0); void setInputRecordMode(); void setActionRecordMode(); }; #endif giada-0.14.5/src/gui/elems/mainWindow/keyboard/channelMode.cpp000066400000000000000000000070711322662744500242030ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ge_modeBox * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include #include "../../../../utils/gui.h" #include "../../../../core/graphics.h" #include "../../../../core/sampleChannel.h" #include "../../../../core/const.h" #include "../../basics/boxtypes.h" #include "channelMode.h" geChannelMode::geChannelMode(int x, int y, int w, int h, SampleChannel *ch, const char *L) : Fl_Menu_Button(x, y, w, h, L), ch(ch) { box(G_CUSTOM_BORDER_BOX); textsize(G_GUI_FONT_SIZE_BASE); textcolor(G_COLOR_LIGHT_2); color(G_COLOR_GREY_2); add("Loop . basic", 0, cb_changeMode, (void *)LOOP_BASIC); add("Loop . once", 0, cb_changeMode, (void *)LOOP_ONCE); add("Loop . once . bar", 0, cb_changeMode, (void *)LOOP_ONCE_BAR); add("Loop . repeat", 0, cb_changeMode, (void *)LOOP_REPEAT); add("Oneshot . basic", 0, cb_changeMode, (void *)SINGLE_BASIC); add("Oneshot . press", 0, cb_changeMode, (void *)SINGLE_PRESS); add("Oneshot . retrig", 0, cb_changeMode, (void *)SINGLE_RETRIG); add("Oneshot . endless", 0, cb_changeMode, (void *)SINGLE_ENDLESS); } /* -------------------------------------------------------------------------- */ void geChannelMode::draw() { fl_rect(x(), y(), w(), h(), G_COLOR_GREY_4); // border switch (ch->mode) { case LOOP_BASIC: fl_draw_pixmap(loopBasic_xpm, x()+1, y()+1); break; case LOOP_ONCE: fl_draw_pixmap(loopOnce_xpm, x()+1, y()+1); break; case LOOP_ONCE_BAR: fl_draw_pixmap(loopOnceBar_xpm, x()+1, y()+1); break; case LOOP_REPEAT: fl_draw_pixmap(loopRepeat_xpm, x()+1, y()+1); break; case SINGLE_BASIC: fl_draw_pixmap(oneshotBasic_xpm, x()+1, y()+1); break; case SINGLE_PRESS: fl_draw_pixmap(oneshotPress_xpm, x()+1, y()+1); break; case SINGLE_RETRIG: fl_draw_pixmap(oneshotRetrig_xpm, x()+1, y()+1); break; case SINGLE_ENDLESS: fl_draw_pixmap(oneshotEndless_xpm, x()+1, y()+1); break; } } /* -------------------------------------------------------------------------- */ void geChannelMode::cb_changeMode(Fl_Widget *v, void *p) { ((geChannelMode*)v)->__cb_changeMode((intptr_t)p); } /* -------------------------------------------------------------------------- */ void geChannelMode::__cb_changeMode(int mode) { ch->mode = mode; /* what to do when the channel is playing and you change the mode? * Nothing, since v0.5.3. Just refresh the action editor window, in * case it's open */ gu_refreshActionEditor(); } giada-0.14.5/src/gui/elems/mainWindow/keyboard/channelMode.h000066400000000000000000000030001322662744500236340ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ge_modeBox * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef GE_CHANNEL_MODE_H #define GE_CHANNEL_MODE_H #include class geChannelMode : public Fl_Menu_Button { private: static void cb_changeMode (Fl_Widget *v, void *p); inline void __cb_changeMode(int mode); class SampleChannel *ch; public: geChannelMode(int x, int y, int w, int h, class SampleChannel *ch, const char *l=0); void draw(); }; #endif giada-0.14.5/src/gui/elems/mainWindow/keyboard/channelStatus.cpp000066400000000000000000000054171322662744500246040ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include #include "../../../../core/mixer.h" #include "../../../../core/clock.h" #include "../../../../core/sampleChannel.h" #include "../../../../core/recorder.h" #include "../../../../core/const.h" #include "channelStatus.h" using namespace giada::m; geChannelStatus::geChannelStatus(int x, int y, int w, int h, SampleChannel *ch, const char *L) : Fl_Box(x, y, w, h, L), ch(ch) {} /* -------------------------------------------------------------------------- */ void geChannelStatus::draw() { fl_rect(x(), y(), w(), h(), G_COLOR_GREY_4); // reset border fl_rectf(x()+1, y()+1, w()-2, h()-2, G_COLOR_GREY_2); // reset background if (ch != nullptr) { if (ch->status & (STATUS_WAIT | STATUS_ENDING | REC_ENDING | REC_WAITING) || ch->recStatus & (REC_WAITING | REC_ENDING)) { fl_rect(x(), y(), w(), h(), G_COLOR_LIGHT_1); } else if (ch->status == STATUS_PLAY) fl_rect(x(), y(), w(), h(), G_COLOR_LIGHT_1); else fl_rectf(x()+1, y()+1, w()-2, h()-2, G_COLOR_GREY_2); // status empty if (mixer::recording && ch->isArmed()) fl_rectf(x()+1, y()+1, w()-2, h()-2, G_COLOR_RED); // take in progress else if (recorder::active && recorder::canRec(ch, clock::isRunning(), mixer::recording)) fl_rectf(x()+1, y()+1, w()-2, h()-2, G_COLOR_BLUE); // action record /* equation for the progress bar: * ((chanTracker - chanStart) * w()) / (chanEnd - chanStart). */ int pos = ch->getPosition(); if (pos == -1) pos = 0; else pos = (pos * (w()-1)) / ((ch->getEnd() - ch->getBegin())); fl_rectf(x()+1, y()+1, pos, h()-2, G_COLOR_LIGHT_1); } } giada-0.14.5/src/gui/elems/mainWindow/keyboard/channelStatus.h000066400000000000000000000026141322662744500242450ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ge_status * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef GE_CHANNEL_STATUS_H #define GE_CHANNEL_STATUS_H #include class geChannelStatus : public Fl_Box { public: geChannelStatus(int X, int Y, int W, int H, class SampleChannel *ch, const char *L=0); void draw(); class SampleChannel *ch; }; #endif giada-0.14.5/src/gui/elems/mainWindow/keyboard/column.cpp000066400000000000000000000206771322662744500232720ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include #include #include #include "../../../../core/sampleChannel.h" #include "../../../../core/midiChannel.h" #include "../../../../glue/channel.h" #include "../../../../utils/log.h" #include "../../../../utils/fs.h" #include "../../../../utils/string.h" #include "../../../dialogs/gd_warnings.h" #include "../../../elems/basics/boxtypes.h" #include "../../../elems/basics/resizerBar.h" #include "keyboard.h" #include "sampleChannel.h" #include "midiChannel.h" #include "column.h" using std::vector; using std::string; geColumn::geColumn(int X, int Y, int W, int H, int index, geKeyboard* parent) : Fl_Group(X, Y, W, H), m_parent(parent), m_index (index) { /* geColumn does a bit of a mess: we pass a pointer to its m_parent (geKeyboard) and the geColumn itself deals with the creation of another widget, outside geColumn and inside geKeyboard, which handles the vertical resize bar (geResizerBar). The resizer cannot stay inside geColumn: it needs a broader view on the other side widgets. The view can be obtained from geKeyboard only (the upper level). Unfortunately, parent() can be nullptr: at this point (i.e the constructor) geColumn is still detached from any parent. We use a custom geKeyboard *parent instead. */ begin(); m_addChannelBtn = new geButton(x(), y(), w(), G_GUI_UNIT, "Add new channel"); end(); m_resizer = new geResizerBar(x()+w(), y(), G_GUI_OUTER_MARGIN * 2, h(), G_MIN_COLUMN_WIDTH, geResizerBar::HORIZONTAL); m_parent->add(m_resizer); m_addChannelBtn->callback(cb_addChannel, (void*)this); } /* -------------------------------------------------------------------------- */ geColumn::~geColumn() { /* FIXME - this could actually cause a memory leak. m_resizer is just removed, not deleted. But we cannot delete it right now. */ m_parent->remove(m_resizer); } /* -------------------------------------------------------------------------- */ int geColumn::handle(int e) { switch (e) { case FL_RELEASE: { if (Fl::event_button() == FL_RIGHT_MOUSE) { __cb_addChannel(); return 1; } } case FL_DND_ENTER: // return(1) for these events to 'accept' dnd case FL_DND_DRAG: case FL_DND_RELEASE: { return 1; } case FL_PASTE: { // handle actual drop (paste) operation vector paths; gu_split(Fl::event_text(), "\n", &paths); bool fails = false; int result = 0; for (string& path : paths) { gu_log("[geColumn::handle] loading %s...\n", path.c_str()); SampleChannel* c = static_cast(glue_addChannel( m_index, CHANNEL_SAMPLE, G_GUI_CHANNEL_H_1)); result = glue_loadChannel(c, gu_stripFileUrl(path)); if (result != G_RES_OK) { deleteChannel(c->guiChannel); fails = true; } } if (fails) { if (paths.size() > 1) gdAlert("Some files were not loaded successfully."); else m_parent->printChannelMessage(result); } return 1; } } /* we return fl_Group::handle only if none of the cases above are fired. That is because we don't want to propagate a dnd drop to all the sub widgets. */ return Fl_Group::handle(e); } /* -------------------------------------------------------------------------- */ void geColumn::resize(int X, int Y, int W, int H) { /* Resize all children, including "add channel" button. */ for (int i=0; iresize(X, (wgPrev == nullptr ? Y : wgPrev->y() + wgPrev->h() + G_GUI_INNER_MARGIN), W, wgCurr->h()); } /* Resize group itself. Must use internal functions, resize() would trigger infinite recursion. */ x(X); y(Y); w(W); h(H); /* Resize resizerBar. */ m_resizer->size(G_GUI_OUTER_MARGIN * 2, H); } /* -------------------------------------------------------------------------- */ void geColumn::refreshChannels() { for (int i=1; i(child(i))->refresh(); } /* -------------------------------------------------------------------------- */ void geColumn::draw() { fl_color(G_COLOR_GREY_1_5); fl_rectf(x(), y(), w(), h()); /* call draw and then redraw in order to avoid channel corruption when scrolling horizontally */ for (int i=0; idraw(); child(i)->redraw(); } } /* -------------------------------------------------------------------------- */ void geColumn::cb_addChannel(Fl_Widget* v, void* p) { ((geColumn*)p)->__cb_addChannel(); } /* -------------------------------------------------------------------------- */ void geColumn::repositionChannels() { int totalH = 0; for (int i=0; ih() + G_GUI_INNER_MARGIN; resize(x(), y(), w(), totalH + 66); // evil space for drag n drop } /* -------------------------------------------------------------------------- */ geChannel* geColumn::addChannel(Channel* ch, int size) { geChannel* gch = nullptr; /* All geChannels are added with y=0. That's not a problem, they will be repositioned later on during geColumn::resize(). */ if (ch->type == CHANNEL_SAMPLE) gch = new geSampleChannel(x(), 0, w(), size, static_cast(ch)); else gch = new geMidiChannel(x(), 0, w(), size, static_cast(ch)); add(gch); repositionChannels(); gch->redraw(); // fix corruption m_parent->redraw(); // redraw Keyboard return gch; } /* -------------------------------------------------------------------------- */ void geColumn::deleteChannel(geChannel* gch) { gch->hide(); remove(gch); delete gch; /** TODO * reposition is useless when called by geColumn::clear(). Add a new * parameter to skip the operation */ repositionChannels(); } /* -------------------------------------------------------------------------- */ void geColumn::__cb_addChannel() { gu_log("[geColumn::__cb_addChannel] m_index = %d\n", m_index); int type = openTypeMenu(); if (type) glue_addChannel(m_index, type, G_GUI_CHANNEL_H_1); } /* -------------------------------------------------------------------------- */ int geColumn::openTypeMenu() { Fl_Menu_Item rclick_menu[] = { {"Sample channel"}, {"MIDI channel"}, {0} }; Fl_Menu_Button* b = new Fl_Menu_Button(0, 0, 100, 50); b->box(G_CUSTOM_BORDER_BOX); b->textsize(G_GUI_FONT_SIZE_BASE); b->textcolor(G_COLOR_LIGHT_2); b->color(G_COLOR_GREY_2); const Fl_Menu_Item *m = rclick_menu->popup(Fl::event_x(), Fl::event_y(), 0, 0, b); if (!m) return 0; if (strcmp(m->label(), "Sample channel") == 0) return CHANNEL_SAMPLE; if (strcmp(m->label(), "MIDI channel") == 0) return CHANNEL_MIDI; return 0; } /* -------------------------------------------------------------------------- */ void geColumn::clear(bool full) { if (full) Fl_Group::clear(); else { while (children() >= 2) { // skip "add new channel" btn int i = children()-1; deleteChannel(static_cast(child(i))); } } } /* -------------------------------------------------------------------------- */ Channel* geColumn::getChannel(int i) { return static_cast(child(i + 1))->ch; // Skip "add channel" } /* -------------------------------------------------------------------------- */ int geColumn::getIndex() { return m_index; } void geColumn::setIndex(int i) { m_index = i; } bool geColumn::isEmpty() { return children() == 1; } int geColumn::countChannels() { return children() - 1; }giada-0.14.5/src/gui/elems/mainWindow/keyboard/column.h000066400000000000000000000045451322662744500227330ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef GE_COLUMN_H #define GE_COLUMN_H #include class Channel; class geButton; class geChannel; class geResizerBar; class geKeyboard; class geColumn : public Fl_Group { private: static void cb_addChannel (Fl_Widget* v, void* p); inline void __cb_addChannel(); int openTypeMenu(); geButton* m_addChannelBtn; geResizerBar* m_resizer; geKeyboard* m_parent; int m_index; public: geColumn(int x, int y, int w, int h, int index, geKeyboard* parent); ~geColumn(); /* addChannel Adds a new channel in this column and set the internal pointer to channel to 'ch'. */ geChannel* addChannel(Channel* ch, int size); int handle(int e) override; void draw() override; void resize(int x, int y, int w, int h) override; /* clear Removes all channels from the column. If full==true, delete also the "add new channel" button. */ void clear(bool full=false); /* deleteChannel Removes the channel 'gch' from this column. */ void deleteChannel(geChannel* gch); void repositionChannels(); /* refreshChannels Updates channels' graphical statues. Called on each GUI cycle. */ void refreshChannels(); Channel* getChannel(int i); int getIndex(); void setIndex(int i); bool isEmpty(); int countChannels(); }; #endif giada-0.14.5/src/gui/elems/mainWindow/keyboard/keyboard.cpp000066400000000000000000000234471322662744500235730ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include "../../../../core/sampleChannel.h" #include "../../../../glue/transport.h" #include "../../../../glue/io.h" #include "../../../../utils/log.h" #include "../../../dialogs/gd_warnings.h" #include "../../basics/boxtypes.h" #include "column.h" #include "sampleChannel.h" #include "channelButton.h" #include "keyboard.h" int geKeyboard::indexColumn = 0; /* -------------------------------------------------------------------------- */ geKeyboard::geKeyboard(int X, int Y, int W, int H) : Fl_Scroll (X, Y, W, H), bckspcPressed(false), endPressed (false), spacePressed (false), addColumnBtn (nullptr) { color(G_COLOR_GREY_1); type(Fl_Scroll::BOTH_ALWAYS); scrollbar.color(G_COLOR_GREY_2); scrollbar.selection_color(G_COLOR_GREY_4); scrollbar.labelcolor(G_COLOR_LIGHT_1); scrollbar.slider(G_CUSTOM_BORDER_BOX); hscrollbar.color(G_COLOR_GREY_2); hscrollbar.selection_color(G_COLOR_GREY_4); hscrollbar.labelcolor(G_COLOR_LIGHT_1); hscrollbar.slider(G_CUSTOM_BORDER_BOX); addColumnBtn = new geButton(8, y(), 200, 20, "Add new column"); addColumnBtn->callback(cb_addColumn, (void*) this); add(addColumnBtn); init(); } /* -------------------------------------------------------------------------- */ void geKeyboard::init() { /* add 6 empty columns as init layout */ __cb_addColumn(); __cb_addColumn(); __cb_addColumn(); __cb_addColumn(); __cb_addColumn(); __cb_addColumn(); } /* -------------------------------------------------------------------------- */ void geKeyboard::freeChannel(geChannel* gch) { gch->reset(); } /* -------------------------------------------------------------------------- */ void geKeyboard::deleteChannel(geChannel* gch) { for (unsigned i=0; ifind(gch); if (k != columns.at(i)->children()) { columns.at(i)->deleteChannel(gch); return; } } } /* -------------------------------------------------------------------------- */ void geKeyboard::updateChannel(geChannel* gch) { gch->update(); } /* -------------------------------------------------------------------------- */ void geKeyboard::organizeColumns() { if (columns.size() == 0) return; /* Otherwise delete all empty columns. */ for (size_t i=columns.size(); i-- > 0;) { if (columns.at(i)->isEmpty()) { Fl::delete_widget(columns.at(i)); columns.erase(columns.begin() + i); } } /* Zero columns? Just add the "add column" button. Compact column and avoid empty spaces otherwise. */ if (columns.size() == 0) addColumnBtn->position(x() - xposition(), y()); else { for (size_t i=0; ix() + columns.at(i-1)->w() + COLUMN_GAP; columns.at(i)->position(pos, y()); } addColumnBtn->position(columns.back()->x() + columns.back()->w() + COLUMN_GAP, y()); } refreshColIndexes(); redraw(); } /* -------------------------------------------------------------------------- */ void geKeyboard::cb_addColumn(Fl_Widget* v, void* p) { ((geKeyboard*)p)->__cb_addColumn(G_DEFAULT_COLUMN_WIDTH); } /* -------------------------------------------------------------------------- */ geChannel* geKeyboard::addChannel(int colIndex, Channel* ch, int size, bool build) { geColumn* col = getColumnByIndex(colIndex); /* no column with index 'colIndex' found? Just create it and set its index to 'colIndex'. */ if (!col) { __cb_addColumn(); col = columns.back(); col->setIndex(colIndex); gu_log("[geKeyboard::addChannel] created new column with index=%d\n", colIndex); } gu_log("[geKeyboard::addChannel] add to column with index=%d, size=%d\n", col->getIndex(), size); return col->addChannel(ch, size); } /* -------------------------------------------------------------------------- */ void geKeyboard::refreshColumns() { for (unsigned i=0; irefreshChannels(); } /* -------------------------------------------------------------------------- */ geColumn* geKeyboard::getColumnByIndex(int index) { for (unsigned i=0; igetIndex() == index) return columns.at(i); return nullptr; } /* -------------------------------------------------------------------------- */ /* TODO - the following event handling for play, stop, rewind, start rec and so on should be moved to the proper widget: gdMainWindow or (better) geController. */ int geKeyboard::handle(int e) { int ret = Fl_Group::handle(e); // assume the buttons won't handle the Keyboard events switch (e) { case FL_FOCUS: case FL_UNFOCUS: { ret = 1; // enables receiving Keyboard events break; } case FL_SHORTCUT: // in case widget that isn't ours has focus case FL_KEYDOWN: // Keyboard key pushed case FL_KEYUP: { // Keyboard key released /* rewind session. Avoid retrigs */ if (e == FL_KEYDOWN) { if (Fl::event_key() == FL_BackSpace && !bckspcPressed) { bckspcPressed = true; glue_rewindSeq(false); // not from GUI ret = 1; break; } else if (Fl::event_key() == FL_End && !endPressed) { endPressed = true; glue_startStopInputRec(false); // not from GUI ret = 1; break; } else if (Fl::event_key() == FL_Enter && !enterPressed) { enterPressed = true; glue_startStopActionRec(false); // not from GUI ret = 1; break; } else if (Fl::event_key() == ' ' && !spacePressed) { spacePressed = true; glue_startStopSeq(false); // unot from GUI ret = 1; break; } } else if (e == FL_KEYUP) { if (Fl::event_key() == FL_BackSpace) bckspcPressed = false; else if (Fl::event_key() == FL_End) endPressed = false; else if (Fl::event_key() == ' ') spacePressed = false; else if (Fl::event_key() == FL_Enter) enterPressed = false; } /* Walk button arrays, trying to match button's label with the Keyboard event. * If found, set that button's value() based on up/down event, * and invoke that button's callback() */ for (unsigned i=0; ichildren(); k++) ret &= static_cast(columns.at(i)->child(k))->keyPress(e); break; } } return ret; } /* -------------------------------------------------------------------------- */ void geKeyboard::clear() { for (unsigned i=0; iposition(8, y()); } /* -------------------------------------------------------------------------- */ void geKeyboard::setChannelWithActions(geSampleChannel* gch) { if (gch->ch->hasActions) gch->showActionButton(); else gch->hideActionButton(); } /* -------------------------------------------------------------------------- */ void geKeyboard::printChannelMessage(int res) { if (res == G_RES_ERR_WRONG_DATA) gdAlert("Multichannel samples not supported."); else if (res == G_RES_ERR_IO) gdAlert("Unable to read this sample."); else if (res == G_RES_ERR_PATH_TOO_LONG) gdAlert("File path too long."); else if (res == G_RES_ERR_NO_DATA) gdAlert("No file specified."); else gdAlert("Unknown error."); } /* -------------------------------------------------------------------------- */ void geKeyboard::__cb_addColumn(int width) { int colx; int colxw; if (columns.size() == 0) { colx = x() - xposition(); // mind the offset with xposition() colxw = colx + width; } else { geColumn* prev = columns.back(); colx = prev->x()+prev->w() + COLUMN_GAP; colxw = colx + width; } /* add geColumn to geKeyboard and to columns vector */ geColumn* gc = new geColumn(colx, y(), width, 2000, indexColumn, this); add(gc); columns.push_back(gc); indexColumn++; /* move addColumn button */ addColumnBtn->position(colxw + COLUMN_GAP, y()); redraw(); gu_log("[geKeyboard::__cb_addColumn] new column added (index=%d, w=%d), total count=%d, addColumn(x)=%d\n", gc->getIndex(), width, columns.size(), addColumnBtn->x()); /* recompute col indexes */ refreshColIndexes(); } /* -------------------------------------------------------------------------- */ void geKeyboard::addColumn(int width) { __cb_addColumn(width); } /* -------------------------------------------------------------------------- */ void geKeyboard::refreshColIndexes() { for (unsigned i=0; isetIndex(i); } /* -------------------------------------------------------------------------- */ int geKeyboard::getColumnWidth(int i) { return getColumnByIndex(i)->w(); } /* -------------------------------------------------------------------------- */ geColumn* geKeyboard::getColumn(int i) { return columns.at(i); } giada-0.14.5/src/gui/elems/mainWindow/keyboard/keyboard.h000066400000000000000000000077431322662744500232410ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef GE_KEYBOARD_H #define GE_KEYBOARD_H #include #include #include "../../../../core/const.h" class Channel; class geButton; class geColumn; class geChannel; class geSampleChannel; class geKeyboard : public Fl_Scroll { private: static const int COLUMN_GAP = 16; /* refreshColIndexes * Recompute all column indexes in order to avoid any gaps between them. * Indexes must always be contiguous! */ void refreshColIndexes(); static void cb_addColumn (Fl_Widget* v, void* p); inline void __cb_addColumn(int width=G_DEFAULT_COLUMN_WIDTH); bool bckspcPressed; bool endPressed; bool spacePressed; bool enterPressed; /* indexColumn * the last index used for column. */ static int indexColumn; geButton* addColumnBtn; /* columns * a vector of columns which in turn contain channels. */ std::vector columns; public: geKeyboard(int X, int Y, int W, int H); int handle(int e); /* init * build the initial setup of empty channels. */ void init(); /* addChannel Adds a new channel to geChannels. Used by callbacks and during patch loading. Requires Channel (and not geChannel). If build is set to true, also generate the corresponding column if column (index) does not exist yet. */ geChannel* addChannel(int column, Channel* ch, int size, bool build=false); /* addColumn * add a new column to the top of the stack. */ void addColumn(int width=380); /* deleteChannel * delete a channel from geChannels<> where geChannel->ch == ch and remove * it from the stack. */ void deleteChannel(geChannel* gch); /* freeChannel * free a channel from geChannels<> where geChannel->ch == ch. No channels * are deleted */ void freeChannel(geChannel* gch); /* updateChannel * wrapper function to call gch->update(). */ void updateChannel(geChannel* gch); /* organizeColumns * reorganize columns layout by removing empty gaps. */ void organizeColumns(); /* refreshColumns * refresh each column's channel, called on each GUI cycle. */ void refreshColumns(); /* getColumnByIndex * return the column with index 'index', or nullptr if not found. */ geColumn* getColumnByIndex(int index); /* getColumn * return the column with from columns->at(i). */ geColumn* getColumn(int i); /* clear * delete all channels and groups. */ void clear(); /* setChannelWithActions * add 'R' button if channel has actions, and set recorder to active. */ void setChannelWithActions(geSampleChannel* gch); /* printChannelMessage * given any output by glue_loadChannel, print the message on screen * on a gdAlert subwindow. */ void printChannelMessage(int res); /* getTotalColumns */ unsigned getTotalColumns() { return columns.size(); } /* getColumnWidth * return the width in pixel of i-th column. Warning: 'i' is the i-th column * in the column array, NOT the index. */ int getColumnWidth(int i); }; #endif giada-0.14.5/src/gui/elems/mainWindow/keyboard/midiChannel.cpp000066400000000000000000000224231322662744500241770ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) G_GUI_UNIT10-G_GUI_UNIT17 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include #include "../../../../core/const.h" #include "../../../../core/graphics.h" #include "../../../../core/midiChannel.h" #include "../../../../utils/gui.h" #include "../../../../utils/string.h" #include "../../../../glue/channel.h" #include "../../../../glue/io.h" #include "../../../../glue/recorder.h" #include "../../../dialogs/gd_mainWindow.h" #include "../../../dialogs/channelNameInput.h" #include "../../../dialogs/gd_actionEditor.h" #include "../../../dialogs/gd_warnings.h" #include "../../../dialogs/gd_keyGrabber.h" #include "../../../dialogs/pluginList.h" #include "../../../dialogs/midiIO/midiInputChannel.h" #include "../../../dialogs/midiIO/midiOutputMidiCh.h" #include "../../basics/boxtypes.h" #include "../../basics/idButton.h" #include "../../basics/statusButton.h" #include "../../basics/dial.h" #include "column.h" #include "midiChannelButton.h" #include "midiChannel.h" extern gdMainWindow* G_MainWin; using std::string; using namespace giada; namespace { enum class Menu { EDIT_ACTIONS = 0, CLEAR_ACTIONS, CLEAR_ACTIONS_ALL, __END_CLEAR_ACTION_SUBMENU__, SETUP_KEYBOARD_INPUT, SETUP_MIDI_INPUT, SETUP_MIDI_OUTPUT, RESIZE, RESIZE_H1, RESIZE_H2, RESIZE_H3, RESIZE_H4, __END_RESIZE_SUBMENU__, RENAME_CHANNEL, CLONE_CHANNEL, DELETE_CHANNEL }; /* -------------------------------------------------------------------------- */ void menuCallback(Fl_Widget* w, void* v) { geMidiChannel* gch = static_cast(w); Menu selectedItem = (Menu) (intptr_t) v; switch (selectedItem) { case Menu::CLEAR_ACTIONS: case Menu::__END_CLEAR_ACTION_SUBMENU__: case Menu::RESIZE: case Menu::__END_RESIZE_SUBMENU__: break; case Menu::EDIT_ACTIONS: gu_openSubWindow(G_MainWin, new gdActionEditor(gch->ch), WID_ACTION_EDITOR); break; case Menu::CLEAR_ACTIONS_ALL: c::recorder::clearAllActions(gch); break; case Menu::SETUP_KEYBOARD_INPUT: gu_openSubWindow(G_MainWin, new gdKeyGrabber(gch->ch), 0); break; case Menu::SETUP_MIDI_INPUT: gu_openSubWindow(G_MainWin, new gdMidiInputChannel(gch->ch), 0); break; case Menu::SETUP_MIDI_OUTPUT: gu_openSubWindow(G_MainWin, new gdMidiOutputMidiCh(static_cast(gch->ch)), 0); break; case Menu::RESIZE_H1: gch->changeSize(G_GUI_CHANNEL_H_1); static_cast(gch->parent())->repositionChannels(); break; case Menu::RESIZE_H2: gch->changeSize(G_GUI_CHANNEL_H_2); static_cast(gch->parent())->repositionChannels(); break; case Menu::RESIZE_H3: gch->changeSize(G_GUI_CHANNEL_H_3); static_cast(gch->parent())->repositionChannels(); break; case Menu::RESIZE_H4: gch->changeSize(G_GUI_CHANNEL_H_4); static_cast(gch->parent())->repositionChannels(); break; case Menu::CLONE_CHANNEL: glue_cloneChannel(gch->ch); break; case Menu::RENAME_CHANNEL: gu_openSubWindow(G_MainWin, new gdChannelNameInput(gch->ch), WID_SAMPLE_NAME); break; case Menu::DELETE_CHANNEL: glue_deleteChannel(gch->ch); break; } } }; // {namespace} /* -------------------------------------------------------------------------- */ geMidiChannel::geMidiChannel(int X, int Y, int W, int H, MidiChannel* ch) : geChannel(X, Y, W, H, CHANNEL_MIDI, ch) { begin(); #if defined(WITH_VST) int delta = 144; // (6 widgets * G_GUI_UNIT) + (6 paddings * 4) #else int delta = 120; // (5 widgets * G_GUI_UNIT) + (5 paddings * 4) #endif button = new geIdButton(x(), y(), G_GUI_UNIT, G_GUI_UNIT, "", channelStop_xpm, channelPlay_xpm); arm = new geButton(button->x()+button->w()+4, y(), G_GUI_UNIT, G_GUI_UNIT, "", armOff_xpm, armOn_xpm); mainButton = new geMidiChannelButton(arm->x()+arm->w()+4, y(), w() - delta, H, "-- MIDI --"); mute = new geButton(mainButton->x()+mainButton->w()+4, y(), G_GUI_UNIT, G_GUI_UNIT, "", muteOff_xpm, muteOn_xpm); solo = new geButton(mute->x()+mute->w()+4, y(), G_GUI_UNIT, G_GUI_UNIT, "", soloOff_xpm, soloOn_xpm); #if defined(WITH_VST) fx = new geStatusButton(solo->x()+solo->w()+4, y(), G_GUI_UNIT, G_GUI_UNIT, fxOff_xpm, fxOn_xpm); vol = new geDial(fx->x()+fx->w()+4, y(), G_GUI_UNIT, G_GUI_UNIT); #else vol = new geDial(solo->x()+solo->w()+4, y(), G_GUI_UNIT, G_GUI_UNIT); #endif end(); resizable(mainButton); update(); button->callback(cb_button, (void*)this); button->when(FL_WHEN_CHANGED); // do callback on keypress && on keyrelease arm->type(FL_TOGGLE_BUTTON); arm->callback(cb_arm, (void*)this); #ifdef WITH_VST fx->callback(cb_openFxWindow, (void*)this); #endif mute->type(FL_TOGGLE_BUTTON); mute->callback(cb_mute, (void*)this); solo->type(FL_TOGGLE_BUTTON); solo->callback(cb_solo, (void*)this); mainButton->callback(cb_openMenu, (void*)this); vol->callback(cb_changeVol, (void*)this); ch->guiChannel = this; changeSize(H); // Update size dynamically } /* -------------------------------------------------------------------------- */ void geMidiChannel::cb_button (Fl_Widget* v, void* p) { ((geMidiChannel*)p)->cb_button(); } void geMidiChannel::cb_openMenu(Fl_Widget* v, void* p) { ((geMidiChannel*)p)->cb_openMenu(); } /* -------------------------------------------------------------------------- */ void geMidiChannel::cb_button() { if (button->value()) glue_keyPress(ch, Fl::event_ctrl(), Fl::event_shift()); } /* -------------------------------------------------------------------------- */ void geMidiChannel::cb_openMenu() { Fl_Menu_Item rclick_menu[] = { {"Edit actions...", 0, menuCallback, (void*) Menu::EDIT_ACTIONS}, {"Clear actions", 0, menuCallback, (void*) Menu::CLEAR_ACTIONS, FL_SUBMENU}, {"All", 0, menuCallback, (void*) Menu::CLEAR_ACTIONS_ALL}, {0}, {"Setup keyboard input...", 0, menuCallback, (void*) Menu::SETUP_KEYBOARD_INPUT}, {"Setup MIDI input...", 0, menuCallback, (void*) Menu::SETUP_MIDI_INPUT}, {"Setup MIDI output...", 0, menuCallback, (void*) Menu::SETUP_MIDI_OUTPUT}, {"Resize", 0, menuCallback, (void*) Menu::RESIZE, FL_SUBMENU}, {"Normal", 0, menuCallback, (void*) Menu::RESIZE_H1}, {"Medium", 0, menuCallback, (void*) Menu::RESIZE_H2}, {"Large", 0, menuCallback, (void*) Menu::RESIZE_H3}, {"X-Large", 0, menuCallback, (void*) Menu::RESIZE_H4}, {0}, {"Rename channel", 0, menuCallback, (void*) Menu::RENAME_CHANNEL}, {"Clone channel", 0, menuCallback, (void*) Menu::CLONE_CHANNEL}, {"Delete channel", 0, menuCallback, (void*) Menu::DELETE_CHANNEL}, {0} }; /* No 'clear actions' if there are no actions. */ if (!ch->hasActions) rclick_menu[(int)Menu::CLEAR_ACTIONS].deactivate(); Fl_Menu_Button* b = new Fl_Menu_Button(0, 0, 100, 50); b->box(G_CUSTOM_BORDER_BOX); b->textsize(G_GUI_FONT_SIZE_BASE); b->textcolor(G_COLOR_LIGHT_2); b->color(G_COLOR_GREY_2); const Fl_Menu_Item* m = rclick_menu->popup(Fl::event_x(), Fl::event_y(), 0, 0, b); if (m) m->do_callback(this, m->user_data()); return; } /* -------------------------------------------------------------------------- */ void geMidiChannel::refresh() { setColorsByStatus(ch->status, ch->recStatus); mainButton->redraw(); } /* -------------------------------------------------------------------------- */ void geMidiChannel::reset() { mainButton->setDefaultMode("-- MIDI --"); mainButton->redraw(); } /* -------------------------------------------------------------------------- */ void geMidiChannel::update() { const MidiChannel* mch = static_cast(ch); string label; if (mch->getName().empty()) label = "-- MIDI --"; else label = mch->getName().c_str(); if (mch->midiOut) label += " (ch " + gu_iToString(mch->midiOutChan + 1) + " out)"; mainButton->label(label.c_str()); vol->value(mch->volume); mute->value(mch->mute); solo->value(mch->solo); mainButton->setKey(mch->key); arm->value(mch->isArmed()); #ifdef WITH_VST fx->status = mch->plugins.size() > 0; fx->redraw(); #endif } /* -------------------------------------------------------------------------- */ void geMidiChannel::resize(int X, int Y, int W, int H) { geChannel::resize(X, Y, W, H); arm->hide(); #ifdef WITH_VST fx->hide(); #endif if (w() > BREAK_ARM) arm->show(); #ifdef WITH_VST if (w() > BREAK_FX) fx->show(); #endif packWidgets(); }giada-0.14.5/src/gui/elems/mainWindow/keyboard/midiChannel.h000066400000000000000000000032551322662744500236460ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef GE_MIDI_CHANNEL_H #define GE_MIDI_CHANNEL_H #include "channel.h" #include "channelButton.h" class MidiChannel; class geMidiChannel : public geChannel { private: static void cb_button(Fl_Widget* v, void* p); static void cb_openMenu(Fl_Widget* v, void* p); void cb_button(); void cb_openMenu(); public: geMidiChannel(int x, int y, int w, int h, MidiChannel* ch); void resize(int x, int y, int w, int h) override; void reset() override; void update() override; void refresh() override; int keyPress(int event); // TODO - move to base class }; #endif giada-0.14.5/src/gui/elems/mainWindow/keyboard/midiChannelButton.cpp000066400000000000000000000027161322662744500253760ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include "midiChannelButton.h" geMidiChannelButton::geMidiChannelButton(int x, int y, int w, int h, const char* l) : geChannelButton(x, y, w, h, l) { } /* -------------------------------------------------------------------------- */ int geMidiChannelButton::handle(int e) { // Currently MIDI drag-n-drop does nothing. return geButton::handle(e); } giada-0.14.5/src/gui/elems/mainWindow/keyboard/midiChannelButton.h000066400000000000000000000025511322662744500250400ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef GE_MIDI_CHANNEL_BUTTON_H #define GE_MIDI_CHANNEL_BUTTON_H #include "channelButton.h" class geMidiChannelButton : public geChannelButton { public: geMidiChannelButton(int x, int y, int w, int h, const char* l=0); int handle(int e); }; #endifgiada-0.14.5/src/gui/elems/mainWindow/keyboard/sampleChannel.cpp000066400000000000000000000351671322662744500245470ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include "../../../../core/mixer.h" #include "../../../../core/conf.h" #include "../../../../core/clock.h" #include "../../../../core/graphics.h" #include "../../../../core/wave.h" #include "../../../../core/sampleChannel.h" #include "../../../../glue/io.h" #include "../../../../glue/channel.h" #include "../../../../glue/recorder.h" #include "../../../../glue/storage.h" #include "../../../../utils/gui.h" #include "../../../dialogs/gd_mainWindow.h" #include "../../../dialogs/gd_keyGrabber.h" #include "../../../dialogs/sampleEditor.h" #include "../../../dialogs/channelNameInput.h" #include "../../../dialogs/gd_actionEditor.h" #include "../../../dialogs/gd_warnings.h" #include "../../../dialogs/browser/browserSave.h" #include "../../../dialogs/browser/browserLoad.h" #include "../../../dialogs/midiIO/midiOutputSampleCh.h" #include "../../../dialogs/midiIO/midiInputChannel.h" #include "../../basics/boxtypes.h" #include "../../basics/idButton.h" #include "../../basics/statusButton.h" #include "../../basics/dial.h" #include "channelStatus.h" #include "channelMode.h" #include "sampleChannelButton.h" #include "keyboard.h" #include "column.h" #include "sampleChannel.h" extern gdMainWindow* G_MainWin; using namespace giada; namespace { enum class Menu { INPUT_MONITOR = 0, LOAD_SAMPLE, EXPORT_SAMPLE, SETUP_KEYBOARD_INPUT, SETUP_MIDI_INPUT, SETUP_MIDI_OUTPUT, EDIT_SAMPLE, EDIT_ACTIONS, CLEAR_ACTIONS, CLEAR_ACTIONS_ALL, CLEAR_ACTIONS_MUTE, CLEAR_ACTIONS_VOLUME, CLEAR_ACTIONS_START_STOP, __END_CLEAR_ACTIONS_SUBMENU__, RESIZE, RESIZE_H1, RESIZE_H2, RESIZE_H3, RESIZE_H4, __END_RESIZE_SUBMENU__, RENAME_CHANNEL, CLONE_CHANNEL, FREE_CHANNEL, DELETE_CHANNEL }; /* -------------------------------------------------------------------------- */ void menuCallback(Fl_Widget* w, void* v) { geSampleChannel* gch = static_cast(w); Menu selectedItem = (Menu) (intptr_t) v; switch (selectedItem) { case Menu::INPUT_MONITOR: { glue_toggleInputMonitor(gch->ch); break; } case Menu::LOAD_SAMPLE: { gdWindow *w = new gdBrowserLoad(m::conf::browserX, m::conf::browserY, m::conf::browserW, m::conf::browserH, "Browse sample", m::conf::samplePath.c_str(), glue_loadSample, gch->ch); gu_openSubWindow(G_MainWin, w, WID_FILE_BROWSER); break; } case Menu::EXPORT_SAMPLE: { gdWindow *w = new gdBrowserSave(m::conf::browserX, m::conf::browserY, m::conf::browserW, m::conf::browserH, "Save sample", m::conf::samplePath.c_str(), "", glue_saveSample, gch->ch); gu_openSubWindow(G_MainWin, w, WID_FILE_BROWSER); break; } case Menu::SETUP_KEYBOARD_INPUT: { new gdKeyGrabber(gch->ch); // FIXME - use gu_openSubWindow break; } case Menu::SETUP_MIDI_INPUT: { gu_openSubWindow(G_MainWin, new gdMidiInputChannel(gch->ch), 0); break; } case Menu::SETUP_MIDI_OUTPUT: { gu_openSubWindow(G_MainWin, new gdMidiOutputSampleCh(static_cast(gch->ch)), 0); break; } case Menu::EDIT_SAMPLE: { gu_openSubWindow(G_MainWin, new gdSampleEditor(static_cast(gch->ch)), WID_SAMPLE_EDITOR); break; } case Menu::EDIT_ACTIONS: { gu_openSubWindow(G_MainWin, new gdActionEditor(gch->ch), WID_ACTION_EDITOR); break; } case Menu::CLEAR_ACTIONS: case Menu::RESIZE: case Menu::__END_CLEAR_ACTIONS_SUBMENU__: case Menu::__END_RESIZE_SUBMENU__: break; case Menu::CLEAR_ACTIONS_ALL: { c::recorder::clearAllActions(gch); break; } case Menu::CLEAR_ACTIONS_MUTE: { c::recorder::clearMuteActions(gch); break; } case Menu::CLEAR_ACTIONS_VOLUME: { c::recorder::clearVolumeActions(gch); break; } case Menu::CLEAR_ACTIONS_START_STOP: { c::recorder::clearStartStopActions(gch); break; } case Menu::RESIZE_H1: { gch->changeSize(G_GUI_CHANNEL_H_1); static_cast(gch->parent())->repositionChannels(); break; } case Menu::RESIZE_H2: { gch->changeSize(G_GUI_CHANNEL_H_2); static_cast(gch->parent())->repositionChannels(); break; } case Menu::RESIZE_H3: { gch->changeSize(G_GUI_CHANNEL_H_3); static_cast(gch->parent())->repositionChannels(); break; } case Menu::RESIZE_H4: { gch->changeSize(G_GUI_CHANNEL_H_4); static_cast(gch->parent())->repositionChannels(); break; } case Menu::CLONE_CHANNEL: { glue_cloneChannel(gch->ch); break; } case Menu::RENAME_CHANNEL: { gu_openSubWindow(G_MainWin, new gdChannelNameInput(gch->ch), WID_SAMPLE_NAME); break; } case Menu::FREE_CHANNEL: { glue_freeChannel(gch->ch); break; } case Menu::DELETE_CHANNEL: { glue_deleteChannel(gch->ch); break; } } } }; // {namespace} /* -------------------------------------------------------------------------- */ geSampleChannel::geSampleChannel(int X, int Y, int W, int H, SampleChannel* ch) : geChannel(X, Y, W, H, CHANNEL_SAMPLE, ch) { begin(); button = new geIdButton(x(), y(), G_GUI_UNIT, G_GUI_UNIT, "", channelStop_xpm, channelPlay_xpm); arm = new geButton(button->x()+button->w()+4, y(), G_GUI_UNIT, G_GUI_UNIT, "", armOff_xpm, armOn_xpm); status = new geChannelStatus(arm->x()+arm->w()+4, y(), G_GUI_UNIT, H, ch); mainButton = new geSampleChannelButton(status->x()+status->w()+4, y(), G_GUI_UNIT, H, "-- no sample --"); readActions = new geButton(mainButton->x()+mainButton->w()+4, y(), G_GUI_UNIT, G_GUI_UNIT, "", readActionOff_xpm, readActionOn_xpm); modeBox = new geChannelMode(readActions->x()+readActions->w()+4, y(), G_GUI_UNIT, G_GUI_UNIT, ch); mute = new geButton(modeBox->x()+modeBox->w()+4, y(), G_GUI_UNIT, G_GUI_UNIT, "", muteOff_xpm, muteOn_xpm); solo = new geButton(mute->x()+mute->w()+4, y(), G_GUI_UNIT, G_GUI_UNIT, "", soloOff_xpm, soloOn_xpm); #ifdef WITH_VST fx = new geStatusButton(solo->x()+solo->w()+4, y(), G_GUI_UNIT, G_GUI_UNIT, fxOff_xpm, fxOn_xpm); vol = new geDial(fx->x()+fx->w()+4, y(), G_GUI_UNIT, G_GUI_UNIT); #else vol = new geDial(solo->x()+solo->w()+4, y(), G_GUI_UNIT, G_GUI_UNIT); #endif end(); resizable(mainButton); update(); button->callback(cb_button, (void*)this); button->when(FL_WHEN_CHANGED); // do callback on keypress && on keyrelease arm->type(FL_TOGGLE_BUTTON); arm->callback(cb_arm, (void*)this); #ifdef WITH_VST fx->callback(cb_openFxWindow, (void*)this); #endif mute->type(FL_TOGGLE_BUTTON); mute->callback(cb_mute, (void*)this); solo->type(FL_TOGGLE_BUTTON); solo->callback(cb_solo, (void*)this); mainButton->callback(cb_openMenu, (void*)this); readActions->type(FL_TOGGLE_BUTTON); readActions->value(ch->readActions); readActions->callback(cb_readActions, (void*)this); vol->callback(cb_changeVol, (void*)this); ch->guiChannel = this; changeSize(H); // Update size dynamically } /* -------------------------------------------------------------------------- */ void geSampleChannel::cb_button (Fl_Widget* v, void* p) { ((geSampleChannel*)p)->__cb_button(); } void geSampleChannel::cb_openMenu (Fl_Widget* v, void* p) { ((geSampleChannel*)p)->__cb_openMenu(); } void geSampleChannel::cb_readActions (Fl_Widget* v, void* p) { ((geSampleChannel*)p)->__cb_readActions(); } /* -------------------------------------------------------------------------- */ void geSampleChannel::__cb_button() { if (button->value()) // pushed glue_keyPress(ch, Fl::event_ctrl(), Fl::event_shift()); else // released glue_keyRelease(ch, Fl::event_ctrl(), Fl::event_shift()); } /* -------------------------------------------------------------------------- */ void geSampleChannel::__cb_openMenu() { /* If you're recording (input or actions) no menu is allowed; you can't do anything, especially deallocate the channel */ if (m::mixer::recording || m::recorder::active) return; Fl_Menu_Item rclick_menu[] = { {"Input monitor", 0, menuCallback, (void*) Menu::INPUT_MONITOR, FL_MENU_TOGGLE | FL_MENU_DIVIDER | (static_cast(ch)->inputMonitor ? FL_MENU_VALUE : 0)}, {"Load new sample...", 0, menuCallback, (void*) Menu::LOAD_SAMPLE}, {"Export sample to file...", 0, menuCallback, (void*) Menu::EXPORT_SAMPLE}, {"Setup keyboard input...", 0, menuCallback, (void*) Menu::SETUP_KEYBOARD_INPUT}, {"Setup MIDI input...", 0, menuCallback, (void*) Menu::SETUP_MIDI_INPUT}, {"Setup MIDI output...", 0, menuCallback, (void*) Menu::SETUP_MIDI_OUTPUT}, {"Edit sample...", 0, menuCallback, (void*) Menu::EDIT_SAMPLE}, {"Edit actions...", 0, menuCallback, (void*) Menu::EDIT_ACTIONS}, {"Clear actions", 0, menuCallback, (void*) Menu::CLEAR_ACTIONS, FL_SUBMENU}, {"All", 0, menuCallback, (void*) Menu::CLEAR_ACTIONS_ALL}, {"Mute", 0, menuCallback, (void*) Menu::CLEAR_ACTIONS_MUTE}, {"Volume", 0, menuCallback, (void*) Menu::CLEAR_ACTIONS_VOLUME}, {"Start/Stop", 0, menuCallback, (void*) Menu::CLEAR_ACTIONS_START_STOP}, {0}, {"Resize", 0, menuCallback, (void*) Menu::RESIZE, FL_SUBMENU}, {"Normal", 0, menuCallback, (void*) Menu::RESIZE_H1}, {"Medium", 0, menuCallback, (void*) Menu::RESIZE_H2}, {"Large", 0, menuCallback, (void*) Menu::RESIZE_H3}, {"X-Large", 0, menuCallback, (void*) Menu::RESIZE_H4}, {0}, {"Rename channel", 0, menuCallback, (void*) Menu::RENAME_CHANNEL}, {"Clone channel", 0, menuCallback, (void*) Menu::CLONE_CHANNEL}, {"Free channel", 0, menuCallback, (void*) Menu::FREE_CHANNEL}, {"Delete channel", 0, menuCallback, (void*) Menu::DELETE_CHANNEL}, {0} }; if (ch->status & (STATUS_EMPTY | STATUS_MISSING)) { rclick_menu[(int) Menu::EXPORT_SAMPLE].deactivate(); rclick_menu[(int) Menu::EDIT_SAMPLE].deactivate(); rclick_menu[(int) Menu::FREE_CHANNEL].deactivate(); rclick_menu[(int) Menu::RENAME_CHANNEL].deactivate(); } if (!ch->hasActions) rclick_menu[(int) Menu::CLEAR_ACTIONS].deactivate(); /* No 'clear start/stop actions' for those channels in loop mode: they cannot have start/stop actions. */ if (static_cast(ch)->mode & LOOP_ANY) rclick_menu[(int) Menu::CLEAR_ACTIONS_START_STOP].deactivate(); Fl_Menu_Button* b = new Fl_Menu_Button(0, 0, 100, 50); b->box(G_CUSTOM_BORDER_BOX); b->textsize(G_GUI_FONT_SIZE_BASE); b->textcolor(G_COLOR_LIGHT_2); b->color(G_COLOR_GREY_2); const Fl_Menu_Item* m = rclick_menu->popup(Fl::event_x(), Fl::event_y(), 0, 0, b); if (m) m->do_callback(this, m->user_data()); return; } /* -------------------------------------------------------------------------- */ void geSampleChannel::__cb_readActions() { glue_toggleReadingRecs(static_cast(ch)); } /* -------------------------------------------------------------------------- */ void geSampleChannel::refresh() { if (!mainButton->visible()) // mainButton invisible? status too (see below) return; setColorsByStatus(ch->status, ch->recStatus); if (static_cast(ch)->wave != nullptr) { if (m::mixer::recording && ch->isArmed()) mainButton->setInputRecordMode(); if (m::recorder::active) { if (m::recorder::canRec(ch, m::clock::isRunning(), m::mixer::recording)) mainButton->setActionRecordMode(); } status->redraw(); // status invisible? sampleButton too (see below) } mainButton->redraw(); } /* -------------------------------------------------------------------------- */ void geSampleChannel::reset() { hideActionButton(); mainButton->setDefaultMode("-- no sample --"); mainButton->redraw(); status->redraw(); } /* -------------------------------------------------------------------------- */ void geSampleChannel::update() { const SampleChannel* sch = static_cast(ch); switch (sch->status) { case STATUS_EMPTY: mainButton->label("-- no sample --"); break; case STATUS_MISSING: case STATUS_WRONG: mainButton->label("* file not found! *"); break; default: if (sch->getName().empty()) mainButton->label(sch->wave->getBasename(false).c_str()); else mainButton->label(sch->getName().c_str()); break; } /* Update channels. If you load a patch with recorded actions, the 'R' button must be shown. Moreover if the actions are active, the 'R' button must be activated accordingly. */ if (sch->hasActions) showActionButton(); else hideActionButton(); modeBox->value(sch->mode); modeBox->redraw(); vol->value(sch->volume); mute->value(sch->mute); solo->value(sch->solo); mainButton->setKey(sch->key); arm->value(sch->isArmed()); #ifdef WITH_VST fx->status = sch->plugins.size() > 0; fx->redraw(); #endif } /* -------------------------------------------------------------------------- */ void geSampleChannel::showActionButton() { readActions->value(static_cast(ch)->readActions); readActions->show(); packWidgets(); redraw(); } /* -------------------------------------------------------------------------- */ void geSampleChannel::hideActionButton() { readActions->hide(); packWidgets(); redraw(); } /* -------------------------------------------------------------------------- */ void geSampleChannel::resize(int X, int Y, int W, int H) { geChannel::resize(X, Y, W, H); arm->hide(); modeBox->hide(); readActions->hide(); #ifdef WITH_VST fx->hide(); #endif if (w() > BREAK_ARM) arm->show(); #ifdef WITH_VST if (w() > BREAK_FX) fx->show(); #endif if (w() > BREAK_MODE_BOX) modeBox->show(); if (w() > BREAK_READ_ACTIONS && ch->hasActions) readActions->show(); packWidgets(); } void geSampleChannel::changeSize(int H) { geChannel::changeSize(H); int Y = y() + (H / 2 - (G_GUI_UNIT / 2)); status->resize(x(), Y, w(), G_GUI_UNIT); modeBox->resize(x(), Y, w(), G_GUI_UNIT); readActions->resize(x(), Y, w(), G_GUI_UNIT); }giada-0.14.5/src/gui/elems/mainWindow/keyboard/sampleChannel.h000066400000000000000000000037201322662744500242020ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef GE_SAMPLE_CHANNEL_H #define GE_SAMPLE_CHANNEL_H #include "channel.h" class SampleChannel; class geChannelMode; class geButton; class geSampleChannel : public geChannel { private: static void cb_button (Fl_Widget* v, void* p); static void cb_openMenu (Fl_Widget* v, void* p); static void cb_readActions(Fl_Widget* v, void* p); void __cb_button (); void __cb_openMenu (); void __cb_readActions(); public: geSampleChannel(int x, int y, int w, int h, SampleChannel* ch); void resize(int x, int y, int w, int h) override; void reset() override; void update() override; void refresh() override; void changeSize(int h) override; /* show/hideActionButton Adds or removes 'R' button when actions are available. */ void showActionButton(); void hideActionButton(); geChannelMode* modeBox; geButton* readActions; }; #endif giada-0.14.5/src/gui/elems/mainWindow/keyboard/sampleChannelButton.cpp000066400000000000000000000043121322662744500257270ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include #include "../../../../core/const.h" #include "../../../../core/sampleChannel.h" #include "../../../../utils/string.h" #include "../../../../utils/fs.h" #include "../../../../glue/channel.h" #include "../../../dialogs/gd_mainWindow.h" #include "sampleChannel.h" #include "keyboard.h" #include "sampleChannelButton.h" extern gdMainWindow* G_MainWin; geSampleChannelButton::geSampleChannelButton(int x, int y, int w, int h, const char* l) : geChannelButton(x, y, w, h, l) { } /* -------------------------------------------------------------------------- */ int geSampleChannelButton::handle(int e) { int ret = geButton::handle(e); switch (e) { case FL_DND_ENTER: case FL_DND_DRAG: case FL_DND_RELEASE: { ret = 1; break; } case FL_PASTE: { geSampleChannel* gch = static_cast(parent()); SampleChannel* ch = static_cast(gch->ch); int result = glue_loadChannel(ch, gu_trim(gu_stripFileUrl(Fl::event_text()))); if (result != G_RES_OK) G_MainWin->keyboard->printChannelMessage(result); ret = 1; break; } } return ret; } giada-0.14.5/src/gui/elems/mainWindow/keyboard/sampleChannelButton.h000066400000000000000000000025631322662744500254020ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef GE_SAMPLE_CHANNEL_BUTTON_H #define GE_SAMPLE_CHANNEL_BUTTON_H #include "channelButton.h" class geSampleChannelButton : public geChannelButton { public: geSampleChannelButton(int x, int y, int w, int h, const char* l=0); int handle(int e); }; #endif giada-0.14.5/src/gui/elems/mainWindow/mainIO.cpp000066400000000000000000000116131322662744500213370ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include "../../../core/const.h" #include "../../../core/graphics.h" #include "../../../core/mixer.h" #include "../../../core/pluginHost.h" #include "../../../glue/main.h" #include "../../../utils/gui.h" #include "../../elems/soundMeter.h" #include "../../elems/basics/statusButton.h" #include "../../elems/basics/dial.h" #include "../../dialogs/gd_mainWindow.h" #include "../../dialogs/pluginList.h" #include "mainIO.h" extern gdMainWindow *G_MainWin; using namespace giada::m; geMainIO::geMainIO(int x, int y) : Fl_Group(x, y, 396, 20) { begin(); #if defined(WITH_VST) masterFxIn = new geStatusButton (x, y, 20, 20, fxOff_xpm, fxOn_xpm); inVol = new geDial (masterFxIn->x()+masterFxIn->w()+4, y, 20, 20); inMeter = new geSoundMeter(inVol->x()+inVol->w()+4, y+4, 140, 12); inToOut = new geButton (inMeter->x()+inMeter->w()+4, y+4, 12, 12, "", inputToOutputOff_xpm, inputToOutputOn_xpm); outMeter = new geSoundMeter(inToOut->x()+inToOut->w()+4, y+4, 140, 12); outVol = new geDial (outMeter->x()+outMeter->w()+4, y, 20, 20); masterFxOut = new geStatusButton (outVol->x()+outVol->w()+4, y, 20, 20, fxOff_xpm, fxOn_xpm); #else inVol = new geDial (x+62, y, 20, 20); inMeter = new geSoundMeter(inVol->x()+inVol->w()+4, y+5, 140, 12); outMeter = new geSoundMeter(inMeter->x()+inMeter->w()+4, y+5, 140, 12); outVol = new geDial (outMeter->x()+outMeter->w()+4, y, 20, 20); #endif end(); resizable(nullptr); // don't resize any widget outVol->callback(cb_outVol, (void*)this); outVol->value(mixer::outVol); inVol->callback(cb_inVol, (void*)this); inVol->value(mixer::inVol); #ifdef WITH_VST masterFxOut->callback(cb_masterFxOut, (void*)this); masterFxIn->callback(cb_masterFxIn, (void*)this); inToOut->callback(cb_inToOut, (void*)this); inToOut->type(FL_TOGGLE_BUTTON); #endif } /* -------------------------------------------------------------------------- */ void geMainIO::cb_outVol (Fl_Widget *v, void *p) { ((geMainIO*)p)->__cb_outVol(); } void geMainIO::cb_inVol (Fl_Widget *v, void *p) { ((geMainIO*)p)->__cb_inVol(); } #ifdef WITH_VST void geMainIO::cb_masterFxOut(Fl_Widget *v, void *p) { ((geMainIO*)p)->__cb_masterFxOut(); } void geMainIO::cb_masterFxIn (Fl_Widget *v, void *p) { ((geMainIO*)p)->__cb_masterFxIn(); } void geMainIO::cb_inToOut (Fl_Widget *v, void *p) { ((geMainIO*)p)->__cb_inToOut(); } #endif /* -------------------------------------------------------------------------- */ void geMainIO::__cb_outVol() { glue_setOutVol(outVol->value()); } /* -------------------------------------------------------------------------- */ void geMainIO::__cb_inVol() { glue_setInVol(inVol->value()); } /* -------------------------------------------------------------------------- */ #ifdef WITH_VST void geMainIO::__cb_masterFxOut() { gu_openSubWindow(G_MainWin, new gdPluginList(pluginHost::MASTER_OUT), WID_FX_LIST); } void geMainIO::__cb_masterFxIn() { gu_openSubWindow(G_MainWin, new gdPluginList(pluginHost::MASTER_IN), WID_FX_LIST); } void geMainIO::__cb_inToOut() { mixer::inToOut = inToOut->value(); } #endif /* -------------------------------------------------------------------------- */ void geMainIO::setOutVol(float v) { outVol->value(v); } void geMainIO::setInVol(float v) { inVol->value(v); } /* -------------------------------------------------------------------------- */ #ifdef WITH_VST void geMainIO::setMasterFxOutFull(bool v) { masterFxOut->status = v; masterFxOut->redraw(); } void geMainIO::setMasterFxInFull(bool v) { masterFxIn->status = v; masterFxIn->redraw(); } #endif /* -------------------------------------------------------------------------- */ void geMainIO::refresh() { outMeter->mixerPeak = mixer::peakOut; inMeter->mixerPeak = mixer::peakIn; outMeter->redraw(); inMeter->redraw(); } giada-0.14.5/src/gui/elems/mainWindow/mainIO.h000066400000000000000000000042631322662744500210070ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef GE_MAIN_IO_H #define GE_MAIN_IO_H #include class geSoundMeter; class geDial; #ifdef WITH_VST class geStatusButton; class geButton; #endif class geMainIO : public Fl_Group { private: geSoundMeter *outMeter; geSoundMeter *inMeter; geDial *outVol; geDial *inVol; #ifdef WITH_VST geStatusButton *masterFxOut; geStatusButton *masterFxIn; geButton *inToOut; #endif static void cb_outVol (Fl_Widget *v, void *p); static void cb_inVol (Fl_Widget *v, void *p); #ifdef WITH_VST static void cb_masterFxOut(Fl_Widget *v, void *p); static void cb_masterFxIn (Fl_Widget *v, void *p); static void cb_inToOut (Fl_Widget *v, void *p); #endif inline void __cb_outVol (); inline void __cb_inVol (); #ifdef WITH_VST inline void __cb_masterFxOut(); inline void __cb_masterFxIn (); inline void __cb_inToOut (); #endif public: geMainIO(int x, int y); void refresh(); void setOutVol(float v); void setInVol (float v); #ifdef WITH_VST void setMasterFxOutFull(bool v); void setMasterFxInFull(bool v); #endif }; #endif giada-0.14.5/src/gui/elems/mainWindow/mainMenu.cpp000066400000000000000000000155361322662744500217440ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include #include "../../../core/const.h" #include "../../../core/mixer.h" #include "../../../core/mixerHandler.h" #include "../../../core/conf.h" #include "../../../core/patch.h" #include "../../../core/channel.h" #include "../../../core/sampleChannel.h" #include "../../../utils/gui.h" #include "../../../glue/storage.h" #include "../../../glue/main.h" #include "../../elems/basics/boxtypes.h" #include "../../elems/basics/button.h" #include "../../dialogs/gd_mainWindow.h" #include "../../dialogs/gd_about.h" #include "../../dialogs/gd_config.h" #include "../../dialogs/gd_warnings.h" #include "../../dialogs/browser/browserLoad.h" #include "../../dialogs/browser/browserSave.h" #include "../../dialogs/midiIO/midiInputMaster.h" #include "keyboard/keyboard.h" #include "mainMenu.h" extern gdMainWindow* G_MainWin; using namespace giada::m; geMainMenu::geMainMenu(int x, int y) : Fl_Group(x, y, 300, 20) { begin(); file = new geButton(x, y, 70, 21, "file"); edit = new geButton(file->x()+file->w()+4, y, 70, 21, "edit"); config = new geButton(edit->x()+edit->w()+4, y, 70, 21, "config"); about = new geButton(config->x()+config->w()+4, y, 70, 21, "about"); end(); resizable(nullptr); // don't resize any widget about->callback(cb_about, (void*)this); file->callback(cb_file, (void*)this); edit->callback(cb_edit, (void*)this); config->callback(cb_config, (void*)this); } /* -------------------------------------------------------------------------- */ void geMainMenu::cb_about (Fl_Widget* v, void* p) { ((geMainMenu*)p)->__cb_about(); } void geMainMenu::cb_config(Fl_Widget* v, void* p) { ((geMainMenu*)p)->__cb_config(); } void geMainMenu::cb_file (Fl_Widget* v, void* p) { ((geMainMenu*)p)->__cb_file(); } void geMainMenu::cb_edit (Fl_Widget* v, void* p) { ((geMainMenu*)p)->__cb_edit(); } /* -------------------------------------------------------------------------- */ void geMainMenu::__cb_about() { gu_openSubWindow(G_MainWin, new gdAbout(), WID_ABOUT); } /* -------------------------------------------------------------------------- */ void geMainMenu::__cb_config() { gu_openSubWindow(G_MainWin, new gdConfig(400, 370), WID_CONFIG); } /* -------------------------------------------------------------------------- */ void geMainMenu::__cb_file() { /* An Fl_Menu_Button is made of many Fl_Menu_Item */ Fl_Menu_Item menu[] = { {"Open patch or project..."}, {"Save patch..."}, {"Save project..."}, {"Quit Giada"}, {0} }; Fl_Menu_Button* b = new Fl_Menu_Button(0, 0, 100, 50); b->box(G_CUSTOM_BORDER_BOX); b->textsize(G_GUI_FONT_SIZE_BASE); b->textcolor(G_COLOR_LIGHT_2); b->color(G_COLOR_GREY_2); const Fl_Menu_Item* m = menu->popup(Fl::event_x(), Fl::event_y(), 0, 0, b); if (!m) return; if (strcmp(m->label(), "Open patch or project...") == 0) { gdWindow *childWin = new gdBrowserLoad(conf::browserX, conf::browserY, conf::browserW, conf::browserH, "Load patch or project", conf::patchPath, glue_loadPatch, nullptr); gu_openSubWindow(G_MainWin, childWin, WID_FILE_BROWSER); return; } if (strcmp(m->label(), "Save patch...") == 0) { if (mh::hasLogicalSamples() || mh::hasEditedSamples()) if (!gdConfirmWin("Warning", "You should save a project in order to store\nyour takes and/or processed samples.")) return; gdWindow *childWin = new gdBrowserSave(conf::browserX, conf::browserY, conf::browserW, conf::browserH, "Save patch", conf::patchPath, patch::name, glue_savePatch, nullptr); gu_openSubWindow(G_MainWin, childWin, WID_FILE_BROWSER); return; } if (strcmp(m->label(), "Save project...") == 0) { gdWindow *childWin = new gdBrowserSave(conf::browserX, conf::browserY, conf::browserW, conf::browserH, "Save project", conf::patchPath, patch::name, glue_saveProject, nullptr); gu_openSubWindow(G_MainWin, childWin, WID_FILE_BROWSER); return; } if (strcmp(m->label(), "Quit Giada") == 0) { G_MainWin->do_callback(); return; } } /* -------------------------------------------------------------------------- */ void geMainMenu::__cb_edit() { Fl_Menu_Item menu[] = { {"Clear all samples"}, {"Clear all actions"}, {"Remove empty columns"}, {"Reset to init state"}, {"Setup global MIDI input..."}, {0} }; /* clear all actions disabled if no recs, clear all samples disabled * if no samples. */ menu[1].deactivate(); for (unsigned i=0; ihasActions) { menu[1].activate(); break; } for (unsigned i=0; itype == CHANNEL_SAMPLE) if (((SampleChannel*)mixer::channels.at(i))->wave != nullptr) { menu[0].activate(); break; } Fl_Menu_Button* b = new Fl_Menu_Button(0, 0, 100, 50); b->box(G_CUSTOM_BORDER_BOX); b->textsize(G_GUI_FONT_SIZE_BASE); b->textcolor(G_COLOR_LIGHT_2); b->color(G_COLOR_GREY_2); const Fl_Menu_Item* m = menu->popup(Fl::event_x(), Fl::event_y(), 0, 0, b); if (!m) return; if (strcmp(m->label(), "Clear all samples") == 0) { if (!gdConfirmWin("Warning", "Clear all samples: are you sure?")) return; G_MainWin->delSubWindow(WID_SAMPLE_EDITOR); glue_clearAllSamples(); return; } if (strcmp(m->label(), "Clear all actions") == 0) { if (!gdConfirmWin("Warning", "Clear all actions: are you sure?")) return; G_MainWin->delSubWindow(WID_ACTION_EDITOR); glue_clearAllRecs(); return; } if (strcmp(m->label(), "Reset to init state") == 0) { if (!gdConfirmWin("Warning", "Reset to init state: are you sure?")) return; glue_resetToInitState(); return; } if (strcmp(m->label(), "Remove empty columns") == 0) { G_MainWin->keyboard->organizeColumns(); return; } if (strcmp(m->label(), "Setup global MIDI input...") == 0) { gu_openSubWindow(G_MainWin, new gdMidiInputMaster(), 0); return; } } giada-0.14.5/src/gui/elems/mainWindow/mainMenu.h000066400000000000000000000032371322662744500214040ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef GE_MAIN_MENU_H #define GE_MAIN_MENU_H #include class geButton; class geMainMenu : public Fl_Group { private: geButton *file; geButton *edit; geButton *config; geButton *about; static void cb_about (Fl_Widget *v, void *p); static void cb_config(Fl_Widget *v, void *p); static void cb_file (Fl_Widget *v, void *p); static void cb_edit (Fl_Widget *v, void *p); inline void __cb_about (); inline void __cb_config(); inline void __cb_file (); inline void __cb_edit (); public: geMainMenu(int x, int y); }; #endif giada-0.14.5/src/gui/elems/mainWindow/mainTimer.cpp000066400000000000000000000117661322662744500221210ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include "../../../core/const.h" #include "../../../core/mixer.h" #include "../../../core/graphics.h" #include "../../../core/clock.h" #include "../../../glue/main.h" #include "../../../utils/gui.h" #include "../../../utils/string.h" #include "../../elems/basics/button.h" #include "../../elems/basics/choice.h" #include "../../dialogs/gd_mainWindow.h" #include "../../dialogs/bpmInput.h" #include "../../dialogs/beatsInput.h" #include "mainTimer.h" extern gdMainWindow *G_MainWin; using std::string; using namespace giada::m; geMainTimer::geMainTimer(int x, int y) : Fl_Group(x, y, 180, 20) { begin(); quantizer = new geChoice(x, y, 40, 20, "", false); bpm = new geButton (quantizer->x()+quantizer->w()+4, y, 40, 20); meter = new geButton (bpm->x()+bpm->w()+8, y, 40, 20, "4/1"); multiplier = new geButton (meter->x()+meter->w()+4, y, 20, 20, "", multiplyOff_xpm, multiplyOn_xpm); divider = new geButton (multiplier->x()+multiplier->w()+4, y, 20, 20, "", divideOff_xpm, divideOn_xpm); end(); resizable(nullptr); // don't resize any widget bpm->copy_label(gu_fToString(clock::getBpm(), 1).c_str()); bpm->callback(cb_bpm, (void*)this); meter->callback(cb_meter, (void*)this); multiplier->callback(cb_multiplier, (void*)this); divider->callback(cb_divider, (void*)this); quantizer->add("off", 0, cb_quantizer, (void*)this); quantizer->add("1b", 0, cb_quantizer, (void*)this); quantizer->add("2b", 0, cb_quantizer, (void*)this); quantizer->add("3b", 0, cb_quantizer, (void*)this); quantizer->add("4b", 0, cb_quantizer, (void*)this); quantizer->add("6b", 0, cb_quantizer, (void*)this); quantizer->add("8b", 0, cb_quantizer, (void*)this); quantizer->value(0); // "off" by default } /* -------------------------------------------------------------------------- */ void geMainTimer::cb_bpm (Fl_Widget *v, void *p) { ((geMainTimer*)p)->__cb_bpm(); } void geMainTimer::cb_meter (Fl_Widget *v, void *p) { ((geMainTimer*)p)->__cb_meter(); } void geMainTimer::cb_quantizer (Fl_Widget *v, void *p) { ((geMainTimer*)p)->__cb_quantizer(); } void geMainTimer::cb_multiplier(Fl_Widget *v, void *p) { ((geMainTimer*)p)->__cb_multiplier(); } void geMainTimer::cb_divider (Fl_Widget *v, void *p) { ((geMainTimer*)p)->__cb_divider(); } /* -------------------------------------------------------------------------- */ void geMainTimer::__cb_bpm() { gu_openSubWindow(G_MainWin, new gdBpmInput(bpm->label()), WID_BPM); } /* -------------------------------------------------------------------------- */ void geMainTimer::__cb_meter() { gu_openSubWindow(G_MainWin, new gdBeatsInput(), WID_BEATS); } /* -------------------------------------------------------------------------- */ void geMainTimer::__cb_quantizer() { glue_quantize(quantizer->value()); } /* -------------------------------------------------------------------------- */ void geMainTimer::__cb_multiplier() { glue_beatsMultiply(); } /* -------------------------------------------------------------------------- */ void geMainTimer::__cb_divider() { glue_beatsDivide(); } /* -------------------------------------------------------------------------- */ void geMainTimer::setBpm(const char *v) { bpm->copy_label(v); } void geMainTimer::setBpm(float v) { bpm->copy_label(gu_fToString((float) v, 1).c_str()); // Only 1 decimal place (e.g. 120.0) } /* -------------------------------------------------------------------------- */ void geMainTimer::setLock(bool v) { if (v) { bpm->deactivate(); meter->deactivate(); multiplier->deactivate(); divider->deactivate(); } else { bpm->activate(); meter->activate(); multiplier->activate(); divider->activate(); } } /* -------------------------------------------------------------------------- */ void geMainTimer::setMeter(int beats, int bars) { string tmp = gu_iToString(beats) + "/" + gu_iToString(bars); meter->copy_label(tmp.c_str()); } giada-0.14.5/src/gui/elems/mainWindow/mainTimer.h000066400000000000000000000037651322662744500215660ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef GE_MAIN_TIMER_H #define GE_MAIN_TIMER_H #include class geButton; class geChoice; class geMainTimer : public Fl_Group { private: geButton *bpm; geButton *meter; geChoice *quantizer; geButton *multiplier; geButton *divider; static void cb_bpm (Fl_Widget *v, void *p); static void cb_meter (Fl_Widget *v, void *p); static void cb_quantizer (Fl_Widget *v, void *p); static void cb_multiplier(Fl_Widget *v, void *p); static void cb_divider (Fl_Widget *v, void *p); inline void __cb_bpm(); inline void __cb_meter(); inline void __cb_quantizer(); inline void __cb_multiplier(); inline void __cb_divider(); public: geMainTimer(int x, int y); void setBpm(const char *v); void setBpm(float v); void setMeter(int beats, int bars); /* setLock Locks bpm, beter and multipliers. Used during audio recordings. */ void setLock(bool v); }; #endif giada-0.14.5/src/gui/elems/mainWindow/mainTransport.cpp000066400000000000000000000103451322662744500230250ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include "../../../core/graphics.h" #include "../../../glue/transport.h" #include "../../../glue/io.h" #include "../../elems/basics/button.h" #include "mainTransport.h" geMainTransport::geMainTransport(int x, int y) : Fl_Group(x, y, 131, 25) { begin(); rewind = new geButton(x, y, 25, 25, "", rewindOff_xpm, rewindOn_xpm); play = new geButton(rewind->x()+rewind->w()+4, y, 25, 25, "", play_xpm, pause_xpm); recAction = new geButton(play->x()+play->w()+4, y, 25, 25, "", recOff_xpm, recOn_xpm); recInput = new geButton(recAction->x()+recAction->w()+4, y, 25, 25, "", inputRecOff_xpm, inputRecOn_xpm); metronome = new geButton(recInput->x()+recInput->w()+4, y+10, 15, 15, "", metronomeOff_xpm, metronomeOn_xpm); end(); resizable(nullptr); // don't resize any widget rewind->callback(cb_rewind, (void*)this); play->callback(cb_play); play->type(FL_TOGGLE_BUTTON); recAction->callback(cb_recAction, (void*)this); recAction->type(FL_TOGGLE_BUTTON); recInput->callback(cb_recInput, (void*)this); recInput->type(FL_TOGGLE_BUTTON); metronome->callback(cb_metronome); metronome->type(FL_TOGGLE_BUTTON); } /* -------------------------------------------------------------------------- */ void geMainTransport::cb_rewind (Fl_Widget *v, void *p) { ((geMainTransport*)p)->__cb_rewind(); } void geMainTransport::cb_play (Fl_Widget *v, void *p) { ((geMainTransport*)p)->__cb_play(); } void geMainTransport::cb_recAction(Fl_Widget *v, void *p) { ((geMainTransport*)p)->__cb_recAction(); } void geMainTransport::cb_recInput (Fl_Widget *v, void *p) { ((geMainTransport*)p)->__cb_recInput(); } void geMainTransport::cb_metronome(Fl_Widget *v, void *p) { ((geMainTransport*)p)->__cb_metronome(); } /* -------------------------------------------------------------------------- */ void geMainTransport::__cb_rewind() { glue_rewindSeq(true); } /* -------------------------------------------------------------------------- */ void geMainTransport::__cb_play() { glue_startStopSeq(true); } /* -------------------------------------------------------------------------- */ void geMainTransport::__cb_recAction() { glue_startStopActionRec(true); } /* -------------------------------------------------------------------------- */ void geMainTransport::__cb_recInput() { glue_startStopInputRec(true); } /* -------------------------------------------------------------------------- */ void geMainTransport::__cb_metronome() { glue_startStopMetronome(true); } /* -------------------------------------------------------------------------- */ void geMainTransport::updatePlay(int v) { play->value(v); play->redraw(); } /* -------------------------------------------------------------------------- */ void geMainTransport::updateMetronome(int v) { metronome->value(v); metronome->redraw(); } /* -------------------------------------------------------------------------- */ void geMainTransport::updateRecInput(int v) { recInput->value(v); recInput->redraw(); } /* -------------------------------------------------------------------------- */ void geMainTransport::updateRecAction(int v) { recAction->value(v); recAction->redraw(); } giada-0.14.5/src/gui/elems/mainWindow/mainTransport.h000066400000000000000000000036561322662744500225010ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef GE_MAIN_TRANSPORT_H #define GE_MAIN_TRANSPORT_H #include class geButton; class geMainTransport : public Fl_Group { private: geButton *rewind; geButton *play; geButton *recAction; geButton *recInput; geButton *metronome; static void cb_rewind (Fl_Widget *v, void *p); static void cb_play (Fl_Widget *v, void *p); static void cb_recAction(Fl_Widget *v, void *p); static void cb_recInput (Fl_Widget *v, void *p); static void cb_metronome(Fl_Widget *v, void *p); inline void __cb_rewind (); inline void __cb_play (); inline void __cb_recAction(); inline void __cb_recInput (); inline void __cb_metronome(); public: geMainTransport(int x, int y); void updatePlay (int v); void updateMetronome(int v); void updateRecInput (int v); void updateRecAction(int v); }; #endif giada-0.14.5/src/gui/elems/midiLearner.cpp000066400000000000000000000065011322662744500203020ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include "../../utils/string.h" #include "../dialogs/midiIO/midiInputBase.h" #include "basics/boxtypes.h" #include "basics/button.h" #include "basics/box.h" #include "midiLearner.h" using std::string; using namespace giada::m; geMidiLearner::geMidiLearner(int X, int Y, int W, const char* l, midiDispatcher::cb_midiLearn* cb, uint32_t* param, Channel* ch) : Fl_Group(X, Y, W, 20), callback(cb), ch (ch), param (param) { begin(); text = new geBox(x(), y(), 156, 20, l); value = new geButton(text->x()+text->w()+4, y(), 80, 20); button = new geButton(value->x()+value->w()+4, y(), 40, 20, "learn"); end(); text->box(G_CUSTOM_BORDER_BOX); text->align(FL_ALIGN_LEFT | FL_ALIGN_INSIDE); value->box(G_CUSTOM_BORDER_BOX); value->callback(cb_value, (void*)this); value->when(FL_WHEN_RELEASE); updateValue(); button->type(FL_TOGGLE_BUTTON); button->callback(cb_button, (void*)this); } /* -------------------------------------------------------------------------- */ void geMidiLearner::updateValue() { string tmp; if (*param != 0x0) { tmp = "0x" + gu_iToString(*param, true); // true: hex mode tmp.pop_back(); // Remove last two digits, useless in MIDI messages tmp.pop_back(); // Remove last two digits, useless in MIDI messages } else tmp = "(not set)"; value->copy_label(tmp.c_str()); button->value(0); } /* -------------------------------------------------------------------------- */ void geMidiLearner::cb_button(Fl_Widget* v, void* p) { ((geMidiLearner*)p)->cb_button(); } void geMidiLearner::cb_value(Fl_Widget* v, void* p) { ((geMidiLearner*)p)->cb_value(); } /* -------------------------------------------------------------------------- */ void geMidiLearner::cb_value() { if (Fl::event_button() == FL_RIGHT_MOUSE) { *param = 0x0; updateValue(); } /// TODO - elif (LEFT_MOUSE) : insert values by hand } /* -------------------------------------------------------------------------- */ void geMidiLearner::cb_button() { if (button->value() == 1) { cbData.window = static_cast(parent()); // parent = gdMidiInput cbData.learner = this; cbData.channel = ch; midiDispatcher::startMidiLearn(callback, (void*)&cbData); } else midiDispatcher::stopMidiLearn(); } giada-0.14.5/src/gui/elems/midiLearner.h000066400000000000000000000044121322662744500177460ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef GE_MIDI_LEARNER_H #define GE_MIDI_LEARNER_H #include #include "../../core/midiDispatcher.h" class Channel; class gdMidiInputBase; class geMidiLearner; class geBox; class geButton; class geMidiLearner : public Fl_Group { private: /* callback Callback to pass to midiDispatcher. Requires two parameters: * uint32_t msg - MIDI message * void *data - extra data */ giada::m::midiDispatcher::cb_midiLearn* callback; /* Channel it belongs to. Might be nullptr if the learner comes from the MIDI input master window. */ Channel* ch; geBox* text; geButton* value; geButton* button; static void cb_button(Fl_Widget* v, void* p); static void cb_value (Fl_Widget* v, void* p); void cb_button(); void cb_value(); public: /* cbData_t Struct we pass to midiDispatcher as extra parameter. */ struct cbData_t { gdMidiInputBase* window; geMidiLearner* learner; Channel* channel; } cbData; /* param * pointer to ch->midiIn[value] */ uint32_t* param; geMidiLearner(int x, int y, int w, const char* l, giada::m::midiDispatcher::cb_midiLearn* cb, uint32_t* param, Channel* ch); void updateValue(); }; #endif giada-0.14.5/src/gui/elems/plugin/000077500000000000000000000000001322662744500166375ustar00rootroot00000000000000giada-0.14.5/src/gui/elems/plugin/pluginBrowser.cpp000066400000000000000000000067061322662744500222160ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifdef WITH_VST #include #include "../../../core/plugin.h" #include "../../../core/const.h" #include "../../../core/pluginHost.h" #include "../basics/boxtypes.h" #include "pluginBrowser.h" using std::vector; using std::string; using namespace giada::m; gePluginBrowser::gePluginBrowser(int x, int y, int w, int h) : Fl_Browser(x, y, w, h) { box(G_CUSTOM_BORDER_BOX); textsize(G_GUI_FONT_SIZE_BASE); textcolor(G_COLOR_LIGHT_2); selection_color(G_COLOR_GREY_4); color(G_COLOR_GREY_2); this->scrollbar.color(G_COLOR_GREY_2); this->scrollbar.selection_color(G_COLOR_GREY_4); this->scrollbar.labelcolor(G_COLOR_LIGHT_1); this->scrollbar.slider(G_CUSTOM_BORDER_BOX); this->hscrollbar.color(G_COLOR_GREY_2); this->hscrollbar.selection_color(G_COLOR_GREY_4); this->hscrollbar.labelcolor(G_COLOR_LIGHT_1); this->hscrollbar.slider(G_CUSTOM_BORDER_BOX); type(FL_HOLD_BROWSER); computeWidths(); column_widths(widths); column_char('\t'); // tabs as column delimiters refresh(); end(); } /* -------------------------------------------------------------------------- */ void gePluginBrowser::refresh() { clear(); add("NAME\tMANUFACTURER\tCATEGORY\tFORMAT\tUID"); add("---\t---\t---\t---\t---"); for (int i=0; i widths[0]) widths[0] = w0; if (w1 > widths[1]) widths[1] = w1; if (w3 > widths[3]) widths[3] = w3; } widths[0] += 60; widths[1] += 60; widths[2] = fl_width("CATEGORY") + 60; widths[3] += 60; widths[4] = 0; } #endif giada-0.14.5/src/gui/elems/plugin/pluginBrowser.h000066400000000000000000000026221322662744500216540ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifdef WITH_VST #ifndef GE_PLUGIN_BROWSER_H #define GE_PLUGIN_BROWSER_H #include class gePluginBrowser : public Fl_Browser { private: int widths[5] = {0}; void computeWidths(); public: gePluginBrowser(int x, int y, int w, int h); void refresh(); }; #endif #endif giada-0.14.5/src/gui/elems/plugin/pluginParameter.cpp000066400000000000000000000057741322662744500225170ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifdef WITH_VST #include "../../../core/plugin.h" #include "../../../core/const.h" #include "../../../glue/plugin.h" #include "../basics/boxtypes.h" #include "../basics/box.h" #include "../basics/slider.h" #include "pluginParameter.h" using std::string; using namespace giada::c; gePluginParameter::gePluginParameter(int paramIndex, Plugin* p, int X, int Y, int W, int labelWidth) : Fl_Group (X, Y, W, G_GUI_UNIT), m_paramIndex(paramIndex), m_plugin (p) { begin(); m_label = new geBox(x(), y(), labelWidth, G_GUI_UNIT); m_label->copy_label(m_plugin->getParameterName(m_paramIndex).c_str()); m_label->align(FL_ALIGN_LEFT | FL_ALIGN_INSIDE); m_slider = new geSlider(m_label->x()+m_label->w()+G_GUI_OUTER_MARGIN, y(), w()-(m_label->x()+m_label->w()+G_GUI_OUTER_MARGIN)-VALUE_WIDTH, G_GUI_UNIT); m_slider->value(m_plugin->getParameter(m_paramIndex)); m_slider->callback(cb_setValue, (void*)this); m_value = new geBox(m_slider->x()+m_slider->w()+G_GUI_OUTER_MARGIN, y(), VALUE_WIDTH, G_GUI_UNIT); m_value->align(FL_ALIGN_LEFT | FL_ALIGN_INSIDE); m_value->box(G_CUSTOM_BORDER_BOX); end(); resizable(m_slider); update(false); } /* -------------------------------------------------------------------------- */ void gePluginParameter::cb_setValue(Fl_Widget* v, void* p) { ((gePluginParameter*)p)->cb_setValue(); } /* -------------------------------------------------------------------------- */ void gePluginParameter::cb_setValue() { plugin::setParameter(m_plugin, m_paramIndex, m_slider->value()); } /* -------------------------------------------------------------------------- */ void gePluginParameter::update(bool changeSlider) { string v = m_plugin->getParameterText(m_paramIndex) + " " + m_plugin->getParameterLabel(m_paramIndex); m_value->copy_label(v.c_str()); if (changeSlider) m_slider->value(m_plugin->getParameter(m_paramIndex)); } #endif // #ifdef WITH_VST giada-0.14.5/src/gui/elems/plugin/pluginParameter.h000066400000000000000000000032571322662744500221560ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifdef WITH_VST #ifndef GE_PLUGIN_PARAMETER_H #define GE_PLUGIN_PARAMETER_H #include class Plugin; class geBox; class geSlider; class gePluginParameter : public Fl_Group { private: static const int VALUE_WIDTH = 100; int m_paramIndex; Plugin* m_plugin; geBox* m_label; geSlider* m_slider; geBox* m_value; static void cb_setValue(Fl_Widget* v, void* p); void cb_setValue(); public: gePluginParameter(int paramIndex, Plugin* p, int x, int y, int w, int labelWidth); void update(bool changeSlider); }; #endif #endif // #ifdef WITH_VST giada-0.14.5/src/gui/elems/sampleEditor/000077500000000000000000000000001322662744500177715ustar00rootroot00000000000000giada-0.14.5/src/gui/elems/sampleEditor/boostTool.cpp000066400000000000000000000074741322662744500224750ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include #include "../../../core/sampleChannel.h" #include "../../../core/const.h" #include "../../../core/waveFx.h" #include "../../../glue/channel.h" #include "../../../utils/gui.h" #include "../../../utils/string.h" #include "../../../utils/math.h" #include "../../dialogs/sampleEditor.h" #include "../basics/dial.h" #include "../basics/input.h" #include "../basics/box.h" #include "../basics/button.h" #include "waveTools.h" #include "boostTool.h" using namespace giada::m; geBoostTool::geBoostTool(int X, int Y, SampleChannel *ch) : Fl_Group(X, Y, 220, 20), ch (ch) { begin(); label = new geBox(x(), y(), gu_getStringWidth("Boost"), 20, "Boost", FL_ALIGN_RIGHT); dial = new geDial(label->x()+label->w()+4, y(), 20, 20); input = new geInput(dial->x()+dial->w()+4, y(), 70, 20); normalize = new geButton(input->x()+input->w()+4, y(), 70, 20, "Normalize"); end(); dial->range(1.0f, 10.0f); dial->callback(cb_setBoost, (void*)this); dial->when(FL_WHEN_CHANGED | FL_WHEN_RELEASE); input->callback(cb_setBoostNum, (void*)this); normalize->callback(cb_normalize, (void*)this); refresh(); } /* -------------------------------------------------------------------------- */ void geBoostTool::refresh() { input->value(gu_fToString(gu_linearToDB(ch->getBoost()), 2).c_str()); // 2 digits // A dial greater than it's max value goes crazy dial->value(ch->getBoost() <= 10.0f ? ch->getBoost() : 10.0f); } /* -------------------------------------------------------------------------- */ void geBoostTool::cb_setBoost (Fl_Widget *w, void *p) { ((geBoostTool*)p)->__cb_setBoost(); } void geBoostTool::cb_setBoostNum(Fl_Widget *w, void *p) { ((geBoostTool*)p)->__cb_setBoostNum(); } void geBoostTool::cb_normalize (Fl_Widget *w, void *p) { ((geBoostTool*)p)->__cb_normalize(); } /* -------------------------------------------------------------------------- */ void geBoostTool::__cb_setBoost() { if (Fl::event() == FL_DRAG) glue_setBoost(ch, dial->value()); else if (Fl::event() == FL_RELEASE) { glue_setBoost(ch, dial->value()); static_cast(window())->waveTools->updateWaveform(); } } /* -------------------------------------------------------------------------- */ void geBoostTool::__cb_setBoostNum() { glue_setBoost(ch, gu_dBtoLinear(atof(input->value()))); static_cast(window())->waveTools->updateWaveform(); } /* -------------------------------------------------------------------------- */ void geBoostTool::__cb_normalize() { float val = wfx::normalizeSoft(ch->wave); glue_setBoost(ch, val); // it's like a fake user moving the dial static_cast(window())->waveTools->updateWaveform(); } giada-0.14.5/src/gui/elems/sampleEditor/boostTool.h000066400000000000000000000033731322662744500221340ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef GE_BOOST_TOOL_H #define GE_BOOST_TOOL_H #include class SampleChannel; class geDial; class geInput; class geButton; class geBox; class geBoostTool : public Fl_Group { private: SampleChannel *ch; geBox *label; geDial *dial; geInput *input; geButton *normalize; static void cb_setBoost (Fl_Widget *w, void *p); static void cb_setBoostNum(Fl_Widget *w, void *p); static void cb_normalize (Fl_Widget *w, void *p); inline void __cb_setBoost (); inline void __cb_setBoostNum(); inline void __cb_normalize (); public: geBoostTool(int x, int y, SampleChannel *ch); void refresh(); }; #endif giada-0.14.5/src/gui/elems/sampleEditor/panTool.cpp000066400000000000000000000063071322662744500221170ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include #include "../../../core/sampleChannel.h" #include "../../../core/const.h" #include "../../../core/waveFx.h" #include "../../../glue/channel.h" #include "../../../utils/gui.h" #include "../../../utils/math.h" #include "../../../utils/string.h" #include "../../dialogs/sampleEditor.h" #include "../basics/dial.h" #include "../basics/input.h" #include "../basics/box.h" #include "../basics/button.h" #include "waveTools.h" #include "panTool.h" using std::string; gePanTool::gePanTool(int x, int y, SampleChannel *ch) : Fl_Group(x, y, 200, 20), ch (ch) { begin(); label = new geBox(x, y, gu_getStringWidth("Pan"), 20, "Pan", FL_ALIGN_RIGHT); dial = new geDial(label->x()+label->w()+4, y, 20, 20); input = new geInput(dial->x()+dial->w()+4, y, 70, 20); reset = new geButton(input->x()+input->w()+4, y, 70, 20, "Reset"); end(); dial->range(0.0f, 1.0f); dial->callback(cb_panning, (void*)this); input->align(FL_ALIGN_RIGHT); input->readonly(1); input->cursor_color(FL_WHITE); reset->callback(cb_panReset, (void*)this); refresh(); } /* -------------------------------------------------------------------------- */ void gePanTool::refresh() { dial->value(ch->getPan()); if (ch->getPan() < 0.5f) { string tmp = gu_iToString((int) ((-ch->getPan() * 200.0f) + 100.0f)) + " L"; input->value(tmp.c_str()); } else if (ch->getPan() == 0.5) input->value("C"); else { string tmp = gu_iToString((int) ((ch->getPan() * 200.0f) - 100.0f)) + " R"; input->value(tmp.c_str()); } } /* -------------------------------------------------------------------------- */ void gePanTool::cb_panning (Fl_Widget *w, void *p) { ((gePanTool*)p)->__cb_panning(); } void gePanTool::cb_panReset(Fl_Widget *w, void *p) { ((gePanTool*)p)->__cb_panReset(); } /* -------------------------------------------------------------------------- */ void gePanTool::__cb_panning() { glue_setPanning(ch, dial->value()); } /* -------------------------------------------------------------------------- */ void gePanTool::__cb_panReset() { glue_setPanning(ch, 0.5f); }giada-0.14.5/src/gui/elems/sampleEditor/panTool.h000066400000000000000000000032131322662744500215550ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef GE_PAN_TOOL_H #define GE_PAN_TOOL_H #include class SampleChannel; class geDial; class geInput; class geButton; class geBox; class gePanTool : public Fl_Group { private: SampleChannel *ch; geBox *label; geDial *dial; geInput *input; geButton *reset; static void cb_panning (Fl_Widget *w, void *p); static void cb_panReset(Fl_Widget *w, void *p); inline void __cb_panning(); inline void __cb_panReset(); public: gePanTool(int x, int y, SampleChannel *ch); void refresh(); }; #endif giada-0.14.5/src/gui/elems/sampleEditor/pitchTool.cpp000066400000000000000000000122371322662744500224470ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include #include "../../../core/sampleChannel.h" #include "../../../core/const.h" #include "../../../core/graphics.h" #include "../../../core/clock.h" #include "../../../glue/channel.h" #include "../../../utils/gui.h" #include "../../../utils/string.h" #include "../../dialogs/sampleEditor.h" #include "../basics/dial.h" #include "../basics/input.h" #include "../basics/box.h" #include "../basics/button.h" #include "pitchTool.h" using namespace giada::m; gePitchTool::gePitchTool(int x, int y, SampleChannel* ch) : Fl_Group(x, y, 600, 20), ch (ch) { begin(); label = new geBox(x, y, gu_getStringWidth("Pitch"), 20, "Pitch", FL_ALIGN_RIGHT); dial = new geDial(label->x()+label->w()+4, y, 20, 20); input = new geInput(dial->x()+dial->w()+4, y, 70, 20); pitchToBar = new geButton(input->x()+input->w()+4, y, 70, 20, "To bar"); pitchToSong = new geButton(pitchToBar->x()+pitchToBar->w()+4, y, 70, 20, "To song"); pitchHalf = new geButton(pitchToSong->x()+pitchToSong->w()+4, y, 20, 20, "", divideOff_xpm, divideOn_xpm); pitchDouble = new geButton(pitchHalf->x()+pitchHalf->w()+4, y, 20, 20, "", multiplyOff_xpm, multiplyOn_xpm); pitchReset = new geButton(pitchDouble->x()+pitchDouble->w()+4, y, 70, 20, "Reset"); end(); dial->range(0.01f, 4.0f); dial->callback(cb_setPitch, (void*)this); dial->when(FL_WHEN_RELEASE); input->align(FL_ALIGN_RIGHT); input->callback(cb_setPitchNum, (void*)this); input->when(FL_WHEN_RELEASE | FL_WHEN_ENTER_KEY); pitchToBar->callback(cb_setPitchToBar, (void*)this); pitchToSong->callback(cb_setPitchToSong, (void*)this); pitchHalf->callback(cb_setPitchHalf, (void*)this); pitchDouble->callback(cb_setPitchDouble, (void*)this); pitchReset->callback(cb_resetPitch, (void*)this); refresh(); } /* -------------------------------------------------------------------------- */ void gePitchTool::refresh() { dial->value(ch->getPitch()); input->value(gu_fToString(ch->getPitch(), 4).c_str()); // 4 digits } /* -------------------------------------------------------------------------- */ void gePitchTool::cb_setPitch (Fl_Widget* w, void* p) { ((gePitchTool*)p)->__cb_setPitch(); } void gePitchTool::cb_setPitchToBar (Fl_Widget* w, void* p) { ((gePitchTool*)p)->__cb_setPitchToBar(); } void gePitchTool::cb_setPitchToSong(Fl_Widget* w, void* p) { ((gePitchTool*)p)->__cb_setPitchToSong(); } void gePitchTool::cb_setPitchHalf (Fl_Widget* w, void* p) { ((gePitchTool*)p)->__cb_setPitchHalf(); } void gePitchTool::cb_setPitchDouble(Fl_Widget* w, void* p) { ((gePitchTool*)p)->__cb_setPitchDouble(); } void gePitchTool::cb_resetPitch (Fl_Widget* w, void* p) { ((gePitchTool*)p)->__cb_resetPitch(); } void gePitchTool::cb_setPitchNum (Fl_Widget* w, void* p) { ((gePitchTool*)p)->__cb_setPitchNum(); } /* -------------------------------------------------------------------------- */ void gePitchTool::__cb_setPitch() { glue_setPitch(ch, dial->value()); } /* -------------------------------------------------------------------------- */ void gePitchTool::__cb_setPitchNum() { glue_setPitch(ch, atof(input->value())); } /* -------------------------------------------------------------------------- */ void gePitchTool::__cb_setPitchHalf() { glue_setPitch(ch, dial->value()/2); } /* -------------------------------------------------------------------------- */ void gePitchTool::__cb_setPitchDouble() { glue_setPitch(ch, dial->value()*2); } /* -------------------------------------------------------------------------- */ void gePitchTool::__cb_setPitchToBar() { // TODO - opaque channel's count glue_setPitch(ch, (ch->getEnd()*2) / (float) clock::getFramesPerBar()); } /* -------------------------------------------------------------------------- */ void gePitchTool::__cb_setPitchToSong() { // TODO - opaque channel's count glue_setPitch(ch, (ch->getEnd()*2) / (float) clock::getTotalFrames()); } /* -------------------------------------------------------------------------- */ void gePitchTool::__cb_resetPitch() { glue_setPitch(ch, G_DEFAULT_PITCH); } giada-0.14.5/src/gui/elems/sampleEditor/pitchTool.h000066400000000000000000000043231322662744500221110ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef GE_PITCH_TOOL_H #define GE_PITCH_TOOL_H #include class SampleChannel; class geDial; class geInput; class geButton; class geBox; class gePitchTool : public Fl_Group { private: SampleChannel *ch; geBox *label; geDial *dial; geInput *input; geButton *pitchToBar; geButton *pitchToSong; geButton *pitchHalf; geButton *pitchDouble; geButton *pitchReset; static void cb_setPitch (Fl_Widget *w, void *p); static void cb_setPitchToBar (Fl_Widget *w, void *p); static void cb_setPitchToSong(Fl_Widget *w, void *p); static void cb_setPitchHalf (Fl_Widget *w, void *p); static void cb_setPitchDouble(Fl_Widget *w, void *p); static void cb_resetPitch (Fl_Widget *w, void *p); static void cb_setPitchNum (Fl_Widget *w, void *p); inline void __cb_setPitch(); inline void __cb_setPitchToBar(); inline void __cb_setPitchToSong(); inline void __cb_setPitchHalf(); inline void __cb_setPitchDouble(); inline void __cb_resetPitch(); inline void __cb_setPitchNum(); public: gePitchTool(int x, int y, SampleChannel *ch); void refresh(); }; #endif giada-0.14.5/src/gui/elems/sampleEditor/rangeTool.cpp000066400000000000000000000067171322662744500224420ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include #include "../../../core/sampleChannel.h" #include "../../../core/wave.h" #include "../../../glue/channel.h" #include "../../../glue/sampleEditor.h" #include "../../../utils/gui.h" #include "../../../utils/string.h" #include "../../dialogs/sampleEditor.h" #include "../basics/input.h" #include "../basics/box.h" #include "../basics/button.h" #include "waveTools.h" #include "rangeTool.h" using namespace giada::c; geRangeTool::geRangeTool(int x, int y, SampleChannel* ch) : Fl_Group(x, y, 280, G_GUI_UNIT), m_ch (ch) { begin(); m_label = new geBox(x, y, gu_getStringWidth("Range"), G_GUI_UNIT, "Range", FL_ALIGN_RIGHT); m_begin = new geInput(m_label->x()+m_label->w()+G_GUI_INNER_MARGIN, y, 70, G_GUI_UNIT); m_end = new geInput(m_begin->x()+m_begin->w()+G_GUI_INNER_MARGIN, y, 70, G_GUI_UNIT); m_reset = new geButton(m_end->x()+m_end->w()+G_GUI_INNER_MARGIN, y, 70, G_GUI_UNIT, "Reset"); end(); m_begin->type(FL_INT_INPUT); m_begin->when(FL_WHEN_RELEASE | FL_WHEN_ENTER_KEY); // on focus lost or enter key m_begin->callback(cb_setChanPos, this); m_end->type(FL_INT_INPUT); m_end->when(FL_WHEN_RELEASE | FL_WHEN_ENTER_KEY); // on focus lost or enter key m_end->callback(cb_setChanPos, this); m_reset->callback(cb_resetStartEnd, this); refresh(); } /* -------------------------------------------------------------------------- */ void geRangeTool::refresh() { m_begin->value(gu_iToString(m_ch->getBegin()).c_str()); m_end->value(gu_iToString(m_ch->getEnd()).c_str()); } /* -------------------------------------------------------------------------- */ void geRangeTool::cb_setChanPos (Fl_Widget* w, void* p) { ((geRangeTool*)p)->__cb_setChanPos(); } void geRangeTool::cb_resetStartEnd(Fl_Widget* w, void* p) { ((geRangeTool*)p)->__cb_resetStartEnd(); } /* -------------------------------------------------------------------------- */ void geRangeTool::__cb_setChanPos() { sampleEditor::setBeginEnd(m_ch, atoi(m_begin->value()), atoi(m_end->value())); static_cast(window())->waveTools->updateWaveform(); // TODO - glue's business! } /* -------------------------------------------------------------------------- */ void geRangeTool::__cb_resetStartEnd() { sampleEditor::setBeginEnd(m_ch, 0, m_ch->wave->getSize() - 1); static_cast(window())->waveTools->updateWaveform(); // TODO - glue's business! } giada-0.14.5/src/gui/elems/sampleEditor/rangeTool.h000066400000000000000000000032401322662744500220730ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef GE_RANGE_TOOL_H #define GE_RANGE_TOOL_H #include class SampleChannel; class geInput; class geButton; class geBox; class geRangeTool : public Fl_Group { private: SampleChannel* m_ch; geBox* m_label; geInput* m_begin; geInput* m_end; geButton* m_reset; static void cb_setChanPos (Fl_Widget* w, void* p); static void cb_resetStartEnd(Fl_Widget* w, void* p); inline void __cb_setChanPos(); inline void __cb_resetStartEnd(); public: geRangeTool(int x, int y, SampleChannel* ch); void refresh(); }; #endif giada-0.14.5/src/gui/elems/sampleEditor/shiftTool.cpp000066400000000000000000000061061322662744500224530ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include #include "../../../core/const.h" #include "../../../core/sampleChannel.h" #include "../../../utils/gui.h" #include "../../../utils/string.h" #include "../../../glue/sampleEditor.h" #include "../../dialogs/gd_warnings.h" #include "../basics/input.h" #include "../basics/box.h" #include "../basics/button.h" #include "shiftTool.h" using namespace giada::c; geShiftTool::geShiftTool(int x, int y, SampleChannel* ch) : Fl_Group(x, y, 300, G_GUI_UNIT), m_ch (ch) { begin(); m_label = new geBox(x, y, gu_getStringWidth("Shift"), G_GUI_UNIT, "Shift", FL_ALIGN_RIGHT); m_shift = new geInput(m_label->x()+m_label->w()+G_GUI_INNER_MARGIN, y, 70, G_GUI_UNIT); m_reset = new geButton(m_shift->x()+m_shift->w()+G_GUI_INNER_MARGIN, y, 70, G_GUI_UNIT, "Reset"); end(); m_shift->type(FL_INT_INPUT); m_shift->when(FL_WHEN_RELEASE | FL_WHEN_ENTER_KEY); // on focus lost or enter key m_shift->value(gu_iToString(ch->getShift()).c_str()); m_shift->callback(cb_setShift, (void*)this); m_reset->callback(cb_reset, (void*)this); refresh(); } /* -------------------------------------------------------------------------- */ void geShiftTool::cb_setShift(Fl_Widget* w, void* p) { ((geShiftTool*)p)->cb_setShift(); } void geShiftTool::cb_reset(Fl_Widget* w, void* p) { ((geShiftTool*)p)->cb_reset(); } /* -------------------------------------------------------------------------- */ void geShiftTool::cb_setShift() { shift(atoi(m_shift->value())); } /* -------------------------------------------------------------------------- */ void geShiftTool::cb_reset() { shift(0); } /* -------------------------------------------------------------------------- */ void geShiftTool::refresh() { m_shift->value(gu_iToString(m_ch->getShift()).c_str()); } /* -------------------------------------------------------------------------- */ void geShiftTool::shift(int f) { if (m_ch->isPlaying()) gdAlert("Can't shift sample while playing."); else sampleEditor::shift(m_ch, f); } giada-0.14.5/src/gui/elems/sampleEditor/shiftTool.h000066400000000000000000000031571322662744500221230ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef GE_SHIFT_TOOL_H #define GE_SHIFT_TOOL_H #include class SampleChannel; class geInput; class geButton; class geBox; class geShiftTool : public Fl_Group { private: SampleChannel* m_ch; geBox* m_label; geInput* m_shift; geButton* m_reset; static void cb_setShift(Fl_Widget* w, void* p); static void cb_reset(Fl_Widget* w, void* p); void cb_setShift(); void cb_reset(); void shift(int f); public: geShiftTool(int x, int y, SampleChannel* ch); void refresh(); }; #endif giada-0.14.5/src/gui/elems/sampleEditor/volumeTool.cpp000066400000000000000000000061421322662744500226450ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include #include #include #include "../../../core/sampleChannel.h" #include "../../../core/const.h" #include "../../../glue/channel.h" #include "../../../utils/gui.h" #include "../../../utils/math.h" #include "../../../utils/string.h" #include "../basics/dial.h" #include "../basics/input.h" #include "../basics/box.h" #include "../mainWindow/keyboard/channel.h" #include "volumeTool.h" using std::string; geVolumeTool::geVolumeTool(int X, int Y, SampleChannel *ch) : Fl_Group(X, Y, 150, 20), ch (ch) { begin(); label = new geBox (x(), y(), gu_getStringWidth("Volume"), 20, "Volume", FL_ALIGN_RIGHT); dial = new geDial (label->x()+label->w()+4, y(), 20, 20); input = new geInput(dial->x()+dial->w()+4, y(), 70, 20); end(); dial->range(0.0f, 1.0f); dial->callback(cb_setVolume, (void*)this); input->callback(cb_setVolumeNum, (void*)this); refresh(); } /* -------------------------------------------------------------------------- */ void geVolumeTool::refresh() { string tmp; float dB = gu_linearToDB(ch->volume); if (dB > -INFINITY) tmp = gu_fToString(dB, 2); // 2 digits else tmp = "-inf"; input->value(tmp.c_str()); dial->value(ch->guiChannel->vol->value()); } /* -------------------------------------------------------------------------- */ void geVolumeTool::cb_setVolume (Fl_Widget *w, void *p) { ((geVolumeTool*)p)->__cb_setVolume(); } void geVolumeTool::cb_setVolumeNum(Fl_Widget *w, void *p) { ((geVolumeTool*)p)->__cb_setVolumeNum(); } /* -------------------------------------------------------------------------- */ void geVolumeTool::__cb_setVolume() { glue_setVolume(ch, dial->value(), false, true); refresh(); } /* -------------------------------------------------------------------------- */ void geVolumeTool::__cb_setVolumeNum() { float value = pow(10, (atof(input->value()) / 20)); // linear = 10^(dB/20) glue_setVolume(ch, value, false, true); dial->value(ch->guiChannel->vol->value()); } giada-0.14.5/src/gui/elems/sampleEditor/volumeTool.h000066400000000000000000000032011322662744500223030ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef GE_VOLUME_TOOL_H #define GE_VOLUME_TOOL_H #include class SampleChannel; class geDial; class geInput; class geBox; class geVolumeTool : public Fl_Group { private: SampleChannel *ch; geBox *label; geDial *dial; geInput *input; static void cb_setVolume (Fl_Widget *w, void *p); static void cb_setVolumeNum(Fl_Widget *w, void *p); inline void __cb_setVolume (); inline void __cb_setVolumeNum(); public: geVolumeTool(int x, int y, SampleChannel *ch); void refresh(); }; #endif giada-0.14.5/src/gui/elems/sampleEditor/waveTools.cpp000066400000000000000000000153741322662744500224720ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include "../../../core/const.h" #ifdef G_OS_MAC // our Clang still doesn't know about cstdint (c++11 stuff) #include #else #include #endif #include #include #include "../../../core/sampleChannel.h" #include "../../../core/waveFx.h" #include "../../../glue/sampleEditor.h" #include "../basics/boxtypes.h" #include "waveform.h" #include "waveTools.h" using namespace giada; namespace { enum class Menu { CUT = 0, COPY, PASTE, TRIM, SILENCE, REVERSE, NORMALIZE, FADE_IN, FADE_OUT, SMOOTH_EDGES, SET_BEGIN_END, TO_NEW_CHANNEL }; /* -------------------------------------------------------------------------- */ void menuCallback(Fl_Widget* w, void* v) { geWaveTools* wavetools = static_cast(w); Menu selectedItem = (Menu) (intptr_t) v; int a = wavetools->waveform->getSelectionA(); int b = wavetools->waveform->getSelectionB(); switch (selectedItem) { case Menu::CUT: c::sampleEditor::cut(wavetools->ch, a, b); break; case Menu::COPY: c::sampleEditor::copy(wavetools->ch, a, b); break; case Menu::PASTE: c::sampleEditor::paste(wavetools->ch, a); break; case Menu::TRIM: c::sampleEditor::trim(wavetools->ch, a, b); break; case Menu::SILENCE: c::sampleEditor::silence(wavetools->ch, a, b); break; case Menu::REVERSE: c::sampleEditor::reverse(wavetools->ch, a, b); break; case Menu::NORMALIZE: c::sampleEditor::normalizeHard(wavetools->ch, a, b); break; case Menu::FADE_IN: c::sampleEditor::fade(wavetools->ch, a, b, m::wfx::FADE_IN); break; case Menu::FADE_OUT: c::sampleEditor::fade(wavetools->ch, a, b, m::wfx::FADE_OUT); break; case Menu::SMOOTH_EDGES: c::sampleEditor::smoothEdges(wavetools->ch, a, b); break; case Menu::SET_BEGIN_END: c::sampleEditor::setBeginEnd(wavetools->ch, a, b); break; case Menu::TO_NEW_CHANNEL: c::sampleEditor::toNewChannel(wavetools->ch, a, b); break; } } }; // {anonymous} /* -------------------------------------------------------------------------- */ geWaveTools::geWaveTools(int x, int y, int w, int h, SampleChannel *ch, const char *l) : Fl_Scroll(x, y, w, h, l), ch (ch) { type(Fl_Scroll::HORIZONTAL_ALWAYS); hscrollbar.color(G_COLOR_GREY_2); hscrollbar.selection_color(G_COLOR_GREY_4); hscrollbar.labelcolor(G_COLOR_LIGHT_1); hscrollbar.slider(G_CUSTOM_BORDER_BOX); waveform = new geWaveform(x, y, w, h-24, ch); } /* -------------------------------------------------------------------------- */ void geWaveTools::updateWaveform() { waveform->refresh(); } /* -------------------------------------------------------------------------- */ void geWaveTools::redrawWaveformAsync() { if (ch->isPreview()) waveform->redraw(); } /* -------------------------------------------------------------------------- */ void geWaveTools::resize(int x, int y, int w, int h) { if (this->w() == w || (this->w() != w && this->h() != h)) { // vertical or both resize Fl_Widget::resize(x, y, w, h); waveform->resize(x, y, waveform->w(), h-24); updateWaveform(); } else { // horizontal resize Fl_Widget::resize(x, y, w, h); } if (this->w() > waveform->w()) waveform->stretchToWindow(); int offset = waveform->x() + waveform->w() - this->w() - this->x(); if (offset < 0) waveform->position(waveform->x()-offset, this->y()); } /* -------------------------------------------------------------------------- */ int geWaveTools::handle(int e) { switch (e) { case FL_MOUSEWHEEL: { waveform->setZoom(Fl::event_dy()); redraw(); return 1; } case FL_PUSH: { if (Fl::event_button3()) { // right button openMenu(); return 1; } Fl::focus(waveform); } default: return Fl_Group::handle(e); } } /* -------------------------------------------------------------------------- */ void geWaveTools::openMenu() { Fl_Menu_Item menu[] = { {"Cut", 0, menuCallback, (void*) Menu::CUT}, {"Copy", 0, menuCallback, (void*) Menu::COPY}, {"Paste", 0, menuCallback, (void*) Menu::PASTE}, {"Trim", 0, menuCallback, (void*) Menu::TRIM}, {"Silence", 0, menuCallback, (void*) Menu::SILENCE}, {"Reverse", 0, menuCallback, (void*) Menu::REVERSE}, {"Normalize", 0, menuCallback, (void*) Menu::NORMALIZE}, {"Fade in", 0, menuCallback, (void*) Menu::FADE_IN}, {"Fade out", 0, menuCallback, (void*) Menu::FADE_OUT}, {"Smooth edges", 0, menuCallback, (void*) Menu::SMOOTH_EDGES}, {"Set begin/end here", 0, menuCallback, (void*) Menu::SET_BEGIN_END}, {"Copy to new channel", 0, menuCallback, (void*) Menu::TO_NEW_CHANNEL}, {0} }; if (ch->status == STATUS_PLAY) { menu[(int)Menu::CUT].deactivate(); menu[(int)Menu::TRIM].deactivate(); } if (!waveform->isSelected()) { menu[(int)Menu::CUT].deactivate(); menu[(int)Menu::COPY].deactivate(); menu[(int)Menu::TRIM].deactivate(); menu[(int)Menu::SILENCE].deactivate(); menu[(int)Menu::REVERSE].deactivate(); menu[(int)Menu::NORMALIZE].deactivate(); menu[(int)Menu::FADE_IN].deactivate(); menu[(int)Menu::FADE_OUT].deactivate(); menu[(int)Menu::SMOOTH_EDGES].deactivate(); menu[(int)Menu::SET_BEGIN_END].deactivate(); menu[(int)Menu::TO_NEW_CHANNEL].deactivate(); } Fl_Menu_Button *b = new Fl_Menu_Button(0, 0, 100, 50); b->box(G_CUSTOM_BORDER_BOX); b->textsize(G_GUI_FONT_SIZE_BASE); b->textcolor(G_COLOR_LIGHT_2); b->color(G_COLOR_GREY_2); const Fl_Menu_Item *m = menu->popup(Fl::event_x(), Fl::event_y(), 0, 0, b); if (m) m->do_callback(this, m->user_data()); return; } giada-0.14.5/src/gui/elems/sampleEditor/waveTools.h000066400000000000000000000036131322662744500221300ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef GE_WAVE_TOOLS_H #define GE_WAVE_TOOLS_H #include class SampleChannel; class geWaveform; class geWaveTools : public Fl_Scroll { private: void openMenu(); public: SampleChannel *ch; geWaveform *waveform; geWaveTools(int X,int Y,int W, int H, SampleChannel *ch, const char *L=0); void resize(int x, int y, int w, int h); int handle(int e); /* updateWaveform Updates the waveform by realloc-ing new data (i.e. when the waveform has changed). */ void updateWaveform(); /* redrawWaveformAsync Redraws the waveform, called by the video thread. This is meant to be called repeatedly when you need to update the play head inside the waveform. The method is smart enough to skip painting if the channel is stopped. */ void redrawWaveformAsync(); }; #endif giada-0.14.5/src/gui/elems/sampleEditor/waveform.cpp000066400000000000000000000377371322662744500223440ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include #include #include #include #include "../../../core/wave.h" #include "../../../core/conf.h" #include "../../../core/const.h" #include "../../../core/mixer.h" #include "../../../core/waveFx.h" #include "../../../core/sampleChannel.h" #include "../../../glue/channel.h" #include "../../../glue/sampleEditor.h" #include "../../../utils/log.h" #include "../../dialogs/sampleEditor.h" #include "../basics/boxtypes.h" #include "waveTools.h" #include "waveform.h" using namespace giada::m; using namespace giada::c; geWaveform::geWaveform(int x, int y, int w, int h, SampleChannel* ch, const char* l) : Fl_Widget (x, y, w, h, l), m_selection {}, m_ch (ch), m_chanStart (0), m_chanStartLit(false), m_chanEnd (0), m_chanEndLit (false), m_pushed (false), m_dragged (false), m_resizedA (false), m_resizedB (false), m_ratio (0.0f) { m_data.sup = nullptr; m_data.inf = nullptr; m_data.size = 0; m_grid.snap = conf::sampleEditorGridOn; m_grid.level = conf::sampleEditorGridVal; alloc(w); } /* -------------------------------------------------------------------------- */ geWaveform::~geWaveform() { freeData(); } /* -------------------------------------------------------------------------- */ void geWaveform::freeData() { if (m_data.sup) { delete[] m_data.sup; delete[] m_data.inf; m_data.sup = nullptr; m_data.inf = nullptr; m_data.size = 0; } m_grid.points.clear(); } /* -------------------------------------------------------------------------- */ int geWaveform::alloc(int datasize, bool force) { Wave* wave = m_ch->wave; m_ratio = wave->getSize() / (float) datasize; /* Limit 1:1 drawing (to avoid sub-frame drawing) by keeping m_ratio >= 1. */ if (m_ratio < 1) { datasize = wave->getSize(); m_ratio = 1; } if (datasize == m_data.size && !force) return 0; freeData(); m_data.size = datasize; m_data.sup = new (std::nothrow) int[m_data.size]; m_data.inf = new (std::nothrow) int[m_data.size]; if (!m_data.sup || !m_data.inf) { gu_log("[geWaveform::alloc] unable to allocate memory for the waveform!\n"); return 0; } gu_log("[geWaveform::alloc] %d pixels, %f m_ratio\n", m_data.size, m_ratio); int offset = h() / 2; int zero = y() + offset; // center, zero amplitude (-inf dB) /* Frid frequency: store a grid point every 'gridFreq' frame (if grid is enabled). TODO - this will cause round off errors, since gridFreq is integer. */ int gridFreq = m_grid.level != 0 ? wave->getSize() / m_grid.level : 0; /* Resampling the waveform, hardcore way. Many thanks to http://fourier.eng.hmc.edu/e161/lectures/resize/node3.html */ for (int i=0; i= wave->getSize()) continue; /* Compute average of stereo signal. */ float avg = 0.0f; float* frame = wave->getFrame(k); for (int j=0; jgetChannels(); j++) avg += frame[j]; avg /= wave->getChannels(); /* Find peaks (greater and lower). */ if (avg > peaksup) peaksup = avg; else if (avg <= peakinf) peakinf = avg; /* Fill up grid vector. */ if (gridFreq != 0 && (int) k % gridFreq == 0 && k != 0) m_grid.points.push_back(k); } m_data.sup[i] = zero - (peaksup * m_ch->getBoost() * offset); m_data.inf[i] = zero - (peakinf * m_ch->getBoost() * offset); // avoid window overflow if (m_data.sup[i] < y()) m_data.sup[i] = y(); if (m_data.inf[i] > y()+h()-1) m_data.inf[i] = y()+h()-1; } recalcPoints(); return 1; } /* -------------------------------------------------------------------------- */ void geWaveform::recalcPoints() { m_chanStart = m_ch->getBegin(); m_chanEnd = m_ch->getEnd(); } /* -------------------------------------------------------------------------- */ void geWaveform::drawSelection() { if (!isSelected()) return; int a = frameToPixel(m_selection.a) + x(); int b = frameToPixel(m_selection.b) + x(); if (a < 0) a = 0; if (b >= w() + BORDER) b = w() + BORDER; if (a < b) fl_rectf(a, y(), b-a, h(), G_COLOR_GREY_4); else fl_rectf(b, y(), a-b, h(), G_COLOR_GREY_4); } /* -------------------------------------------------------------------------- */ void geWaveform::drawWaveform(int from, int to) { int zero = y() + (h() / 2); // zero amplitude (-inf dB) fl_color(G_COLOR_BLACK); for (int i=from; i= m_data.size) break; fl_line(i+x(), zero, i+x(), m_data.sup[i]); fl_line(i+x(), zero, i+x(), m_data.inf[i]); } } /* -------------------------------------------------------------------------- */ void geWaveform::drawGrid(int from, int to) { fl_color(G_COLOR_GREY_3); fl_line_style(FL_DASH, 1, nullptr); for (int pf : m_grid.points) { int pp = frameToPixel(pf); if (pp > from && pp < to) fl_line(pp+x(), y(), pp+x(), y()+h()); } fl_line_style(FL_SOLID, 0, nullptr); } /* -------------------------------------------------------------------------- */ void geWaveform::drawStartEndPoints() { /* print m_chanStart */ int lineX = frameToPixel(m_chanStart) + x(); if (m_chanStartLit) fl_color(G_COLOR_LIGHT_2); else fl_color(G_COLOR_LIGHT_1); /* vertical line */ fl_line(lineX, y()+1, lineX, y()+h()-2); /* print flag and avoid overflow */ if (lineX+FLAG_WIDTH > w()+x()-2) fl_rectf(lineX, y()+h()-FLAG_HEIGHT-1, w()-lineX+x()-1, FLAG_HEIGHT); else fl_rectf(lineX, y()+h()-FLAG_HEIGHT-1, FLAG_WIDTH, FLAG_HEIGHT); /* print m_chanEnd */ lineX = frameToPixel(m_chanEnd) + x() - 1; if (m_chanEndLit) fl_color(G_COLOR_LIGHT_2); else fl_color(G_COLOR_LIGHT_1); /* vertical line */ fl_line(lineX, y()+1, lineX, y()+h()-2); if (lineX-FLAG_WIDTH < x()) fl_rectf(x()+1, y()+1, lineX-x(), FLAG_HEIGHT); else fl_rectf(lineX-FLAG_WIDTH, y()+1, FLAG_WIDTH, FLAG_HEIGHT); } /* -------------------------------------------------------------------------- */ void geWaveform::drawPlayHead() { int p = frameToPixel(m_ch->getTrackerPreview()) + x(); fl_color(G_COLOR_LIGHT_2); fl_line(p, y() + 1, p, y() + h() - 2); } /* -------------------------------------------------------------------------- */ void geWaveform::draw() { assert(m_data.sup != nullptr); assert(m_data.inf != nullptr); fl_rectf(x(), y(), w(), h(), G_COLOR_GREY_2); // blank canvas /* Draw things from 'from' (offset driven by the scrollbar) to 'to' (width of parent window). We don't draw the entire waveform, only the visibile part. */ int from = abs(x() - parent()->x()); int to = from + parent()->w(); if (x() + w() < parent()->w()) to = x() + w() - BORDER; drawSelection(); drawWaveform(from, to); drawGrid(from, to); drawPlayHead(); fl_rect(x(), y(), w(), h(), G_COLOR_GREY_4); // border box drawStartEndPoints(); } /* -------------------------------------------------------------------------- */ int geWaveform::handle(int e) { m_mouseX = pixelToFrame(Fl::event_x() - x()); m_mouseY = pixelToFrame(Fl::event_y() - y()); switch (e) { case FL_KEYDOWN: { if (Fl::event_key() == ' ') static_cast(window())->cb_togglePreview(); else if (Fl::event_key() == FL_BackSpace) sampleEditor::rewindPreview(m_ch); return 1; } case FL_PUSH: { if (Fl::event_clicks() > 0) { selectAll(); return 1; } m_pushed = true; if (!mouseOnEnd() && !mouseOnStart()) { if (Fl::event_button3()) // let the parent (waveTools) handle this return 0; if (mouseOnSelectionA()) m_resizedA = true; else if(mouseOnSelectionB()) m_resizedB = true; else { m_dragged = true; m_selection.a = m_mouseX; m_selection.b = m_mouseX; } } return 1; } case FL_RELEASE: { sampleEditor::setPlayHead(m_ch, m_mouseX); /* If selection has been done (m_dragged or resized), make sure that point A is always lower than B. */ if (m_dragged || m_resizedA || m_resizedB) fixSelection(); /* Handle begin/end markers interaction. */ if (m_chanStartLit || m_chanEndLit) sampleEditor::setBeginEnd(m_ch, m_chanStart, m_chanEnd); m_pushed = false; m_dragged = false; m_resizedA = false; m_resizedB = false; redraw(); return 1; } case FL_ENTER: { // enables FL_DRAG return 1; } case FL_LEAVE: { if (m_chanStartLit || m_chanEndLit) { m_chanStartLit = false; m_chanEndLit = false; redraw(); } return 1; } case FL_MOVE: { if (mouseOnStart()) { m_chanStartLit = true; redraw(); } else if (m_chanStartLit) { m_chanStartLit = false; redraw(); } if (mouseOnEnd()) { m_chanEndLit = true; redraw(); } else if (m_chanEndLit) { m_chanEndLit = false; redraw(); } if (mouseOnSelectionA() && isSelected()) fl_cursor(FL_CURSOR_WE, FL_WHITE, FL_BLACK); else if (mouseOnSelectionB() && isSelected()) fl_cursor(FL_CURSOR_WE, FL_WHITE, FL_BLACK); else fl_cursor(FL_CURSOR_DEFAULT, FL_WHITE, FL_BLACK); return 1; } case FL_DRAG: { /* here the mouse is on the m_chanStart tool */ if (m_chanStartLit && m_pushed) { m_chanStart = snap(m_mouseX); if (m_chanStart < 0) m_chanStart = 0; else if (m_chanStart >= m_chanEnd) m_chanStart = m_chanEnd - 2; redraw(); } else if (m_chanEndLit && m_pushed) { m_chanEnd = snap(m_mouseX); if (m_chanEnd > m_ch->wave->getSize()) m_chanEnd = m_ch->wave->getSize(); else if (m_chanEnd <= m_chanStart) m_chanEnd = m_chanStart + 2; redraw(); } /* Here the mouse is on the waveform, i.e. a new selection has started. */ else if (m_dragged) { m_selection.b = snap(m_mouseX); redraw(); } /* here the mouse is on a selection boundary i.e. resize */ else if (m_resizedA || m_resizedB) { int pos = snap(m_mouseX); m_resizedA ? m_selection.a = pos : m_selection.b = pos; redraw(); } return 1; } default: return Fl_Widget::handle(e); } } /* -------------------------------------------------------------------------- */ int geWaveform::snap(int pos) { if (!m_grid.snap) return pos; for (int pf : m_grid.points) { if (pos >= pf - pixelToFrame(SNAPPING) && pos <= pf + pixelToFrame(SNAPPING)) { return pf; } } return pos; } /* -------------------------------------------------------------------------- */ bool geWaveform::mouseOnStart() { int mouseXp = frameToPixel(m_mouseX); int mouseYp = frameToPixel(m_mouseY); int chanStartP = frameToPixel(m_chanStart); return mouseXp - (FLAG_WIDTH / 2) > chanStartP - BORDER && mouseXp - (FLAG_WIDTH / 2) <= chanStartP - BORDER + FLAG_WIDTH && mouseYp > h() - FLAG_HEIGHT; } /* -------------------------------------------------------------------------- */ bool geWaveform::mouseOnEnd() { int mouseXp = frameToPixel(m_mouseX); int mouseYp = frameToPixel(m_mouseY); int chanEndP = frameToPixel(m_chanEnd); return mouseXp - (FLAG_WIDTH / 2) >= chanEndP - BORDER - FLAG_WIDTH && mouseXp - (FLAG_WIDTH / 2) <= chanEndP - BORDER && mouseYp <= FLAG_HEIGHT + 1; } /* -------------------------------------------------------------------------- */ bool geWaveform::mouseOnSelectionA() { int mouseXp = frameToPixel(m_mouseX); int selAp = frameToPixel(m_selection.a); return mouseXp >= selAp - (FLAG_WIDTH / 2) && mouseXp <= selAp + (FLAG_WIDTH / 2); } bool geWaveform::mouseOnSelectionB() { int mouseXp = frameToPixel(m_mouseX); int selBp = frameToPixel(m_selection.b); return mouseXp >= selBp - (FLAG_WIDTH / 2) && mouseXp <= selBp + (FLAG_WIDTH / 2); } /* -------------------------------------------------------------------------- */ int geWaveform::pixelToFrame(int p) { if (p <= 0) return 0; if (p > m_data.size) return m_ch->wave->getSize() - 1; return p * m_ratio; } /* -------------------------------------------------------------------------- */ int geWaveform::frameToPixel(int p) { return ceil(p / m_ratio); } /* -------------------------------------------------------------------------- */ void geWaveform::fixSelection() { if (m_selection.a > m_selection.b) // inverted m_selection std::swap(m_selection.a, m_selection.b); sampleEditor::setPlayHead(m_ch, m_selection.a); } /* -------------------------------------------------------------------------- */ void geWaveform::clearSel() { m_selection.a = 0; m_selection.b = 0; } /* -------------------------------------------------------------------------- */ void geWaveform::setZoom(int type) { if (!alloc(type == ZOOM_IN ? m_data.size * 2 : m_data.size / 2)) return; size(m_data.size, h()); /* Zoom to cursor. */ int newX = -frameToPixel(m_mouseX) + Fl::event_x(); if (newX > BORDER) newX = BORDER; position(newX, y()); /* Avoid overflow when zooming out with scrollbar like that: |----------[scrollbar]| Offset vs smaller: |[wave------------| offset > 0 smaller = false |[wave----] | offset < 0, smaller = true |-------------] | offset < 0, smaller = false */ int parentW = parent()->w(); int thisW = x() + w() - BORDER; // visible width, not full width if (thisW < parentW) position(x() + parentW - thisW, y()); if (smaller()) stretchToWindow(); redraw(); } /* -------------------------------------------------------------------------- */ void geWaveform::stretchToWindow() { int s = parent()->w(); alloc(s); position(BORDER, y()); size(s, h()); } /* -------------------------------------------------------------------------- */ void geWaveform::refresh() { alloc(m_data.size, true); // force redraw(); } /* -------------------------------------------------------------------------- */ bool geWaveform::smaller() { return w() < parent()->w(); } /* -------------------------------------------------------------------------- */ void geWaveform::setGridLevel(int l) { m_grid.points.clear(); m_grid.level = l; alloc(m_data.size, true); // force alloc redraw(); } /* -------------------------------------------------------------------------- */ bool geWaveform::isSelected() { return m_selection.a != m_selection.b; } /* -------------------------------------------------------------------------- */ void geWaveform::setSnap(bool v) { m_grid.snap = v; } bool geWaveform::getSnap() { return m_grid.snap; } int geWaveform::getSize() { return m_data.size; } /* -------------------------------------------------------------------------- */ int geWaveform::getSelectionA() { return m_selection.a; } int geWaveform::getSelectionB() { return m_selection.b; } void geWaveform::selectAll() { m_selection.a = 0; m_selection.b = m_ch->wave->getSize() - 1; redraw(); } giada-0.14.5/src/gui/elems/sampleEditor/waveform.h000066400000000000000000000103241322662744500217700ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef GE_WAVEFORM_H #define GE_WAVEFORM_H #include #include class SampleChannel; class geWaveform : public Fl_Widget { private: static const int FLAG_WIDTH = 20; static const int FLAG_HEIGHT = 20; static const int BORDER = 8; // window border <-> widget border static const int SNAPPING = 16; /* selection Portion of the selected wave, in frames. */ struct { int a; int b; } m_selection; /* data Real graphic stuff from the underlying waveform. */ struct { int* sup; // upper part of the waveform int* inf; // lower part of the waveform int size; // width of the waveform to draw (in pixel) } m_data; struct { bool snap; int level; std::vector points; } m_grid; SampleChannel* m_ch; int m_chanStart; bool m_chanStartLit; int m_chanEnd; bool m_chanEndLit; bool m_pushed; bool m_dragged; bool m_resizedA; bool m_resizedB; float m_ratio; int m_mouseX; int m_mouseY; /* mouseOnStart/end Is mouse on start or end flag? */ bool mouseOnStart(); bool mouseOnEnd(); /* mouseOnSelectionA/B As above, for the selection. */ bool mouseOnSelectionA(); bool mouseOnSelectionB(); int pixelToFrame(int p); // TODO - move these to utils::, will be needed in actionEditor int frameToPixel(int f); // TODO - move these to utils::, will be needed in actionEditor /* fixSelection Helper function which flattens the selection if it was made from right to left (inverse selection). It also computes the absolute points. Call this one whenever the selection gesture is done. */ void fixSelection(); /* freeData Destroys any graphical buffer. */ void freeData(); /* smaller Is the waveform smaller than the parent window? */ bool smaller(); /* snap Snaps a point at 'pos' pixel. */ int snap(int pos); /* draw* Drawing functions. */ void drawSelection(); void drawWaveform(int from, int to); void drawGrid(int from, int to); void drawStartEndPoints(); void drawPlayHead(); void selectAll(); public: static const int ZOOM_IN = -1; static const int ZOOM_OUT = 0; geWaveform(int x, int y, int w, int h, SampleChannel* ch, const char* l=0); ~geWaveform(); void draw() override; int handle(int e) override; /* alloc Allocates memory for the picture. It's smart enough not to reallocate if datasize hasn't changed, but it can be forced otherwise. */ int alloc(int datasize, bool force=false); /* recalcPoints * re-calc m_chanStart, m_chanEnd, ... */ void recalcPoints(); /* zoom * type == 1 : zoom out, type == -1: zoom in */ void setZoom(int type); /* strecthToWindow * shrink or enlarge the waveform to match parent's width (gWaveTools) */ void stretchToWindow(); /* refresh Redraws the waveform. */ void refresh(); /* setGridLevel * set a new frequency level for the grid. 0 means disabled. */ void setGridLevel(int l); void setSnap(bool v); bool getSnap(); int getSize(); /* isSelected Tells whether a portion of the waveform has been selected. */ bool isSelected(); int getSelectionA(); int getSelectionB(); /* clearSel Removes any active selection. */ void clearSel(); }; #endif giada-0.14.5/src/gui/elems/soundMeter.cpp000066400000000000000000000046341322662744500202010ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include #include #include "../../core/const.h" #include "../../core/kernelAudio.h" #include "soundMeter.h" using namespace giada::m; geSoundMeter::geSoundMeter(int x, int y, int w, int h, const char *L) : Fl_Box (x, y, w, h, L), clip (false), mixerPeak (0.0f), peak (0.0f), dbLevel (0.0f), dbLevelOld(0.0f) { } /* -------------------------------------------------------------------------- */ void geSoundMeter::draw() { fl_rect(x(), y(), w(), h(), G_COLOR_GREY_4); /* peak = the highest value inside the frame */ peak = 0.0f; float tmp_peak = 0.0f; tmp_peak = fabs(mixerPeak); if (tmp_peak > peak) peak = tmp_peak; clip = peak >= 1.0f ? true : false; // 1.0f is considered clip /* dBFS (full scale) calculation, plus decay of -2dB per frame */ dbLevel = 20 * log10(peak); if (dbLevel < dbLevelOld) if (dbLevelOld > -G_MIN_DB_SCALE) dbLevel = dbLevelOld - 2.0f; dbLevelOld = dbLevel; /* graphical part */ float px_level = 0.0f; if (dbLevel < 0.0f) px_level = ((w()/G_MIN_DB_SCALE) * dbLevel) + w(); else px_level = w(); fl_rectf(x()+1, y()+1, w()-2, h()-2, G_COLOR_GREY_2); fl_rectf(x()+1, y()+1, (int) px_level, h()-2, clip || !kernelAudio::getStatus() ? G_COLOR_RED_ALERT : G_COLOR_GREY_4); } giada-0.14.5/src/gui/elems/soundMeter.h000066400000000000000000000026641322662744500176470ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef GE_SOUND_METER_H #define GE_SOUND_METER_H #include class geSoundMeter : public Fl_Box { public: geSoundMeter(int X, int Y, int W, int H, const char *L=0); void draw() override; bool clip; float mixerPeak; // peak from mixer private: float peak; float dbLevel; float dbLevelOld; }; #endif giada-0.14.5/src/main.cpp000066400000000000000000000046051322662744500151050ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include #if defined(__linux__) || defined(__APPLE__) #include #endif #include #include "core/init.h" #include "core/const.h" #include "core/patch.h" #include "core/conf.h" #include "core/midiMapConf.h" #include "core/mixer.h" #include "core/clock.h" #include "core/mixerHandler.h" #include "core/kernelAudio.h" #include "core/kernelMidi.h" #include "core/recorder.h" #include "utils/gui.h" #include "gui/dialogs/gd_mainWindow.h" #include "core/pluginHost.h" pthread_t G_videoThread; bool G_quit; gdMainWindow *G_MainWin; void *videoThreadCb(void *arg); int main(int argc, char **argv) { G_quit = false; init_prepareParser(); init_prepareMidiMap(); init_prepareKernelAudio(); init_prepareKernelMIDI(); init_startGUI(argc, argv); Fl::lock(); pthread_create(&G_videoThread, nullptr, videoThreadCb, nullptr); init_startKernelAudio(); #ifdef WITH_VST juce::initialiseJuce_GUI(); #endif int ret = Fl::run(); #ifdef WITH_VST juce::shutdownJuce_GUI(); #endif pthread_join(G_videoThread, nullptr); return ret; } void *videoThreadCb(void *arg) { if (giada::m::kernelAudio::getStatus()) while (!G_quit) { gu_refreshUI(); #ifdef _WIN32 Sleep(G_GUI_SLEEP); #else usleep(G_GUI_SLEEP); #endif } pthread_exit(nullptr); return 0; } giada-0.14.5/src/utils/000077500000000000000000000000001322662744500146105ustar00rootroot00000000000000giada-0.14.5/src/utils/cocoa.h000066400000000000000000000030101322662744500160370ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * cocoa * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef G_UTILS_COCOA_H #define G_UTILS_COCOA_H /* fl_xid() from FLTK returns a pointer to NSWindow, but plugins on OS X want a * pointer to NSView. The function does the hard conversion. */ void *cocoa_getViewFromWindow(void *p); /* A bug on on OS X seems to misalign plugins' UI. The function takes care of * fixing the positioning. */ void cocoa_setWindowSize(void *p, int w, int h); #endif giada-0.14.5/src/utils/cocoa.mm000066400000000000000000000026701322662744500162340ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * cocoa * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #import #import #import "cocoa.h" void *cocoa_getViewFromWindow(void *p) { NSWindow *win = (NSWindow *) p; return (void*) [win contentView]; } void cocoa_setWindowSize(void *p, int w, int h) { NSWindow *win = (NSWindow *) p; [win setContentSize:NSMakeSize(w, h)]; } giada-0.14.5/src/utils/deps.cpp000066400000000000000000000035001322662744500162450ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include "../core/const.h" #ifdef G_OS_MAC #include #else #include #endif #include #include "../deps/rtaudio-mod/RtAudio.h" #include "deps.h" using std::string; namespace giada { namespace u { namespace deps { string getLibsndfileVersion() { char buffer[128]; sf_command(NULL, SFC_GET_LIB_VERSION, buffer, sizeof(buffer)); return string(buffer); } /* -------------------------------------------------------------------------- */ string getRtAudioVersion() { return RtAudio::getVersion(); } /* -------------------------------------------------------------------------- */ string getRtMidiVersion() { return RtMidi::getVersion(); } }}}; // giada::u::deps::giada-0.14.5/src/utils/deps.h000066400000000000000000000025511322662744500157170ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef G_UTILS_DEPS_H #define G_UTILS_DEPS_H #include namespace giada { namespace u { namespace deps { std::string getLibsndfileVersion(); std::string getRtAudioVersion(); std::string getRtMidiVersion(); }}}; // giada::u::deps:: #endifgiada-0.14.5/src/utils/fs.cpp000066400000000000000000000130151322662744500157240ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * utils * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #if defined(_WIN32) // getcwd (unix) or __getcwd (win) #include #include #else #include #endif #include #include // stat (gu_dirExists) #include #include #ifdef __APPLE__ // our Clang still doesn't know about cstdint (c++11 stuff) #include #else #include #endif #include #include #include #ifdef __APPLE__ #include // basename unix #include // getpwuid #endif #include "../core/const.h" #include "string.h" #include "log.h" #include "fs.h" using std::string; using std::vector; bool gu_fileExists(const string &filename) { FILE *fh = fopen(filename.c_str(), "rb"); if (!fh) { return 0; } else { fclose(fh); return 1; } } /* -------------------------------------------------------------------------- */ bool gu_isDir(const string &path) { bool ret; #if defined(__linux__) struct stat s1; stat(path.c_str(), &s1); ret = S_ISDIR(s1.st_mode); #elif defined(__APPLE__) if (strcmp(path.c_str(), "") == 0) ret = false; else { struct stat s1; stat(path.c_str(), &s1); ret = S_ISDIR(s1.st_mode); /* check if ret is a bundle, a special OS X folder which must be * shown as a regular file (VST). * FIXME - consider native functions CFBundle... */ if (ret) { if (gu_fileExists(path + "/Contents/Info.plist")) ret = false; } } #elif defined(__WIN32) unsigned dwAttrib = GetFileAttributes(path.c_str()); ret = (dwAttrib != INVALID_FILE_ATTRIBUTES && (dwAttrib & FILE_ATTRIBUTE_DIRECTORY)); #endif return ret & !gu_isProject(path); } /* -------------------------------------------------------------------------- */ bool gu_dirExists(const string &path) { struct stat st; if (stat(path.c_str(), &st) != 0 && errno == ENOENT) return false; return true; } /* -------------------------------------------------------------------------- */ bool gu_mkdir(const string &path) { #if defined(__linux__) || defined(__APPLE__) if (mkdir(path.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) == 0) #else if (_mkdir(path.c_str()) == 0) #endif return true; return false; } /* -------------------------------------------------------------------------- */ string gu_basename(const string &s) { string out = s; out.erase(0, out.find_last_of(G_SLASH_STR) + 1); return out; } /* -------------------------------------------------------------------------- */ string gu_dirname(const string &path) { if (path.empty()) return ""; string out = path; out.erase(out.find_last_of(G_SLASH_STR)); return out; } /* -------------------------------------------------------------------------- */ string gu_getCurrentPath() { char buf[PATH_MAX]; #if defined(__WIN32) if (_getcwd(buf, PATH_MAX) != nullptr) #else if (getcwd(buf, PATH_MAX) != nullptr) #endif return buf; else return ""; } /* -------------------------------------------------------------------------- */ string gu_getExt(const string &file) { // TODO - use std functions int len = strlen(file.c_str()); int pos = len; while (pos>0) { if (file[pos] == '.') break; pos--; } if (pos==0) return ""; string out = file; return out.substr(pos+1, len); } /* -------------------------------------------------------------------------- */ string gu_stripExt(const string &s) { return s.substr(0, s.find_last_of(".")); } /* -------------------------------------------------------------------------- */ bool gu_isProject(const string &path) { /** FIXME - checks too weak */ if (gu_getExt(path.c_str()) == "gprj" && gu_dirExists(path)) return 1; return 0; } /* -------------------------------------------------------------------------- */ string gu_stripFileUrl(const string &f) { string out = f; out = gu_replace(out, "file://", ""); out = gu_replace(out, "%20", " "); return out; } /* -------------------------------------------------------------------------- */ string gu_getHomePath() { char path[PATH_MAX]; #if defined(__linux__) snprintf(path, PATH_MAX, "%s/.giada", getenv("HOME")); #elif defined(_WIN32) snprintf(path, PATH_MAX, "."); #elif defined(__APPLE__) struct passwd *p = getpwuid(getuid()); if (p == nullptr) { gu_log("[gu_getHomePath] unable to fetch user infos\n"); return ""; } else { const char *home = p->pw_dir; snprintf(path, PATH_MAX, "%s/Library/Application Support/Giada", home); } #endif return string(path); } giada-0.14.5/src/utils/fs.h000066400000000000000000000036041322662744500153740ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * utils * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef G_UTILS_FS_H #define G_UTILS_FS_H #include bool gu_fileExists(const std::string& path); bool gu_dirExists(const std::string& path); bool gu_isDir(const std::string& path); bool gu_isProject(const std::string& path); bool gu_mkdir(const std::string& path); std::string gu_getCurrentPath(); std::string gu_getHomePath(); /* gu_basename /path/to/file.txt -> file.txt */ std::string gu_basename(const std::string& s); /* gu_dirname /path/to/file.txt -> /path/to */ std::string gu_dirname(const std::string& s); /* gu_getExt /path/to/file.txt -> txt */ std::string gu_getExt(const std::string& s); /* gu_stripExt /path/to/file.txt -> /path/to/file */ std::string gu_stripExt(const std::string& s); std::string gu_stripFileUrl(const std::string& s); #endif giada-0.14.5/src/utils/gui.cpp000066400000000000000000000140501322662744500161000ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include #include #if defined(_WIN32) #include "../ext/resource.h" #elif defined(__linux__) #include #endif #include "../core/mixer.h" #include "../core/recorder.h" #include "../core/wave.h" #include "../core/clock.h" #include "../core/pluginHost.h" #include "../core/channel.h" #include "../core/conf.h" #include "../core/graphics.h" #include "../gui/dialogs/gd_warnings.h" #include "../gui/dialogs/gd_mainWindow.h" #include "../gui/dialogs/gd_actionEditor.h" #include "../gui/dialogs/window.h" #include "../gui/dialogs/sampleEditor.h" #include "../gui/elems/mainWindow/mainIO.h" #include "../gui/elems/mainWindow/mainTimer.h" #include "../gui/elems/mainWindow/mainTransport.h" #include "../gui/elems/mainWindow/beatMeter.h" #include "../gui/elems/mainWindow/keyboard/keyboard.h" #include "../gui/elems/mainWindow/keyboard/channel.h" #include "../gui/elems/sampleEditor/waveTools.h" #include "log.h" #include "string.h" #include "gui.h" extern gdMainWindow *G_MainWin; using std::string; using namespace giada::m; static int blinker = 0; void gu_refreshUI() { Fl::lock(); /* update dynamic elements: in and out meters, beat meter and * each channel */ G_MainWin->mainIO->refresh(); G_MainWin->beatMeter->redraw(); G_MainWin->keyboard->refreshColumns(); /* compute timer for blinker */ blinker++; if (blinker > 12) blinker = 0; /* If Sample Editor is open, repaint it (for dynamic play head). */ gdSampleEditor* se = static_cast(gu_getSubwindow(G_MainWin, WID_SAMPLE_EDITOR)); if (se != nullptr) se->waveTools->redrawWaveformAsync(); /* redraw GUI */ Fl::unlock(); Fl::awake(); } /* -------------------------------------------------------------------------- */ int gu_getBlinker() { return blinker; } /* -------------------------------------------------------------------------- */ void gu_updateControls() { for (unsigned i=0; iguiChannel->update(); G_MainWin->mainIO->setOutVol(mixer::outVol); G_MainWin->mainIO->setInVol(mixer::inVol); #ifdef WITH_VST G_MainWin->mainIO->setMasterFxOutFull(pluginHost::getStack(pluginHost::MASTER_OUT)->size() > 0); G_MainWin->mainIO->setMasterFxInFull(pluginHost::getStack(pluginHost::MASTER_IN)->size() > 0); #endif G_MainWin->mainTimer->setMeter(clock::getBeats(), clock::getBars()); G_MainWin->mainTimer->setBpm(clock::getBpm()); G_MainWin->mainTransport->updatePlay(clock::isRunning()); G_MainWin->mainTransport->updateMetronome(mixer::metronome); } /* -------------------------------------------------------------------------- */ void gu_updateMainWinLabel(const string &s) { std::string out = std::string(G_APP_NAME) + " - " + s; G_MainWin->copy_label(out.c_str()); } /* -------------------------------------------------------------------------- */ void gu_setFavicon(Fl_Window *w) { #if defined(__linux__) fl_open_display(); Pixmap p, mask; XpmCreatePixmapFromData(fl_display, DefaultRootWindow(fl_display), (char **) giada_icon, &p, &mask, nullptr); w->icon((char *)p); #elif defined(_WIN32) w->icon((char *)LoadIcon(fl_display, MAKEINTRESOURCE(IDI_ICON1))); #endif } /* -------------------------------------------------------------------------- */ void gu_openSubWindow(gdWindow *parent, gdWindow *child, int id) { if (parent->hasWindow(id)) { gu_log("[GU] parent has subwindow with id=%d, deleting\n", id); parent->delSubWindow(id); } child->setId(id); parent->addSubWindow(child); } /* -------------------------------------------------------------------------- */ void gu_refreshActionEditor() { /** TODO - why don't we simply call WID_ACTION_EDITOR->redraw()? */ gdActionEditor *aeditor = (gdActionEditor*) G_MainWin->getChild(WID_ACTION_EDITOR); if (aeditor) { Channel *chan = aeditor->chan; G_MainWin->delSubWindow(WID_ACTION_EDITOR); gu_openSubWindow(G_MainWin, new gdActionEditor(chan), WID_ACTION_EDITOR); } } /* -------------------------------------------------------------------------- */ gdWindow *gu_getSubwindow(gdWindow *parent, int id) { if (parent->hasWindow(id)) return parent->getChild(id); else return nullptr; } /* -------------------------------------------------------------------------- */ void gu_closeAllSubwindows() { /* don't close WID_FILE_BROWSER, because it's the caller of this * function */ G_MainWin->delSubWindow(WID_ACTION_EDITOR); G_MainWin->delSubWindow(WID_SAMPLE_EDITOR); G_MainWin->delSubWindow(WID_FX_LIST); G_MainWin->delSubWindow(WID_FX); } /* -------------------------------------------------------------------------- */ int gu_getStringWidth(const std::string &s) { int w = 0; int h = 0; fl_measure(s.c_str(), w, h); return w; } /* -------------------------------------------------------------------------- */ string gu_removeFltkChars(const string &s) { string out = gu_replace(s, "/", "-"); out = gu_replace(out, "|", "-"); out = gu_replace(out, "&", "-"); out = gu_replace(out, "_", "-"); return out; } giada-0.14.5/src/utils/gui.h000066400000000000000000000045661322662744500155600ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef G_UTILS_GUI_H #define G_UTILS_GUI_H #include class Fl_Window; class gdWindow; /* refresh * refresh all GUI elements. */ void gu_refreshUI(); /* getBlinker * return blinker value, used to make widgets blink. */ int gu_getBlinker(); /* updateControls * update attributes of control elements (sample names, volumes, ...). * Useful when loading a new patch. */ void gu_updateControls(); /* update_win_label * update the name of the main window */ void gu_updateMainWinLabel(const std::string &s); void gu_setFavicon(Fl_Window *w); void gu_openSubWindow(gdWindow *parent, gdWindow *child, int id); /* refreshActionEditor * reload the action editor window by closing and reopening it. It's used * when you delete some actions from the mainWindow and the action editor * window is open. */ void gu_refreshActionEditor(); /* closeAllSubwindows * close all subwindows attached to mainWin. */ void gu_closeAllSubwindows(); /* getSubwindow * return a pointer to an open subwindow, otherwise nullptr. */ gdWindow *gu_getSubwindow(gdWindow *parent, int id); /* removeFltkChars * Strip special chars used by FLTK to split menus into sub-menus. */ std::string gu_removeFltkChars(const std::string &s); int gu_getStringWidth(const std::string &s); #endif giada-0.14.5/src/utils/log.cpp000066400000000000000000000040531322662744500160770ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * log * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include #include #include #include "../utils/fs.h" #include "../core/const.h" #include "log.h" using std::string; static FILE *f; static int mode; static bool stat; int gu_logInit(int m) { mode = m; stat = true; if (mode == LOG_MODE_FILE) { string fpath = gu_getHomePath() + G_SLASH + "giada.log"; f = fopen(fpath.c_str(), "a"); if (!f) { stat = false; return 0; } } return 1; } /* -------------------------------------------------------------------------- */ void gu_logClose() { if (mode == LOG_MODE_FILE) fclose(f); } /* -------------------------------------------------------------------------- */ void gu_log(const char *format, ...) { if (mode == LOG_MODE_MUTE) return; va_list args; va_start(args, format); if (mode == LOG_MODE_FILE && stat == true) { vfprintf(f, format, args); #ifdef _WIN32 fflush(f); #endif } else vprintf(format, args); va_end(args); } giada-0.14.5/src/utils/log.h000066400000000000000000000025721322662744500155500ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * log * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef G_UTILS_LOG_H #define G_UTILS_LOG_H /* init * init logger. Mode defines where to write the output: LOG_MODE_STDOUT, * LOG_MODE_FILE and LOG_MODE_MUTE. */ int gu_logInit(int mode); void gu_logClose(); void gu_log(const char *format, ...); #endif giada-0.14.5/src/utils/math.cpp000066400000000000000000000025431322662744500162510ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include #include "math.h" float gu_linearToDB(float f) { return 20 * std::log10(f); } /* -------------------------------------------------------------------------- */ float gu_dBtoLinear(float f) { return std::pow(10, f/20.0f); } giada-0.14.5/src/utils/math.h000066400000000000000000000023371322662744500157170ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef G_UTILS_MATH_H #define G_UTILS_MATH_H float gu_linearToDB(float f); float gu_dBtoLinear(float f); #endif giada-0.14.5/src/utils/string.cpp000066400000000000000000000066021322662744500166260ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * utils * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include #include #include #include "../core/const.h" #include "string.h" using std::string; using std::vector; string gu_getRealPath(const string& path) { string out = ""; #if defined(G_OS_LINUX) || defined(G_OS_MAC) char *buf = realpath(path.c_str(), nullptr); #else // Windows char *buf = _fullpath(nullptr, path.c_str(), PATH_MAX); #endif if (buf) { out = buf; free(buf); } return out; } /* -------------------------------------------------------------------------- */ /* TODO - use std::to_string() */ string gu_fToString(float f, int precision) { std::stringstream out; out << std::fixed << std::setprecision(precision) << f; return out.str(); } /* -------------------------------------------------------------------------- */ string gu_trim(const string& s) { std::size_t first = s.find_first_not_of(" \n\t"); std::size_t last = s.find_last_not_of(" \n\t"); return s.substr(first, last-first+1); } /* -------------------------------------------------------------------------- */ string gu_replace(string in, const string& search, const string& replace) { size_t pos = 0; while ((pos = in.find(search, pos)) != string::npos) { in.replace(pos, search.length(), replace); pos += replace.length(); } return in; } /* -------------------------------------------------------------------------- */ std::string gu_format(const char* format, ...) { va_list args; /* Compute the size of the new expanded string (i.e. with replacement taken into account). */ size_t size = vsnprintf(nullptr, 0, format, args); /* Create a new temporary char array to hold the new expanded string. */ std::unique_ptr tmp(new char[size]); /* Fill the temporary string with the formatted data. */ va_start(args, format); vsprintf(tmp.get(), format, args); va_end(args); return string(tmp.get(), tmp.get() + size - 1); } /* -------------------------------------------------------------------------- */ void gu_split(string in, string sep, vector* v) { string full = in; string token = ""; size_t curr = 0; size_t next = -1; do { curr = next + 1; next = full.find_first_of(sep, curr); token = full.substr(curr, next - curr); if (token != "") v->push_back(token); } while (next != string::npos); } giada-0.14.5/src/utils/string.h000066400000000000000000000034661322662744500163000ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * utils * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef G_UTILS_STRING_H #define G_UTILS_STRING_H #include #include #include #include "../core/const.h" template std::string gu_iToString(T t, bool hex=false) { std::stringstream out; if (hex) out << std::hex << std::uppercase << t; else out << t; return out.str(); } std::string gu_getRealPath(const std::string& path); std::string gu_replace(std::string in, const std::string& search, const std::string& replace); std::string gu_trim(const std::string& s); void gu_split(std::string in, std::string sep, std::vector* v); std::string gu_fToString(float f, int precision); std::string gu_format(const char* format, ...); #endif giada-0.14.5/src/utils/time.cpp000066400000000000000000000027501322662744500162560ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * utils * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #include "../core/const.h" #ifndef G_OS_MAC #include #include #else #include #endif #include "time.h" namespace giada { namespace u { namespace time { void sleep(int millisecs) { #ifndef G_OS_MAC std::this_thread::sleep_for(std::chrono::milliseconds(millisecs)); #else usleep(millisecs * 1000); #endif } }}}; // giada::u::time:: giada-0.14.5/src/utils/time.h000066400000000000000000000024031322662744500157160ustar00rootroot00000000000000/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * utils * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual * * This file is part of Giada - Your Hardcore Loopmachine. * * Giada - Your Hardcore Loopmachine 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. * * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see * . * * -------------------------------------------------------------------------- */ #ifndef G_UTILS_TIME_H #define G_UTILS_TIME_H namespace giada { namespace u { namespace time { void sleep(int millisecs); }}}; #endifgiada-0.14.5/tests/000077500000000000000000000000001322662744500140235ustar00rootroot00000000000000giada-0.14.5/tests/catch/000077500000000000000000000000001322662744500151055ustar00rootroot00000000000000giada-0.14.5/tests/conf.cpp000066400000000000000000000121031322662744500154510ustar00rootroot00000000000000#include "../src/core/const.h" #include "../src/core/conf.h" #include "catch/single_include/catch.hpp" using std::string; using namespace giada::m; TEST_CASE("Test Conf class") { conf::init(); SECTION("test write") { conf::header = "GIADACONFTEST"; conf::logMode = 1; conf::soundSystem = 2; conf::soundDeviceOut = 3; conf::soundDeviceIn = 4; conf::channelsOut = 5; conf::channelsIn = 6; conf::samplerate = 7; conf::buffersize = 8; conf::delayComp = 9; conf::limitOutput = true; conf::rsmpQuality = 10; conf::midiSystem = 11; conf::midiPortOut = 12; conf::midiPortIn = 13; conf::noNoteOff = false; conf::midiMapPath = "path/to/midi/map"; conf::lastFileMap = "path/to/last/midi/map"; conf::midiSync = 14; conf::midiTCfps = 15.1f; conf::midiInRewind = 16; conf::midiInStartStop = 17; conf::midiInActionRec = 18; conf::midiInInputRec = 19; conf::midiInMetronome = 20; conf::midiInVolumeIn = 21; conf::midiInVolumeOut = 22; conf::midiInBeatDouble = 23; conf::midiInBeatHalf = 24; conf::recsStopOnChanHalt = true; conf::chansStopOnSeqHalt = false; conf::treatRecsAsLoops = true; conf::resizeRecordings = false; conf::pluginPath = "path/to/plugins"; conf::patchPath = "path/to/patches"; conf::samplePath = "path/to/samples"; conf::mainWindowX = 0; conf::mainWindowY = 0; conf::mainWindowW = 800; conf::mainWindowH = 600; conf::browserX = 0; conf::browserY = 0; conf::browserW = 800; conf::browserH = 600; conf::actionEditorX = 0; conf::actionEditorY = 0; conf::actionEditorW = 800; conf::actionEditorH = 600; conf::actionEditorZoom = 1; conf::actionEditorGridVal = 10; conf::actionEditorGridOn = 1; conf::sampleEditorX = 0; conf::sampleEditorY = 0; conf::sampleEditorW = 800; conf::sampleEditorH = 600; conf::sampleEditorGridVal = 4; conf::sampleEditorGridOn = 0; conf::pianoRollY = 0; conf::pianoRollH = 900; conf::pluginListX = 0; conf::pluginListY = 50; conf::configX = 20; conf::configY = 20; conf::bpmX = 30; conf::bpmY = 36; conf::beatsX = 1; conf::beatsY = 1; conf::aboutX = 2; conf::aboutY = 2; REQUIRE(conf::write() == 1); } SECTION("test read") { REQUIRE(conf::read() == 1); REQUIRE(conf::header == "GIADACONFTEST"); REQUIRE(conf::logMode == 1); REQUIRE(conf::soundSystem == 2); REQUIRE(conf::soundDeviceOut == 3); REQUIRE(conf::soundDeviceIn == 4); REQUIRE(conf::channelsOut == 5); REQUIRE(conf::channelsIn == 6); REQUIRE(conf::samplerate == 44100); // sanitized REQUIRE(conf::buffersize == 8); REQUIRE(conf::delayComp == 9); REQUIRE(conf::limitOutput == true); REQUIRE(conf::rsmpQuality == 0); // sanitized REQUIRE(conf::midiSystem == 11); REQUIRE(conf::midiPortOut == 12); REQUIRE(conf::midiPortIn == 13); REQUIRE(conf::noNoteOff == false); REQUIRE(conf::midiMapPath == "path/to/midi/map"); REQUIRE(conf::lastFileMap == "path/to/last/midi/map"); REQUIRE(conf::midiSync == 14); REQUIRE(conf::midiTCfps == Approx(15.1)); REQUIRE(conf::midiInRewind == 16); REQUIRE(conf::midiInStartStop == 17); REQUIRE(conf::midiInActionRec == 18); REQUIRE(conf::midiInInputRec == 19); REQUIRE(conf::midiInMetronome == 20); REQUIRE(conf::midiInVolumeIn == 21); REQUIRE(conf::midiInVolumeOut == 22); REQUIRE(conf::midiInBeatDouble == 23); REQUIRE(conf::midiInBeatHalf == 24); REQUIRE(conf::recsStopOnChanHalt == true); REQUIRE(conf::chansStopOnSeqHalt == false); REQUIRE(conf::treatRecsAsLoops == true); REQUIRE(conf::resizeRecordings == false); REQUIRE(conf::pluginPath == "path/to/plugins"); REQUIRE(conf::patchPath == "path/to/patches"); REQUIRE(conf::samplePath == "path/to/samples"); REQUIRE(conf::mainWindowX == 0); REQUIRE(conf::mainWindowY == 0); REQUIRE(conf::mainWindowW == 800); REQUIRE(conf::mainWindowH == 600); REQUIRE(conf::browserX == 0); REQUIRE(conf::browserY == 0); REQUIRE(conf::browserW == 800); REQUIRE(conf::browserH == 600); REQUIRE(conf::actionEditorX == 0); REQUIRE(conf::actionEditorY == 0); REQUIRE(conf::actionEditorW == 800); REQUIRE(conf::actionEditorH == 600); REQUIRE(conf::actionEditorZoom == 100); // sanitized REQUIRE(conf::actionEditorGridVal == 10); REQUIRE(conf::actionEditorGridOn == 1); REQUIRE(conf::sampleEditorX == 0); REQUIRE(conf::sampleEditorY == 0); REQUIRE(conf::sampleEditorW == 800); REQUIRE(conf::sampleEditorH == 600); REQUIRE(conf::sampleEditorGridVal == 4); REQUIRE(conf::sampleEditorGridOn == 0); REQUIRE(conf::pianoRollY == 0); REQUIRE(conf::pianoRollH == 900); REQUIRE(conf::pluginListX == 0); REQUIRE(conf::pluginListY == 50); REQUIRE(conf::configX == 20); REQUIRE(conf::configY == 20); REQUIRE(conf::bpmX == 30); REQUIRE(conf::bpmY == 36); REQUIRE(conf::beatsX == 1); REQUIRE(conf::beatsY == 1); REQUIRE(conf::aboutX == 2); REQUIRE(conf::aboutY == 2); } } giada-0.14.5/tests/main.cpp000066400000000000000000000001461322662744500154540ustar00rootroot00000000000000#define CATCH_CONFIG_MAIN #define CATCH_CONFIG_FAST_COMPILE #include "catch/single_include/catch.hpp" giada-0.14.5/tests/midiMapConf.cpp000066400000000000000000000102141322662744500167130ustar00rootroot00000000000000#include "../src/core/const.h" #include "../src/core/midiMapConf.h" #include "catch/single_include/catch.hpp" using std::string; using namespace giada::m; TEST_CASE("Test MidiMapConf") { SECTION("test default values") { midimap::setDefault(); REQUIRE(midimap::brand == ""); REQUIRE(midimap::device == ""); REQUIRE(midimap::muteOn.channel == 0); REQUIRE(midimap::muteOn.valueStr == ""); REQUIRE(midimap::muteOn.offset == -1); REQUIRE(midimap::muteOn.value == 0); REQUIRE(midimap::muteOff.channel == 0); REQUIRE(midimap::muteOff.valueStr == ""); REQUIRE(midimap::muteOff.offset == -1); REQUIRE(midimap::muteOff.value == 0); REQUIRE(midimap::soloOn.channel == 0); REQUIRE(midimap::soloOn.valueStr == ""); REQUIRE(midimap::soloOn.offset == -1); REQUIRE(midimap::soloOn.value == 0); REQUIRE(midimap::soloOff.channel == 0); REQUIRE(midimap::soloOff.valueStr == ""); REQUIRE(midimap::soloOff.offset == -1); REQUIRE(midimap::soloOff.value == 0); REQUIRE(midimap::waiting.channel == 0); REQUIRE(midimap::waiting.valueStr == ""); REQUIRE(midimap::waiting.offset == -1); REQUIRE(midimap::waiting.value == 0); REQUIRE(midimap::playing.channel == 0); REQUIRE(midimap::playing.valueStr == ""); REQUIRE(midimap::playing.offset == -1); REQUIRE(midimap::playing.value == 0); REQUIRE(midimap::stopping.channel == 0); REQUIRE(midimap::stopping.valueStr == ""); REQUIRE(midimap::stopping.offset == -1); REQUIRE(midimap::stopping.value == 0); REQUIRE(midimap::stopped.channel == 0); REQUIRE(midimap::stopped.valueStr == ""); REQUIRE(midimap::stopped.offset == -1); REQUIRE(midimap::stopped.value == 0); } #ifdef RUN_TESTS_WITH_LOCAL_FILES SECTION("test read") { midimap::init(); midimap::setDefault(); /* expect more than 2 midifiles */ REQUIRE(midimap::maps.size() >= 2); /* try with deprecated mode */ int res = midimap::read("akai-lpd8.giadamap"); if (res != MIDIMAP_READ_OK) res = midimap::readMap_DEPR_("akai-lpd8.giadamap"); REQUIRE(res == MIDIMAP_READ_OK); REQUIRE(midimap::brand == "AKAI"); REQUIRE(midimap::device == "LPD8"); REQUIRE(midimap::initCommands.size() == 2); REQUIRE(midimap::initCommands[0].channel == 0); REQUIRE(midimap::initCommands[0].value == 0xB0000000); REQUIRE(midimap::initCommands[1].channel == 0); REQUIRE(midimap::initCommands[1].value == 0xB0002800); /* TODO - can't check 'valueStr' until deprecated methods are alive */ REQUIRE(midimap::muteOn.channel == 0); //REQUIRE(midimap::muteOn.valueStr == "90nn3F00"); REQUIRE(midimap::muteOn.offset == 16); REQUIRE(midimap::muteOn.value == 0x90003F00); REQUIRE(midimap::muteOff.channel == 0); //REQUIRE(midimap::muteOff.valueStr == "90nn0C00"); REQUIRE(midimap::muteOff.offset == 16); REQUIRE(midimap::muteOff.value == 0x90000C00); REQUIRE(midimap::soloOn.channel == 0); //REQUIRE(midimap::soloOn.valueStr == "90nn0F00"); REQUIRE(midimap::soloOn.offset == 16); REQUIRE(midimap::soloOn.value == 0x90000F00); REQUIRE(midimap::soloOff.channel == 0); //REQUIRE(midimap::soloOff.valueStr == "90nn0C00"); REQUIRE(midimap::soloOff.offset == 16); REQUIRE(midimap::soloOff.value == 0x90000C00); REQUIRE(midimap::waiting.channel == 0); //REQUIRE(midimap::waiting.valueStr == "90nn7f00"); REQUIRE(midimap::waiting.offset == 16); REQUIRE(midimap::waiting.value == 0x90007f00); REQUIRE(midimap::playing.channel == 0); //REQUIRE(midimap::playing.valueStr == "90nn7f00"); REQUIRE(midimap::playing.offset == 16); REQUIRE(midimap::playing.value == 0x90007f00); REQUIRE(midimap::stopping.channel == 0); //REQUIRE(midimap::stopping.valueStr == "90nn7f00"); REQUIRE(midimap::stopping.offset == 16); REQUIRE(midimap::stopping.value == 0x90007f00); REQUIRE(midimap::stopped.channel == 0); //REQUIRE(midimap::stopped.valueStr == "80nn7f00"); REQUIRE(midimap::stopped.offset == 16); REQUIRE(midimap::stopped.value == 0x80007f00); } #endif // #ifdef RUN_TESTS_WITH_LOCAL_FILES } giada-0.14.5/tests/patch.cpp000066400000000000000000000163701322662744500156350ustar00rootroot00000000000000#include "../src/core/patch.h" #include "../src/core/const.h" #include "catch/single_include/catch.hpp" using std::string; using std::vector; using namespace giada::m; TEST_CASE("Test Patch class") { string filename = "./test-patch.json"; SECTION("test write") { patch::action_t action1; patch::action_t action2; patch::channel_t channel1; patch::column_t column; #ifdef WITH_VST patch::plugin_t plugin1; patch::plugin_t plugin2; patch::plugin_t plugin3; #endif action1.type = 0; action1.frame = 50000; action1.fValue = 0.3f; action1.iValue = 1000; action2.type = 2; action2.frame = 589; action2.fValue = 1.0f; action2.iValue = 130; channel1.actions.push_back(action1); channel1.actions.push_back(action2); #ifdef WITH_VST plugin1.path = "/path/to/plugin1"; plugin1.bypass = false; plugin1.params.push_back(0.0f); plugin1.params.push_back(0.1f); plugin1.params.push_back(0.2f); channel1.plugins.push_back(plugin1); plugin2.path = "/another/path/to/plugin2"; plugin2.bypass = true; plugin2.params.push_back(0.6f); plugin2.params.push_back(0.6f); plugin2.params.push_back(0.6f); plugin2.params.push_back(0.0f); plugin2.params.push_back(1.0f); plugin2.params.push_back(1.0f); plugin2.params.push_back(0.333f); channel1.plugins.push_back(plugin2); #endif channel1.type = CHANNEL_SAMPLE; channel1.index = 666; channel1.size = G_GUI_CHANNEL_H_1; channel1.column = 0; channel1.mute = 0; channel1.mute_s = 0; channel1.solo = 0; channel1.volume = 1.0f; channel1.pan = 0.5f; channel1.midiIn = true; channel1.midiInKeyPress = UINT32_MAX; // check maximum value channel1.midiInKeyRel = 1; channel1.midiInKill = 2; channel1.midiInArm = 11; channel1.midiInVolume = 3; channel1.midiInMute = 4; channel1.midiInSolo = 5; channel1.midiOutL = true; channel1.midiOutLplaying = 7; channel1.midiOutLmute = 8; channel1.midiOutLsolo = 9; channel1.samplePath = "/tmp/test.wav"; channel1.key = 666; channel1.mode = 0; channel1.begin = 0; channel1.end = 0; channel1.boost = 0; channel1.recActive = 0; channel1.pitch = 1.2f; channel1.midiInReadActions = 0; channel1.midiInPitch = 0; channel1.midiOut = 0; channel1.midiOutChan = 5; patch::channels.push_back(channel1); column.index = 0; column.width = 500; patch::columns.push_back(column); patch::header = "GPTCH"; patch::version = "1.0"; patch::versionMajor = 6; patch::versionMinor = 6; patch::versionPatch = 6; patch::name = "test patch"; patch::bpm = 100.0f; patch::bars = 4; patch::beats = 23; patch::quantize = 1; patch::masterVolIn = 1.0f; patch::masterVolOut = 0.7f; patch::metronome = 0; patch::lastTakeId = 0; patch::samplerate = 44100; #ifdef WITH_VST patch::masterInPlugins.push_back(plugin1); patch::masterOutPlugins.push_back(plugin2); #endif REQUIRE(patch::write(filename) == 1); } SECTION("test read") { REQUIRE(patch::read(filename) == PATCH_READ_OK); REQUIRE(patch::header == "GPTCH"); REQUIRE(patch::version == "1.0"); REQUIRE(patch::versionMajor == 6); REQUIRE(patch::versionMinor == 6); REQUIRE(patch::versionPatch == 6); REQUIRE(patch::name == "test patch"); REQUIRE(patch::bpm == Approx(100.0f)); REQUIRE(patch::bars == 4); REQUIRE(patch::beats == 23); REQUIRE(patch::quantize == 1); REQUIRE(patch::masterVolIn == Approx(1.0f)); REQUIRE(patch::masterVolOut == Approx(0.7f)); REQUIRE(patch::metronome == 0); REQUIRE(patch::lastTakeId == 0); REQUIRE(patch::samplerate == 44100); patch::column_t column0 = patch::columns.at(0); REQUIRE(column0.index == 0); REQUIRE(column0.width == 500); patch::channel_t channel0 = patch::channels.at(0); REQUIRE(channel0.type == CHANNEL_SAMPLE); REQUIRE(channel0.index == 666); REQUIRE(channel0.size == G_GUI_CHANNEL_H_1); REQUIRE(channel0.column == 0); REQUIRE(channel0.mute == 0); REQUIRE(channel0.mute_s == 0); REQUIRE(channel0.solo == 0); REQUIRE(channel0.volume == Approx(1.0f)); REQUIRE(channel0.pan == Approx(0.5f)); REQUIRE(channel0.midiIn == true); REQUIRE(channel0.midiInKeyPress == UINT32_MAX); REQUIRE(channel0.midiInKeyRel == 1); REQUIRE(channel0.midiInKill == 2); REQUIRE(channel0.midiInArm == 11); REQUIRE(channel0.midiInVolume == 3); REQUIRE(channel0.midiInMute == 4); REQUIRE(channel0.midiInSolo == 5); REQUIRE(channel0.midiOutL == true); REQUIRE(channel0.midiOutLplaying == 7); REQUIRE(channel0.midiOutLmute == 8); REQUIRE(channel0.midiOutLsolo == 9); REQUIRE(channel0.samplePath == "/tmp/test.wav"); REQUIRE(channel0.key == 666); REQUIRE(channel0.mode == 0); REQUIRE(channel0.begin == 0); REQUIRE(channel0.end == 0); REQUIRE(channel0.boost == 1.0f); REQUIRE(channel0.recActive == 0); REQUIRE(channel0.pitch == Approx(1.2f)); REQUIRE(channel0.midiInReadActions == 0); REQUIRE(channel0.midiInPitch == 0); REQUIRE(channel0.midiOut == 0); REQUIRE(channel0.midiOutChan == 5); patch::action_t action0 = channel0.actions.at(0); REQUIRE(action0.type == 0); REQUIRE(action0.frame == 50000); REQUIRE(action0.fValue == Approx(0.3f)); REQUIRE(action0.iValue == 1000); patch::action_t action1 = channel0.actions.at(1); REQUIRE(action1.type == 2); REQUIRE(action1.frame == 589); REQUIRE(action1.fValue == Approx(1.0f)); REQUIRE(action1.iValue == 130); #ifdef WITH_VST patch::plugin_t plugin0 = channel0.plugins.at(0); REQUIRE(plugin0.path == "/path/to/plugin1"); REQUIRE(plugin0.bypass == false); REQUIRE(plugin0.params.at(0) == Approx(0.0f)); REQUIRE(plugin0.params.at(1) == Approx(0.1f)); REQUIRE(plugin0.params.at(2) == Approx(0.2f)); patch::plugin_t plugin1 = channel0.plugins.at(1); REQUIRE(plugin1.path == "/another/path/to/plugin2"); REQUIRE(plugin1.bypass == true); REQUIRE(plugin1.params.at(0) == Approx(0.6f)); REQUIRE(plugin1.params.at(1) == Approx(0.6f)); REQUIRE(plugin1.params.at(2) == Approx(0.6f)); REQUIRE(plugin1.params.at(3) == Approx(0.0f)); REQUIRE(plugin1.params.at(4) == Approx(1.0f)); REQUIRE(plugin1.params.at(5) == Approx(1.0f)); REQUIRE(plugin1.params.at(6) == Approx(0.333f)); patch::plugin_t masterPlugin0 = patch::masterInPlugins.at(0); REQUIRE(masterPlugin0.path == "/path/to/plugin1"); REQUIRE(masterPlugin0.bypass == false); REQUIRE(masterPlugin0.params.at(0) == Approx(0.0f)); REQUIRE(masterPlugin0.params.at(1) == Approx(0.1f)); REQUIRE(masterPlugin0.params.at(2) == Approx(0.2f)); patch::plugin_t masterPlugin1 = patch::masterOutPlugins.at(0); REQUIRE(masterPlugin1.path == "/another/path/to/plugin2"); REQUIRE(masterPlugin1.bypass == true); REQUIRE(masterPlugin1.params.at(0) == Approx(0.6f)); REQUIRE(masterPlugin1.params.at(1) == Approx(0.6f)); REQUIRE(masterPlugin1.params.at(2) == Approx(0.6f)); REQUIRE(masterPlugin1.params.at(3) == Approx(0.0f)); REQUIRE(masterPlugin1.params.at(4) == Approx(1.0f)); REQUIRE(masterPlugin1.params.at(5) == Approx(1.0f)); REQUIRE(masterPlugin1.params.at(6) == Approx(0.333f)); #endif } } giada-0.14.5/tests/pluginHost.cpp000066400000000000000000000014471322662744500166710ustar00rootroot00000000000000#ifdef WITH_VST #ifdef RUN_TESTS_WITH_LOCAL_FILES // temporarily disabled due to entangled deps (WIP) #if 0 #include "../src/core/pluginHost.h" #include "catch/single_include/catch.hpp" TEST_CASE("Test PluginHost class") { PluginHost ph; pthread_mutex_t mutex; pthread_mutex_init(&mutex, NULL); SECTION("test read & write") { REQUIRE(ph.countPlugins(PluginHost::MASTER_IN) == 0); REQUIRE(ph.scanDir(".") > 0); REQUIRE(ph.saveList("test-plugin-list.xml") == 1); REQUIRE(ph.loadList("test-plugin-list.xml") == 1); REQUIRE(ph.addPlugin(0, PluginHost::MASTER_IN, &mutex) != NULL); REQUIRE(ph.countPlugins(PluginHost::MASTER_IN) == 1); ph.freeStack(PluginHost::MASTER_IN, &mutex); REQUIRE(ph.countPlugins(PluginHost::MASTER_IN) == 0); } } #endif #endif #endif giada-0.14.5/tests/recorder.cpp000066400000000000000000000423441322662744500163430ustar00rootroot00000000000000#include "../src/core/recorder.h" #include "../src/core/const.h" #include "catch/single_include/catch.hpp" using std::string; using namespace giada::m; TEST_CASE("Test Recorder") { /* Each SECTION the TEST_CASE is executed from the start. The following code is exectuted before each SECTION. */ pthread_mutex_t mutex; pthread_mutex_init(&mutex, nullptr); recorder::init(); REQUIRE(recorder::frames.size() == 0); REQUIRE(recorder::global.size() == 0); SECTION("Test record single action") { recorder::rec(0, G_ACTION_KEYPRESS, 50, 1, 0.5f); REQUIRE(recorder::frames.size() == 1); REQUIRE(recorder::frames.at(0) == 50); REQUIRE(recorder::global.at(0).size() == 1); // 1 action on frame #0 REQUIRE(recorder::global.at(0).at(0)->chan == 0); REQUIRE(recorder::global.at(0).at(0)->type == G_ACTION_KEYPRESS); REQUIRE(recorder::global.at(0).at(0)->frame == 50); REQUIRE(recorder::global.at(0).at(0)->iValue == 1); REQUIRE(recorder::global.at(0).at(0)->fValue == 0.5f); } SECTION("Test record, two actions on same frame") { recorder::rec(0, G_ACTION_KEYPRESS, 50, 6, 0.3f); recorder::rec(0, G_ACTION_KEYREL, 50, 1, 0.5f); REQUIRE(recorder::frames.size() == 1); // same frame, frames.size must stay 1 REQUIRE(recorder::frames.at(0) == 50); REQUIRE(recorder::global.at(0).size() == 2); // 2 actions on frame #0 REQUIRE(recorder::global.at(0).at(0)->chan == 0); REQUIRE(recorder::global.at(0).at(0)->type == G_ACTION_KEYPRESS); REQUIRE(recorder::global.at(0).at(0)->frame == 50); REQUIRE(recorder::global.at(0).at(0)->iValue == 6); REQUIRE(recorder::global.at(0).at(0)->fValue == 0.3f); REQUIRE(recorder::global.at(0).at(1)->chan == 0); REQUIRE(recorder::global.at(0).at(1)->type == G_ACTION_KEYREL); REQUIRE(recorder::global.at(0).at(1)->frame == 50); REQUIRE(recorder::global.at(0).at(1)->iValue == 1); REQUIRE(recorder::global.at(0).at(1)->fValue == 0.5f); SECTION("Test record, another action on a different frame") { recorder::rec(0, G_ACTION_KEYPRESS, 70, 1, 0.5f); REQUIRE(recorder::frames.size() == 2); REQUIRE(recorder::frames.at(1) == 70); REQUIRE(recorder::global.at(0).size() == 2); // 2 actions on frame #0 REQUIRE(recorder::global.at(1).size() == 1); // 1 actions on frame #1 REQUIRE(recorder::global.at(1).at(0)->chan == 0); REQUIRE(recorder::global.at(1).at(0)->type == G_ACTION_KEYPRESS); REQUIRE(recorder::global.at(1).at(0)->frame == 70); REQUIRE(recorder::global.at(1).at(0)->iValue == 1); REQUIRE(recorder::global.at(1).at(0)->fValue == 0.5f); } } SECTION("Test retrieval") { recorder::rec(0, G_ACTION_KEYPRESS, 50, 6, 0.3f); recorder::rec(0, G_ACTION_KEYREL, 70, 1, 0.5f); recorder::rec(1, G_ACTION_KEYPRESS, 50, 6, 0.3f); recorder::rec(1, G_ACTION_KEYREL, 70, 1, 0.5f); recorder::rec(2, G_ACTION_KEYPRESS, 100, 6, 0.3f); recorder::rec(2, G_ACTION_KEYREL, 120, 1, 0.5f); /* Give me action on chan 1, type G_ACTION_KEYREL, frame 70. */ recorder::action *action = nullptr; REQUIRE(recorder::getAction(1, G_ACTION_KEYREL, 70, &action) == 1); REQUIRE(action != nullptr); REQUIRE(action->chan == 1); REQUIRE(action->type == G_ACTION_KEYREL); REQUIRE(action->frame == 70); REQUIRE(action->iValue == 1); REQUIRE(action->fValue == 0.5f); /* Give me *next* action on chan 0, type G_ACTION_KEYREL, starting from frame 20. Must be action #2 */ REQUIRE(recorder::getNextAction(0, G_ACTION_KEYREL, 20, &action) == 1); REQUIRE(action != nullptr); REQUIRE(action->chan == 0); REQUIRE(action->type == G_ACTION_KEYREL); REQUIRE(action->frame == 70); /* Give me *next* action on chan 2, type G_ACTION_KEYPRESS, starting from frame 200. You are requesting frame outside boundaries. */ REQUIRE(recorder::getNextAction(2, G_ACTION_KEYPRESS, 200, &action) == -1); /* Give me *next* action on chan 2, type G_ACTION_KEYPRESS, starting from frame 100. That action does not exist. */ REQUIRE(recorder::getNextAction(2, G_ACTION_KEYPRESS, 100, &action) == -2); } SECTION("Test retrieval MIDI") { recorder::rec(0, G_ACTION_MIDI, 0, 0x903C3F00, 0.0f); recorder::rec(1, G_ACTION_MIDI, 0, 0x903D3F00, 0.0f); recorder::rec(0, G_ACTION_MIDI, 1000, 0x803C2000, 0.0f); recorder::rec(0, G_ACTION_MIDI, 1050, 0x903C3F00, 0.0f); recorder::rec(0, G_ACTION_MIDI, 2000, 0x803C3F00, 0.0f); recorder::rec(1, G_ACTION_MIDI, 90, 0x803D3F00, 0.0f); recorder::rec(1, G_ACTION_MIDI, 1050, 0x903D3F00, 0.0f); recorder::rec(1, G_ACTION_MIDI, 2000, 0x803D3F00, 0.0f); recorder::action* result = nullptr; recorder::getNextAction(0, G_ACTION_MIDI, 100, &result, 0x803CFF00, 0x0000FF00); REQUIRE(result != nullptr); REQUIRE(result->frame == 1000); } SECTION("Test deletion, single action") { recorder::rec(0, G_ACTION_KEYPRESS, 50, 6, 0.3f); recorder::rec(0, G_ACTION_KEYREL, 60, 1, 0.5f); recorder::rec(1, G_ACTION_KEYPRESS, 70, 6, 0.3f); recorder::rec(1, G_ACTION_KEYREL, 80, 1, 0.5f); /* Delete action #0, don't check values. */ recorder::deleteAction(0, 50, G_ACTION_KEYPRESS, false, &mutex); REQUIRE(recorder::frames.size() == 3); REQUIRE(recorder::global.size() == 3); SECTION("Test deletion checked") { /* Delete action #1, check values. */ recorder::deleteAction(1, 70, G_ACTION_KEYPRESS, true, &mutex, 6, 0.3f); REQUIRE(recorder::frames.size() == 2); REQUIRE(recorder::global.size() == 2); } } SECTION("Test deletion, range of actions") { recorder::rec(0, G_ACTION_KEYPRESS, 50, 6, 0.3f); recorder::rec(0, G_ACTION_KEYREL, 60, 1, 0.5f); recorder::rec(0, G_ACTION_KEYPRESS, 70, 6, 0.3f); recorder::rec(0, G_ACTION_KEYREL, 80, 1, 0.5f); recorder::rec(1, G_ACTION_KEYPRESS, 100, 6, 0.3f); recorder::rec(1, G_ACTION_KEYREL, 120, 1, 0.5f); /* Delete any action on channel 0 of types KEYPRESS | KEYREL between frames 0 and 200. */ recorder::deleteActions(0, 0, 200, G_ACTION_KEYPRESS | G_ACTION_KEYREL, &mutex); REQUIRE(recorder::frames.size() == 2); REQUIRE(recorder::global.size() == 2); REQUIRE(recorder::global.at(0).size() == 1); REQUIRE(recorder::global.at(1).size() == 1); REQUIRE(recorder::global.at(0).at(0)->chan == 1); REQUIRE(recorder::global.at(0).at(0)->type == G_ACTION_KEYPRESS); REQUIRE(recorder::global.at(0).at(0)->frame == 100); REQUIRE(recorder::global.at(0).at(0)->iValue == 6); REQUIRE(recorder::global.at(0).at(0)->fValue == 0.3f); REQUIRE(recorder::global.at(1).at(0)->chan == 1); REQUIRE(recorder::global.at(1).at(0)->type == G_ACTION_KEYREL); REQUIRE(recorder::global.at(1).at(0)->frame == 120); REQUIRE(recorder::global.at(1).at(0)->iValue == 1); REQUIRE(recorder::global.at(1).at(0)->fValue == 0.5f); } SECTION("Test action presence") { recorder::rec(0, G_ACTION_KEYREL, 80, 1, 0.5f); recorder::rec(1, G_ACTION_KEYPRESS, 100, 6, 0.3f); recorder::rec(1, G_ACTION_KEYREL, 120, 1, 0.5f); recorder::deleteAction(0, 80, G_ACTION_KEYREL, false, &mutex); REQUIRE(recorder::hasActions(0) == false); REQUIRE(recorder::hasActions(1) == true); } SECTION("Test clear actions by channel") { recorder::rec(0, G_ACTION_KEYREL, 80, 1, 0.5f); recorder::rec(1, G_ACTION_KEYPRESS, 100, 6, 0.3f); recorder::rec(1, G_ACTION_KEYREL, 120, 1, 0.5f); recorder::clearChan(1); REQUIRE(recorder::hasActions(0) == true); REQUIRE(recorder::hasActions(1) == false); REQUIRE(recorder::frames.size() == 1); REQUIRE(recorder::global.size() == 1); REQUIRE(recorder::global.at(0).size() == 1); } SECTION("Test clear actions by type") { recorder::rec(1, G_ACTION_KEYREL, 80, 1, 0.5f); recorder::rec(0, G_ACTION_KEYPRESS, 100, 6, 0.3f); recorder::rec(1, G_ACTION_KEYREL, 120, 1, 0.5f); /* Clear all actions of type KEYREL from channel 1. */ recorder::clearAction(1, G_ACTION_KEYREL); REQUIRE(recorder::hasActions(0) == true); REQUIRE(recorder::hasActions(1) == false); REQUIRE(recorder::frames.size() == 1); REQUIRE(recorder::global.size() == 1); REQUIRE(recorder::global.at(0).size() == 1); } SECTION("Test clear all") { recorder::rec(0, G_ACTION_KEYPRESS, 0, 1, 0.5f); recorder::rec(1, G_ACTION_KEYPRESS, 0, 1, 0.5f); recorder::rec(0, G_ACTION_KEYREL, 80, 1, 0.5f); recorder::rec(1, G_ACTION_KEYREL, 100, 6, 0.3f); recorder::rec(2, G_ACTION_KILL, 120, 1, 0.5f); recorder::clearAll(); REQUIRE(recorder::frames.size() == 0); REQUIRE(recorder::global.size() == 0); } SECTION("Test optimization") { recorder::rec(0, G_ACTION_KEYPRESS, 20, 1, 0.5f); recorder::rec(0, G_ACTION_KEYREL, 80, 1, 0.5f); recorder::rec(1, G_ACTION_KEYPRESS, 20, 1, 0.5f); recorder::rec(1, G_ACTION_KEYREL, 80, 1, 0.5f); /* Fake frame 80 without actions.*/ recorder::global.at(1).clear(); recorder::optimize(); REQUIRE(recorder::frames.size() == 1); REQUIRE(recorder::global.size() == 1); REQUIRE(recorder::global.at(0).size() == 2); } SECTION("Test BPM update") { recorder::rec(0, G_ACTION_KEYPRESS, 0, 1, 0.5f); recorder::rec(0, G_ACTION_KEYREL, 80, 1, 0.5f); recorder::updateBpm(60.0f, 120.0f, 44100); // scaling up REQUIRE(recorder::frames.at(0) == 0); REQUIRE(recorder::frames.at(1) == 40); recorder::updateBpm(120.0f, 60.0f, 44100); // scaling down REQUIRE(recorder::frames.at(0) == 0); REQUIRE(recorder::frames.at(1) == 80); } SECTION("Test samplerate update") { recorder::rec(0, G_ACTION_KEYPRESS, 0, 1, 0.5f); recorder::rec(0, G_ACTION_KEYREL, 80, 1, 0.5f); recorder::rec(0, G_ACTION_KEYPRESS, 120, 1, 0.5f); recorder::rec(0, G_ACTION_KEYREL, 150, 1, 0.5f); recorder::updateSamplerate(44100, 22050); // scaling down REQUIRE(recorder::frames.at(0) == 0); REQUIRE(recorder::frames.at(1) == 160); REQUIRE(recorder::frames.at(2) == 240); REQUIRE(recorder::frames.at(3) == 300); recorder::updateSamplerate(22050, 44100); // scaling up REQUIRE(recorder::frames.at(0) == 0); REQUIRE(recorder::frames.at(1) == 80); REQUIRE(recorder::frames.at(2) == 120); REQUIRE(recorder::frames.at(3) == 150); } SECTION("Test expand") { recorder::rec(0, G_ACTION_KEYPRESS, 0, 1, 0.5f); recorder::rec(0, G_ACTION_KEYREL, 80, 1, 0.5f); recorder::rec(0, G_ACTION_KILL, 200, 1, 0.5f); recorder::expand(300, 600); REQUIRE(recorder::frames.size() == 6); REQUIRE(recorder::global.size() == 6); REQUIRE(recorder::frames.at(0) == 0); REQUIRE(recorder::frames.at(1) == 80); REQUIRE(recorder::frames.at(2) == 200); REQUIRE(recorder::frames.at(3) == 300); REQUIRE(recorder::frames.at(4) == 380); REQUIRE(recorder::frames.at(5) == 500); } SECTION("Test shrink") { recorder::rec(0, G_ACTION_KEYPRESS, 0, 1, 0.5f); recorder::rec(0, G_ACTION_KEYREL, 80, 1, 0.5f); recorder::rec(0, G_ACTION_KILL, 200, 1, 0.5f); recorder::shrink(100); REQUIRE(recorder::frames.size() == 2); REQUIRE(recorder::global.size() == 2); REQUIRE(recorder::frames.at(0) == 0); REQUIRE(recorder::frames.at(1) == 80); } SECTION("Test overdub, full overwrite") { recorder::rec(0, G_ACTION_MUTEON, 0, 1, 0.5f); recorder::rec(0, G_ACTION_MUTEOFF, 80, 1, 0.5f); recorder::rec(0, G_ACTION_MUTEON, 200, 1, 0.5f); recorder::rec(0, G_ACTION_MUTEOFF, 400, 1, 0.5f); /* Should delete all actions in between and keep the first one, plus a new last action on frame 500. */ recorder::startOverdub(0, G_ACTION_MUTEON | G_ACTION_MUTEOFF, 0, 1024); recorder::stopOverdub(500, 500, &mutex); REQUIRE(recorder::frames.size() == 2); REQUIRE(recorder::global.size() == 2); REQUIRE(recorder::frames.at(0) == 0); REQUIRE(recorder::frames.at(1) == 500); REQUIRE(recorder::global.at(0).at(0)->frame == 0); REQUIRE(recorder::global.at(0).at(0)->type == G_ACTION_MUTEON); REQUIRE(recorder::global.at(1).at(0)->frame == 500); REQUIRE(recorder::global.at(1).at(0)->type == G_ACTION_MUTEOFF); } SECTION("Test overdub, left overlap") { recorder::rec(0, G_ACTION_MUTEON, 100, 1, 0.5f); recorder::rec(0, G_ACTION_MUTEOFF, 400, 1, 0.5f); /* Overdub part of the leftmost part of a composite action. Expected result: a new composite action. Original: ----|########| Overdub: |#######|----- Result: |#######|----- */ recorder::startOverdub(0, G_ACTION_MUTEON | G_ACTION_MUTEOFF, 0, 16); recorder::stopOverdub(300, 500, &mutex); REQUIRE(recorder::frames.size() == 2); REQUIRE(recorder::global.size() == 2); REQUIRE(recorder::frames.at(0) == 0); REQUIRE(recorder::frames.at(1) == 300); REQUIRE(recorder::global.at(0).at(0)->frame == 0); REQUIRE(recorder::global.at(0).at(0)->type == G_ACTION_MUTEON); REQUIRE(recorder::global.at(1).at(0)->frame == 300); REQUIRE(recorder::global.at(1).at(0)->type == G_ACTION_MUTEOFF); } SECTION("Test overdub, right overlap") { recorder::rec(0, G_ACTION_MUTEON, 000, 1, 0.5f); recorder::rec(0, G_ACTION_MUTEOFF, 400, 1, 0.5f); /* Overdub part of the rightmost part of a composite action. Expected result: a new composite action. Original: |########|------ Overdub: -----|#######|-- Result: |###||#######|-- */ recorder::startOverdub(0, G_ACTION_MUTEON | G_ACTION_MUTEOFF, 100, 16); recorder::stopOverdub(500, 500, &mutex); REQUIRE(recorder::frames.size() == 4); REQUIRE(recorder::global.size() == 4); REQUIRE(recorder::frames.at(0) == 0); REQUIRE(recorder::frames.at(1) == 84); // 100 - bufferSize (16) REQUIRE(recorder::frames.at(2) == 100); REQUIRE(recorder::frames.at(3) == 500); REQUIRE(recorder::global.at(0).at(0)->frame == 0); REQUIRE(recorder::global.at(0).at(0)->type == G_ACTION_MUTEON); REQUIRE(recorder::global.at(1).at(0)->frame == 84); REQUIRE(recorder::global.at(1).at(0)->type == G_ACTION_MUTEOFF); REQUIRE(recorder::global.at(2).at(0)->frame == 100); REQUIRE(recorder::global.at(2).at(0)->type == G_ACTION_MUTEON); REQUIRE(recorder::global.at(3).at(0)->frame == 500); REQUIRE(recorder::global.at(3).at(0)->type == G_ACTION_MUTEOFF); } SECTION("Test overdub, hole diggin'") { recorder::rec(0, G_ACTION_MUTEON, 0, 1, 0.5f); recorder::rec(0, G_ACTION_MUTEOFF, 400, 1, 0.5f); /* Overdub in the middle of a long, composite action. Expected result: original action trimmed down plus anther action next to it. Total frames should be 4. Original: |#############| Overdub: ---|#######|--- Result: |#||#######|--- */ recorder::startOverdub(0, G_ACTION_MUTEON | G_ACTION_MUTEOFF, 100, 16); recorder::stopOverdub(300, 500, &mutex); REQUIRE(recorder::frames.size() == 4); REQUIRE(recorder::global.size() == 4); REQUIRE(recorder::frames.at(0) == 0); REQUIRE(recorder::frames.at(1) == 84); // 100 - bufferSize (16) REQUIRE(recorder::frames.at(2) == 100); REQUIRE(recorder::frames.at(3) == 300); REQUIRE(recorder::global.at(0).at(0)->frame == 0); REQUIRE(recorder::global.at(0).at(0)->type == G_ACTION_MUTEON); REQUIRE(recorder::global.at(1).at(0)->frame == 84); REQUIRE(recorder::global.at(1).at(0)->type == G_ACTION_MUTEOFF); REQUIRE(recorder::global.at(2).at(0)->frame == 100); REQUIRE(recorder::global.at(2).at(0)->type == G_ACTION_MUTEON); REQUIRE(recorder::global.at(3).at(0)->frame == 300); REQUIRE(recorder::global.at(3).at(0)->type == G_ACTION_MUTEOFF); } SECTION("Test overdub, cover all") { recorder::rec(0, G_ACTION_MUTEON, 0, 1, 0.5f); recorder::rec(0, G_ACTION_MUTEOFF, 100, 1, 0.5f); recorder::rec(0, G_ACTION_MUTEON, 120, 1, 0.5f); recorder::rec(0, G_ACTION_MUTEOFF, 200, 1, 0.5f); recorder::rec(0, G_ACTION_MUTEON, 220, 1, 0.5f); recorder::rec(0, G_ACTION_MUTEOFF, 300, 1, 0.5f); /* Overdub all existing actions. Expected result: a single composite one. */ recorder::startOverdub(0, G_ACTION_MUTEON | G_ACTION_MUTEOFF, 0, 16); recorder::stopOverdub(500, 500, &mutex); REQUIRE(recorder::frames.size() == 2); REQUIRE(recorder::global.size() == 2); REQUIRE(recorder::frames.at(0) == 0); REQUIRE(recorder::frames.at(1) == 500); REQUIRE(recorder::global.at(0).at(0)->frame == 0); REQUIRE(recorder::global.at(0).at(0)->type == G_ACTION_MUTEON); REQUIRE(recorder::global.at(1).at(0)->frame == 500); REQUIRE(recorder::global.at(1).at(0)->type == G_ACTION_MUTEOFF); } SECTION("Test overdub, null loop") { recorder::rec(0, G_ACTION_MUTEON, 0, 1, 0.5f); recorder::rec(0, G_ACTION_MUTEOFF, 500, 1, 0.5f); /* A null loop is a loop that begins and ends on the very same frame. */ recorder::startOverdub(0, G_ACTION_MUTEON | G_ACTION_MUTEOFF, 300, 16); recorder::stopOverdub(300, 700, &mutex); REQUIRE(recorder::frames.size() == 2); REQUIRE(recorder::frames.at(0) == 0); REQUIRE(recorder::frames.at(1) == 284); // 300 - bufferSize (16) REQUIRE(recorder::global.at(0).at(0)->frame == 0); REQUIRE(recorder::global.at(0).at(0)->type == G_ACTION_MUTEON); REQUIRE(recorder::global.at(1).at(0)->frame == 284); REQUIRE(recorder::global.at(1).at(0)->type == G_ACTION_MUTEOFF); } SECTION("Test overdub, ring loop") { /* A ring loop occurs when you record the last action beyond the end of the sequencer. Original: ---|#######|--- Overdub: #####|------|## Result: ---|#######||#| */ recorder::rec(0, G_ACTION_MUTEON, 200, 1, 0.5f); recorder::rec(0, G_ACTION_MUTEOFF, 300, 1, 0.5f); recorder::startOverdub(0, G_ACTION_MUTEON | G_ACTION_MUTEOFF, 400, 16); recorder::stopOverdub(250, 700, &mutex); REQUIRE(recorder::frames.size() == 4); REQUIRE(recorder::frames.at(0) == 200); REQUIRE(recorder::frames.at(1) == 300); REQUIRE(recorder::frames.at(2) == 400); REQUIRE(recorder::frames.at(3) == 700); } } giada-0.14.5/tests/resources/000077500000000000000000000000001322662744500160355ustar00rootroot00000000000000giada-0.14.5/tests/resources/test.wav000066400000000000000000002366441322662744500175520ustar00rootroot00000000000000RIFF=WAVEfmt DXdatax='hG6z z&2Tt j 7mV?[ob`B<1+EWld .+;Y,SVWw?,2 ^a .>9J\0_(t G{>Q6j4$(F=%Ga9;}sVe{~Y,_![!^6\45[3[Kr`62 /$('f&F%NlTh}3 xbW^[:eYVx7[Q&x~b/(b]81](n9 nE 8-CGU- r>h:%PCmo61P&X:Kl)&$up#,F#q(lK&I.$qo*PVAM9}z^9}-Q<|{SlV -St? lKv}.h'3k@yK|#ZB1iOK |AUP /jg#|Z2mm s`FY{IaOwcEz+;,c}`=!Pk s~pC7We[JT[(sN&,g( <(U!&@ &>b0DR[4.ARi?F?F@9 ne>B"zi4\^[%Vj!B?V<ZIE1T:9(W?vEP 1q/sE\ O~@xL9 7nU ;%;1[n RVGG4Ib%32iwS ]^" ^8\g8 mBy"Y\hu<~7kJ'^\FSTxghN ^+ 4h-  E&Fh|c   2J ' H:(Me@w c d +h0 7JuSg wp,{Z5w jHD W ,e c." {)h 'o5(}UsnNm . : C>n*}Ax:?8s]` %U(7Nj %quDS:dyP_ l six%G-d_~(k?Oat V]O2 ]we/2H+)\ w$eU ^a!'_!y1/ow{ZxY ZLqhF JV ^q |$Ps,J5, $oF[H! 0+]ng ey}k}8`I 0 ^Mxn>zt1E C t3!+ 7wq_+hn$5mB2k"I%| l&2c .p8oI5G  E]\ Y = zCt 2OP - C K ,,"gk \Zn # 4 s~WJ^S1 zqBc )d-Yl; d? Ev'TS &&V?_j`8mP^<\@_ VUW \]ZM.MJj%Pv zbh)v7-B~q]O wg#L D| {3: R" 'x \7JS  w >~&A: V$Sys6 i :xlrO+f&*8oR x jF~# Y!m,iv}q/mismr PFZ K\Fe`HA[; `*O e Oc* 410k1J\Hf%RDY11b,T!&~0< mK35bI7jsAhccEi(hdrF G(akvb_r1<^4'l xBUC*a+ i/ jk]K+nIN&X $x^'W<&];!0:HE;<7X7 o<m T6h6arc4=_f%qeOI>4 DGhb a6N7i}'n_x:[~-\(&"Q1(d2w]bE]t~feL%B tBi.M<PI Cq('BsA~9G3+m ^ yO&+Y2b'MZlCl~oq5U  'x Y>(H2wZQ#wso<e]O N|Xb${W #_GbXl%UUz 7)Itg#7_{6kv+/KdTeu~*D_7D8z +Xmwzx f(^PIH|;Eno~. "B:+i[M*(Dz.{"8Z&cB'h44*5 :my:!zc"xy^d*/['(A$z :QPj.hWRN9Un>~5dQe@/lsh[3M k gDqoqC]HC J7,]@IydKl] :uNE  FVd(pZLj8g|:GR^f"67{ YRH},T48de.,+],6$u}G1;vFP;*LT)5J40 &nnFdEsn]&Wz- N{t3ApAeI)|f1A_>i+7oeHa?|4{]4oPU];77}E~}_?gdNvXzBZ-}c LC94&w?#WEFv1$KHEB j z}h@(8>#L]bMtVdTz yi3L+`?6m}9@+D%AQv)Rn bc; i|: 1D*AxkPKuCt>ZBD3*/b3XiEk1cOGb61YqQRmJ/\uCV* _[HQjWo#VdDlK=+=a 3.h'>&pZ&y0%mN tCv dxg 'o9? lDH: R1R($X )8 6}1M[/-*QbWH  w1< kgvWlxE\a?{>**{<!6- ]s3~07@KU ACoD 44n1SDg@m!GbFlPIgl>9b8lJ Dl>Px0% LKT S 6>L `f78ya? {q/n"~X\6h5;dsp>eBKG&:HN_yS+- hO%hsR^Q`ABRK84|FE cfaTbRe)0 z 25 '- Q ( :Xm O  7ktT:HsT,A7Zgp~%YH_y XDMb EX%V*5gl1 d0wjyDUMgngm64BaP/ byIRob:F(=[2^!R]z dQmOZ"" P@*-l&x:V6[vf%j -W J~TY^ qc\A0Fyrmy";g!yQ}I7 J@w: dj{NN 'm)KSn>V6:p=)!ztMY BLqW=j7.m,>$A9w(5p,K6NfS>fcPhYUL s| *%p44T@Ou<hp8 #`/^e/{m*&5Tn'>lT8q.?{V~RX:j}Z#u }Y=m ra?L3ICtb(rasBZ#MR> dGT+pYI H; 7$d K+ 7:$ =w~4$uX!Yx.kBg|n2x]B@%9z#WY}Ucu+Ys*Osq}+L%[ '? 2N>>aG _oS \k~oaVB*m=\IEY` _w09Ov'3`t h/`zmu#)>;1Fq ""{|F!F. `Z; Ws,E\#P $"s kN(&Mr?xJ,C 7z/ i 'Q x|^FZ@ho"zQ($Mfi*SS^V| . %!/ <xi UogolAfZl+tSaL0<j(Zm<hBa M{% Z}1>oS0Z5 dj6%C~GE;jp G^n,PfL:B B*p?~RL5@1/W$MDz>&z+Gx!WR\ 0Jg /h"*VC}\89@6'f($O*ZJ_1uED9p\2'H}uZ Nff p>7VkU#5f4zRM xp6VJ_`(Bj)~&rSv,M[|v<Sa`eID*r[GvZk>`?h1_EXJed5%B^-jZ:mEB^T[T,.cQ0-w:,5q!gD=kM?hqsIn/OMkRB[jIdQe46K(r|01Z!9h` r)y(" EGwT4&:6oDJo8!(_?ap<Tbbaff.Af)nTmK -9#o |1r:* *AGvfd4W _fb(DdE)$ t+Ri\&0'\cj.{BgwY)Lv&=xyMc'g&HJ'%:Ep!ES E[vUL_$dYa 56/8o(fFM <A(><iHza$'_y"fr2>R )e44x=N }k6N| g0fKiC]A8l_; /%7\P1%XWMTOF"&Kuh ougok1Xn /Cu\t#2?yTSbCol: P\;L0>T-Jh#B R&Olos8x:t7ThUFx T)bfjztpzs=Q_l3HefRtz:zSty NI%;~MkOwsd;v_IqrYrS0^CjYWO%9nv/c0o9M~p[;<C \0 O/=YwZ'zZwY6hlXZQ+stImOqoF&ka Qw;s] FAI,M?ycHSDm jP?*@ym 3= gM-}o!eC[? Fe`%YlZ5@J7peEQe?jv+6">eC^WJ?*J !1g$i?w< )@Fs({`4vRv/0 $`b`3HOy-DG_$^5<nyB0IEswcncMm@`^U,X>J?y[K40y,~#MnT(ajX~!>z%rI%}Na5'Dn<]lb|;&}iF@BJDWUEysiT o -- UqR Ji3Q_x6TSpq9+&a}YUJh_\ mb|'\I?#H$k_n@*?HW|.>ZQNFCtLO/n IF2^q3)G:=;E4WE B@>o,\@v66Pj54,.gw][d13LWe5Y5Uk")^>HV"omD=N06tLG8|hv?-.yxMKgggih Z.59**j#c_UC1*\o@%LY_[a9%RPeeZ348{mcOP@Q15T;+!LSMf jI)|?'V ^cYQIad`t_+dL M~BY'\I\<vv^b"}I%EUfRz#ktq@F| vjR |"aKM{?OJ{/>/Dwez>m\q^AULl^L8_QTY<D3c(7[tMR~zmX[]RxMMug_ gHchyuh_=9>zo a~QVm <.ZM$dIY[xvI2 v[IIf 3>Dv}jG#D*j2iI:AAlnj88gtUZ.lX"4+bS&\\N`Jk u|s6(WQ!Yu]Sjp2i9OCE1thlNbB* [yE)OWfXG62 M+jDD zV, 9X36Y9_Lq "sS{m%sPTqn] ^}8<;q}q^<2+:793d| V?9De|9!S,d;Lz)[g{P;>8u[1&j_N/K6d`bv+h8 "Z'Q'mr{$ \39,uc"K9AX-T;vf;av|b %1cexKK_sy6q%$;_T w&+ {>&C(KvzAW+[K(a&t9L~N5{ydIB< xVedZ/82c{#U5"rds$ed4)G.L%,qS w/4M{;-ib'bUpCppUjH/G9"N'N@q.4* ys'*cq4G&I WcO[R6\pIid2"7N?|^A X3SP7'p4q<inTm=oR(Pb=R|uP Bef CA_^+BF+8qX|G,5i}j*Z-~*Z(b}_tBgnUPznBiZ Ob 4E>VL[tt\*c`]:s]6U LYEC=&=7I0= qs]'~[';(Km~jc!HbjpS0 E!6zyb/NEVOnQ}'~w!8:ViFudb.L/ ?+bo]<~AH]%@M`){5yW/0#_).[ _g;Z16GGT 3||*uk}j i3gI 1 G e)^*-0zRW% )>} D_%/m.W@Wx+d]Wj(1` 'q\sUfBc*/Ew6u`5_ZA:y+i H{ K6:% gz4)uv~]tnZ/91lgx T(O2!@`zYr 31QUfx  G }:Em, :/yLSKi^Rnk Z,=S-?}X.G 8qm3wG1`})<>%>"[OS\3^'v9]M_{T:p >SXzS3^|p5u$!q=k5Z- -:lIu^HJ9M#q]&x_bddO/B(EhzN;U1Jm|&T;YQ4S7m< Nr(;1D7%KIH 8kMuhL/L;lrD=P@]9Qy\en'UWFyf`t$`|__Ct*xV_& k4_q8y7)ZyZ Y28p;nxTWuERCF\k0K AO4 S|Sm[^4?MmLI3f~sM|ivJ6DU-(:Cc< ;Ua6lKg9<JMb|4gluVWdVXcH 8u1 3G} M/W.~cAqu|44 I,Mt.Q-CzGMA=fGguua)\O?.%q2HnkQz3s=CK6rf[; ]D^Iw,R_t^j}u:q T*d~%f;v{t;Zw,wTHeDW%O#s/9&!Up +S]uQ^$1a5o?Hz)rRhu]g;VP_pO_pYSyS)6tAbxH]qZZ 8J% B|9 dl' r7 H^&;7lLG2m4R686=p*o_YVzLYChmFFvx;~6\.#{GL7"!K/RtDj=q( gaI$=T_i !qJ b"0Fp.-~O($2N} Ok(>qbAmTwgD!apr;Ual -cj6.va]J?x  SeKb{J03)P#_GKB}UpsyuqdC?r >b]"|#ih8aHG,8GEr a!P8=y"} sh\qf|dATX,4+qlSn6]OUU-6:@[(uvgz}<"Fz">P"ymq{yc|n~ fJV#|$XDEQ$@kbq]GwxfgQ\^bxj*)j=+JC<$IM,/z<O!rA>gyP 7^4E,9J|VBU$.)ERyjQ/AXNJ"{(%6iP>)9j{(nx@WAy*-j3 im D5myU1]9cI"$E JO4RC x]nNB0 Cch6]P%LZ;==&$bo]t0B])5{, o1,+b]'Y104zt-VGj'x)ooX_!aXR:ksH"[E*Y[<[#O2AA+ 1o2/{ 7eMBQj?pB{BqAl*LL`;$ <+y8_~$Y A>BL}X~O,:our[y@tTNj7*'1/! %R8nqX~E]0a'wI !GN$;.pc{3b!5jq!]D6VQuB(y5X;Y_@q330"11x LhsX]:vj7@g`SKa-B 9R2x .$WpW8`Y*Cdrl)|Yk*h R6vK zR\, 92wNx3pI^K|%\Y9lS}V&5v+WQS1KRyxqxU'TA@OWYj:VG:R)PqaP0ow7B|q)$3:Yoy *umB=^>OdNSdNJbs"fDqU\8NvWd>MB}?`HHF=/ o+X i<cR8#\A6S(UV'(%8_]/7O70*/ao1,&[9C83St_Kj~[L}g mmY5O3ef~'3}:WI"j3i.)+{Bu q]y#9h 94.vfNOyp2H s9Ge:ZjiRj(bZ9oHYh%z~'e]{q QZG238dN,i Btr4kIE5L%3Ni wE^j {u;5.fe1Z N9.h1!;3I$cRHN >L@~A&pY 3Pb% N.i#eje~J]8nk\?yD7dhsL 1sw~S^jrQhg)qm6{|BWoY\\0WKzF_6t| _ "?=:k=\ v604zB.k|#OjpA/ OWzjgfQHqVgxW^Wm>;cDL{tx!~0U2[s\h-W[;!Y?;lB\t!c?VAy/gV.KJ7_N6+QL {81hP{Mm]D^#nsd17$we@c*{2F`[ 8nyo3.$ pE7LI(P2-~X {dym;S P`eYQ{?yjz-KH-3;#Jj9krk'#'C2"V$` :uAPhaDnfC;+Y aTEeEw!Z +[rR'=&kO5#gu%Iaj=>J 6O 8]2z0WiwWimy7%* 93XQ)}&?Z-D]}~{@dY&;)n8B9NB( @c|4YtwHlAshDq`obN$5@!Dnk"-`UtfKp{t>ro mt"N9xjv'i>'9tl5,emwe +X*v2J Ig$"QB6lz_B nBJ0m-J}ws+6z\:@-z{uMFShMo'tW/xtmbcx I9UWf(U8{ +0nOVj;D5_-,r&(b/S,-bvzSVE5[C[+ JY0=Y;=1TJLlAGlf'WUK^0e`tL~u9k 0u83xH & A < Z.=Wt Ri =oVu(0k%{h?K8:K+x'zKM'aWj#J`ULG%B$&qz`\ q( vbq\hI/1BCp<@6h_$m ^?gY&y+r ]cgURr^1*5*d$t B7MqxAV 3VUml f`zPsX7!&SES9um}x C!:Z5e7_@j=9SgVBz|w|ArA:+R(.Yge%}='mP.l~"}*5}:riV#Sp%O6sgVEwmJrKL?7"C'K  [HP ^+ervBY $Y-\-XUj]!wYG i%*^_m3S] $kq[H(l[KZvP$ eJHFRKF(hM"0*{5|ln< ?D;h|,Vwe|_2>@ y14t(59uGr ~L]TSJQ(T3zy4a*0jC:At\ K-'X3`} nAZ9sKB6%X%\V(W^-t[IM5DL6D)`$)EdM?TH<qP,d-8'NR"fG>Z:?rhumi9w2YN#aKs_tpka7 x4pp]bGJg[ueT# 6`y2|.}{Q\6hh j= %F`&[6B$ hKiG9j4H9[^.uSr`+tn/[J+L$W L?_zF0jCiSSNPXQ$MM_O_k?&@f5i~Zyg1^cveTB%1>${tFyC/@w*k[Gt4CguBzx,q[G@/]n*oa!%*k:Xh_&mCz"jL0R)+.I#7F::Y&yLO p!6- =)&WN/j}<;-:xdLsA |Z{xxe{'g?y{/)?vm&?(vCvS*a^nms{V=RiL'R@ZPxsS$ VTFISWdSgDY%KkI k|[pRN,)E Z~mbpX{'q|4/=i? +@32+,s)<|s) ]}a{|M W:bf 3kl2cwx3O.k&ai07y;ZB=my ;'/6}2SD(w1%n$vNcb2?8x3oaw$Tg[* Q9(R4~Zc$z l9M'HOV9G ,wytwQMQrwpE ^SbUrM+"$vk 4$p?&??\ |Jy\UCa{}4t GF;Q`gT3Sn+r11)GF^6 VSuo18sn*)sixjZ.&xa&r%H;Fy#7mEJ?Rcm<F?soOaY1dO"c:K Tp3;^qc~;5zQr4ef@}2- EC6E*$?U ] j-/NM2/U&]<RISg3kwG) .ysf_-2NI67(Vj{8CM ^I~f~UNch^Q/'XYSVu=;%3OV1RnZ<g}Yg0 q"CsTaxj 1>}s5sR]!p27D=B~E%D )'QRT{7wv}sE./h=& g?p&=<.7s49(JS~ x "GT/ ym>eWsZaOziF,yz](UM  #LMEc :}tSDQ{<-B#xRiqn Alw'4a"Q yusb"8PHDWyN o(zv.ZKnG;4QG_f  V!q=npbPbQa 6HPf SY8eCnOWd %8fdQLP/2P9(5SpYT2izxzu_*o Z^!H{Kt Alof e6ZU+wu>Y5!lK]> <m|.91rz#sd'J$\3$aD@aZ4w{AF7mt{vRqfDt6t%Qn  c8RV(g"?6Q_V&f}UI@`4L#Nl&9 NCXc{E-`g"=&AJE<?pr*r#  `.)3\h(*Cof   LiVTbo8W`tw.iB}l<Vz}cl>| F|r[<kESn vQ 4?i(Ku^ch6GhP$yE/R@3"4 ?A}9}m!%[yR^oo rRh[HYYWylY_ YpF}[E;>9&/Q V&y(<8.1@  "L+6s+[uxtWY\C6Y2GQxC$/%Y4hpHl8[GilByk Ly.xrmpV'IPivrddllYAx~# #  5:RGnUNU9~VmZ70xtfo,J{7]NKco;k+mR!Xol#_w lF",4ugvIy(Q483p4j7R*>5v<rB,~jZE,KegE.Q\u#e#V+n% 6u4eu5VeSu'>G8itP[t 7HO45FAsYLwUoS1GRD>`-AbstDHAa J;lMW-y*F4_P8vs_]?pQh9-=4Z9 +QN<E)WM2H@}!m[|mII]7l /:*)B,+O>dpcZQ'a{@ucwI`5*dh8v c!|]%y-W>a(*P{_CMiOrcJK'_;S5 (eF>Lif+ip0'2II8jg 1,%Mq=ebg7CkVD{bniMBF Vf)mR^L\g(- ( Q'V Qa9u}v!&%3Zcs`&)>FNvZ8x={yOQ A*-}o US89Nkx]NW7&yk#CAh#Vix~ Ak5R>kr> 5KbIu ml$H(MPQPq `XV6(Rk62hNAt "CZcpKGu!bt14}Ky?g/k>2ow TM- M38$*fp$,W6JfFz5<>y,Q(Qn_~;;tpYr)9r'uSY8VeL9JBKh;n3^~c.$>q7"*.L rwu7=_4q-]X+)6U]k*2QH8TIUr},$)4$f?3OIlkKi.lRs=&} sXEeS@ + Ox'%Xqq e `F+4L a#`Q"ZP!v<&THi)m1j.Ac1_:mliYm+(cc~er=wo%u%KY73OO-ljuvY*9/Rg9e^ @.lvrxK \dY^`e'[!Fgv49!!6xtX[uVhp>-GHGoS|ZV`?(2XC}yj=;C^Y [VVR8?ZG:) M.eA(cd .  Bw7"05&7{su`]!sp^W/u9;e29 LOnk4B Oet=Mvo3G:$0vW-N|V{)L:XzEuV13e,Vr#1 u[>I"nn*w0C_vFf;!Hm 2y Z)7|CHtr]t3OV(Ofd:Wf5k2|%K~U#kuM -cEvXmq_:3vMR@34NX-MpQ?W. G@ H1ryq]mD gju,RMXFj.13Mp_%|4Clu!Q2Q!?p3hQY*p4GGu1)AUScuvM:0r@*>zDT0(~>8!1$-?`n(W2sW*{oRZN=W p1$slSDI36qx^)jbsmh&,ao E(cN V<=EF-Aq&`bPhw#1i:BYf`Poa=Wvu M :JT!'WuPLzNhw cE#h;+ xI"pT]}3d 5*cA x"] ]!|KxaSxg*;4g4^ _ O'^IK-%e2Ocs.Oz( ^da+@4QB;jj!W\#~'vR?LJ3w"FlI}>1$l {Z7n@D3Rz1NJxb4v==Pe%Ji>?ENlbIF:n+0 <^bt+?[UGJK Y9gPe1 jYJ;r;0M@41[ dPtoxmXa#NbVp<fy3$zU|fw?Fx(06 d f+CmN!`#q) yw=kN#QRYA C*Bym@9  j5@+,)=TA`O\L8S[ga!G%175,;j'PoH6F-Y~Yw2jg=KiA1yD-_& `ry W+ft+NJpQC.Ru# 1jA;)]E f`Z=$LinB9QV' $Ty+tpW5c'`(I]$XG_*#1nY[?y.H0@1u.cZg8LmE9wEFV;@l)h0\RW|$NlTlf%P7%gsu:10`1-xe`QQa|rI: M?? $Vk >XOYqIXj+d.w` E" >UOMXp=B'o(O(@09e;^ZLPNOmzRIn|KFk} [F\|x5N2&yK%&nndH(~~/$Za_Q*^\MU6<w` 6SRCLDP7.3LDncrP>%fcq~8Ua`Pl?OT6}bgFo/]@u-ip62oY ; CdN!zl&91l.S dV7RS->`r9'P.d-xK"]LM5m=u5[@);QJr=gvJ0[E& h?j@!~A^S$XG^Yi!f&xC-,|g,6[<a2AK)6{Jq b8Y>d7ZE 7*?xs&lm. nk(g;u{kf!QPX&*R!2AU"*epSb_ 83 _w)vw[T3sVolcN<]gm:YRtJ`3NJyaK> L(C`S 2#X8kk0Q)bL{_n>JJ4e^\B>Z^!]h 2uDY6Qqg2ei 3 sAQ{n(s'^H*O<_|tZ<TEb/o9y"4$XNq,; +@ 1)>KG@R_d_{y_ MD \*OSV? %5YPQE\ y+ BE.4, H]"sJlFo2Ubz7vp"9B^F ySPaO]M'4#xsCA\(t5iuR\Y/H^fb9?['=ABA}CX@{yT 17R0 RMp-MQ]}"/2fO I&|@}1hAx%fvA=2U@O%PFZdxL<WwB!f{v:"Xfp^sYho+! ( \i=n"Ib%TT^`ucb@X(Yk}4O!\T }rt 8gIqr16Lxt*9BWw8ZRDyz8X}Y=tU'7wbjmn<&zqj*tp9w<JRFFVpTHr3YNPHN/O)2aOQtbWSA(%HpzRtn3 2%hw:nRQ'5w.-*U  :oNPEaOmGI<|#xbCZR%6'jKabeW/0F,eNBm: {0ujN\%OFR}PflH/aI -  ^8;)x2p[~=I{9j,q/N0'iqX}1sD\[G\hrWU t<4C/e'xJf<*UE(~G3:j]9D6o7x5'nixNnc**tG1c!~&26Kv4c3D V3nw1O^! q#A(ES J@E&e!LqueW/C6(-F0 $BxXgb|W].g ]4dJ5nRLrE:fLsx<=6~ ])o}vZm+gC&M1jL hfK:didPJ3T)11QtI:b+Kr::\+OF:1i'qDKZae;pe~nk6` [44-![es9_*RlhgUaG,wV@\*mRS[M$Y?$f3=Bxyd2*hH&.^x#6#+|7Ic.&7*6Jkm0hFdSa?n/2uw:+,5V^cy$T(0) <+g0F&R4j" V&>_rA6W8'T[jqx?V}!+D9$og=[bFYa<K`bZ;'4-}1ey#CylnJiT,m[NrI  ^$X _1T- Im-DS 8o*IQ[2+20` t^2$%\WOwUfAM2I pHokK-&l ?s^WCf&.w[q7%=D%8QCLLew\j :q*#p YZm.! AYJ> 5|8!_O;msM86 o*Kv8f(qZQ ,]^ekqTkt &nM$T_R=H&(3R+8l=E=Zm40/2HH6nha"`wf.7p1MSo@H&`$+v"C5jV!ipMb3e~FHv;@|<qXW`7UvsmV Jp|NPc(X]E%"HWjh# rDOnMV9Yk>XyK@p>k\ (,ggK %f1&C1 FWL41 #5VXjLnz)hZDo?[5X8tQt,0 o.P]E~i{XQsoZ4\8beWKlB3g hT_r></z0Bsb5. TFd1 e;iGG^*91`6j!"~nXe$I:h\`l^"UBA&f7B{RJrl6Z^Z858wl qOz|HD3|Lf] iCCoxZiFu'##9 +!kHlE9z _ W0 9hd-BMnv23+A7:-8r<|-f)F3)Ki{0g%CP2wJ/+knv9,8 St * _l#->+@,eIhYU0 E7GSxlQntu/;+v`_#_ =?mWv<avk=>z.}+PzC!DqmSMV-e+a>`L,NOlj5]b7 PR=w,^So9 1""FW' !3{) t56PQxsng c 9>F|4`ai+{("ghZ-B>0}T3(=n^J(Ti`Oj ql#H0MUD j(a9 OB1"9FXv 'SPPW. G'k)uVM wg*D7gn)?0cI\XbJ&^\|D1/YN%.^M5}a>d)Y|<$F&2)d+wY j=NlM Hi,32)kH_id\eFz?I@fPw@uqP Z 2]gPUE$ mONJtw %'i\ >KVS/@A#qJS9.Io-_]YAd>h{bLUpKm '*:OPTud.3dY,gXL"gJ!yMg8,7dqQEB}bs|/G(dnezNM{l' u+{/KA&nPqe]+8 .r$ 2wU[Z@( BR1*0G5A;iuJG#fpS"= @1zCcG:1nT][;d?|%h?okO]|Co: (x0|I<9:-OAi"rvlfByY kGkPT>4 hu2Tlf6q1./Tz`p/Z`w6fBm/rko%wNDL&wc>?^ g(`0TU0[,OhgyR# a-z_x< 1(5v$S`l*Y^Po@Nu L:4|Z("G. ]eU=kT{iq ;EQ'rd4X)OEH!4_M 1"D~V]TcGLHSu xC07| Ef]t$c 3t V\t5f`C?`FU;-a{c =&.iIW2"B+x!AmI<@ W} Xt_!7HV* Zosc |~z*~1W,] < *a8Gc: 1tO&s_G sla1%@RNvB?9d{)q^VoOD%}4-6, :zq|/ZCwr"?AqY{=?V$j Ffi F\IUg*%bWOd_J<4,u_zq -hG~lBD*( >F /a$@)u1Dk>}H^~ U)7Yk@>Fi7'd~r2]7Stkw~.,5w#a4;s)V/rMf88" aOA!x5jg"~H{ts{H% oU e~:|{r D^~573%1YiTF}1#5]FuBlbC]geySe*K,9*r19\KO!K]vhHa@ eNgpq-@mi{TC{) >?x?/C]D;m{)+J:TJ#TU1{S: F1.%& ~]jSL)9 %<"'o$|ha;C5+ 5fw@GG.Xqej-a~z@Il!"|V& t rp"sjc%`" >'O /T!$+y*sa1Ff:GnpV 1sf6#qt=&z*aZZWL>ry/_&&9r[. GRJ ~DW=w+=ZX&WdX`[ 8|[M,g I?rr~dH |OV%Aq|i?GT)1/:n[8W~81 z'\Fe4' +/COAv^]E U A1V=IU |j"l[LO` bC"=6 Kplu Cm#&3Xk+1j6p_ j7UY'B-1Gvq_UnV.O6G $2FIvnk7> '!g'j`7w"p$`<IGE"<9>|' wI+e}qyZPec2P6Oo:t>iaF 6<{sXk SJRpJK'q$eQ[~IPdl 3oxrv8Q50>[ /Xh4mm:^dOScMqDS iEx/#J.'jQ ( @tsBvKWLfH UmI}eh.C xF<[q)+]>.R8d",OH:;uSUQLA&Y-E2o"QfH\ApsieYBLC% #Dvky/w>j`qVGK_ npgSbez+:-l9~c`EC,n-IB>?rl*GB#|rv-dA\/r|[cn1Pgy>1Q4W{x*HA=f&ZFl;}eK"T-jO nPemO}G8*nMo68y p0y.[5ysp7z*!}Q|t&Se|fd?T!} vP/XFXeeGS7482nX[o+C,M3Z^EmkQ&-e&'(}\+$JQPCtyIvbRQLj[}cfn9 "kVZ\qtu;OZL8&Z ^&: sMr0e.NdCUkc"!-;-5,QA|p*q,p<% o\@ -D.~!7-q I2*p @yY2n@{=vaeahe_ AvtNAV-;YZjQ)PlAW?e L\3P2Z*9hhh{UjpQ(1]Oz"2''^OC|gCQa!OiNF&= j1wb~6]bX7+O }x2>2_`|VFOs$T+c~t\$E=N P9jQN)w;_$,$eT.2*6pjO->sV ,*$ Ln\ R--Yg7rGohSc?29 '  Hed1nS^gn\'kLQry "0X Ckbw4kST=fRAT3!j d;YL\YzwM[t%wk&  D-[ms9'ij`j|\3 | mGT; EUbhW8'oe}a "D\qe90wsikb\$Dg|'H w1yIl -NMWhX$o&d}i ;,df,S<}bME-IJX6?f@*Bq(t.qWK@7q ZOkc}KwVANr 5:\*~M*K| LavJ,> q6zilU T%n:=tM+xM #@f@9*f9jo U8IC3:H*I  99c6dxPv2` Hn?"rC^ }(&4ic5aqd(r D186c~@ >pmJ 8G\0/5V+}TTv) lH;}BJEV yfGS#!I:-9u]PJVZ/= % 7]PRDT5#V(Z)O%,'Bq0BW-~{"gf=+"LcdQ'lA]()u'q4o*@d=#P}G LptH$U__  As & kRzmi} !?QDC =Q[G`eZ'O _DW0P\c|6 }9G^Q-@FDp AqEYjoB#Ub:D7 dvmzRYIs])2'gs+jho dS"oPNxJG\\  t=koqfNBD4+N@  VtnF\w7kTV=]7Pg:x'zi {w|BcC*\Mj YTsc4kVY_1Q:d HK*BSEu'P*X?x3-tv -p= H8$Iz1j_ccMm|TI-,G{jIs6N"UtABV|?ZqZ*3 BNP6g_Ft52KjL veEQ2]>Ju}&z- q~_y "Na'[>BO[$>6np$$cP8QD[eqcc v1=s"z b(Ap& [KNnjwJ lQF/h5r-ij.4l sn="vrtp ={>]l{B:Cnq!V[|}/CXb_e.Ve-6]jjdv}[.lr}X:<hvKCO}  cL{?b{b:M>l0LT#ywV:x)(_p%-w_.9Ex%3DFb7<H.%FdxYsV{{T6f`H>qVP>`[7?$h| H&dx#GrnM}nCL5zUhqe4Bn /b-etPBu!.4,?AhD6-_wUE_-Cgk5d Y13S 45gm6HO{[ 5_!:h|S6E8nT?vF#g2d55CeAJ%0^mat90QmNGaY\{9fe9S2WH6NP%)K#BE ect 'J]}5-Ff+ ALgj|\f[=D[6f`9@4@uL) 59N2a|u6)||v|'dy/)_i6!c1'%~t4VO!9*jpiu\>VPKSwd[xp G1 aW2KnwlB YoMlFHrS+xUF*L69MT6xI X{"ZYu)5[O7t42. PBo -D7 VZ^0S<[E0Z56Df}Wc`zV.4]S6rSS^pM{8oumCi{54iNB'wYO e{y#?Hkf8XD]Ma:9giada-0.14.5/tests/utils.cpp000066400000000000000000000024111322662744500156650ustar00rootroot00000000000000#include "../src/utils/fs.h" #include "../src/utils/string.h" #include "catch/single_include/catch.hpp" using std::vector; TEST_CASE("Test filesystem utils") { REQUIRE(gu_fileExists("giada_tests") == true); REQUIRE(gu_fileExists("ghost_file") == false); REQUIRE(gu_dirExists("src/") == true); REQUIRE(gu_dirExists("ghost_dir/") == false); REQUIRE(gu_isDir("src/") == true); REQUIRE(gu_isDir("giada_tests") == false); REQUIRE(gu_basename("tests/utils.cpp") == "utils.cpp"); REQUIRE(gu_dirname("tests/utils.cpp") == "tests"); REQUIRE(gu_getExt("tests/utils.cpp") == "cpp"); REQUIRE(gu_stripExt("tests/utils.cpp") == "tests/utils"); } TEST_CASE("Test string utils") { REQUIRE(gu_replace("Giada is cool", "cool", "hot") == "Giada is hot"); REQUIRE(gu_trim(" Giada is cool ") == "Giada is cool"); REQUIRE(gu_iToString(666) == "666"); REQUIRE(gu_iToString(0x99AABB, true) == "99AABB"); REQUIRE(gu_fToString(3.14159, 2) == "3.14"); // Catch can't handle this so far? //REQUIRE(gu_format("I see %d men with %s hats", 5, "strange") == "I see 5 men with strange hats"); vector v; gu_split("Giada is cool", " ", &v); REQUIRE(v.size() == 3); REQUIRE(v.at(0) == "Giada"); REQUIRE(v.at(1) == "is"); REQUIRE(v.at(2) == "cool"); } giada-0.14.5/tests/wave.cpp000066400000000000000000000033771322662744500155030ustar00rootroot00000000000000#include #include "../src/core/wave.h" #include "catch/single_include/catch.hpp" using std::string; TEST_CASE("Test Wave class") { static const int SAMPLE_RATE = 44100; static const int BUFFER_SIZE = 4096; static const int CHANNELS = 2; static const int BIT_DEPTH = 32; /* Each SECTION the TEST_CASE is executed from the start. Any code between this comment and the first SECTION macro is exectuted before each SECTION. */ std::unique_ptr wave; SECTION("test basename") { wave = std::unique_ptr(new Wave(nullptr, BUFFER_SIZE, CHANNELS, SAMPLE_RATE, BIT_DEPTH, "path/to/sample.wav")); REQUIRE(wave->getPath() == "path/to/sample.wav"); REQUIRE(wave->getBasename() == "sample"); REQUIRE(wave->getBasename(true) == "sample.wav"); } SECTION("test path") { wave = std::unique_ptr(new Wave(nullptr, BUFFER_SIZE, CHANNELS, SAMPLE_RATE, BIT_DEPTH, "path/to/sample.wav")); wave->setPath("path/is/now/different.mp3"); REQUIRE(wave->getPath() == "path/is/now/different.mp3"); wave->setPath("path/is/now/different.mp3", 5); REQUIRE(wave->getPath() == "path/is/now/different-5.mp3"); } SECTION("test change name") { wave = std::unique_ptr(new Wave(nullptr, BUFFER_SIZE, CHANNELS, SAMPLE_RATE, BIT_DEPTH, "path/to/sample.wav")); REQUIRE(wave->getPath() == "path/to/sample.wav"); REQUIRE(wave->getBasename() == "sample"); REQUIRE(wave->getBasename(true) == "sample.wav"); } SECTION("test memory cleanup") { float* data = new float[BUFFER_SIZE]; wave = std::unique_ptr(new Wave(data, BUFFER_SIZE, CHANNELS, SAMPLE_RATE, BIT_DEPTH, "path/to/sample.wav")); wave->clear(); REQUIRE(wave->getData() == nullptr); REQUIRE(wave->getPath() == ""); REQUIRE(wave->getSize() == 0); } } giada-0.14.5/tests/waveFx.cpp000066400000000000000000000066561322662744500160040ustar00rootroot00000000000000#include #include "../src/core/const.h" #include "../src/core/wave.h" #include "../src/core/waveFx.h" #include "catch/single_include/catch.hpp" using std::string; using namespace giada::m; TEST_CASE("Test waveFx") { static const int SAMPLE_RATE = 44100; static const int BUFFER_SIZE = 4000; static const int BIT_DEPTH = 32; std::unique_ptr waveMono = std::unique_ptr(new Wave(new float[BUFFER_SIZE], BUFFER_SIZE, 1, SAMPLE_RATE, BIT_DEPTH, "path/to/sample.wav")); std::unique_ptr waveStereo = std::unique_ptr(new Wave(new float[BUFFER_SIZE * 2], BUFFER_SIZE, 2, SAMPLE_RATE, BIT_DEPTH, "path/to/sample.wav")); SECTION("test mono->stereo conversion") { int prevSize = waveMono->getSize(); REQUIRE(wfx::monoToStereo(waveMono.get()) == G_RES_OK); REQUIRE(waveMono->getSize() == prevSize); // size does not change, channels do REQUIRE(waveMono->getChannels() == 2); SECTION("test mono->stereo conversion for already stereo wave") { /* Should do nothing. */ int prevSize = waveStereo->getSize(); REQUIRE(wfx::monoToStereo(waveStereo.get()) == G_RES_OK); REQUIRE(waveStereo->getSize() == prevSize); REQUIRE(waveStereo->getChannels() == 2); } } SECTION("test silence") { int a = 20; int b = 57; wfx::silence(waveStereo.get(), a, b); for (int i=a; igetFrame(i); for (int k=0; kgetChannels(); k++) REQUIRE(frame[k] == 0.0f); } SECTION("test silence (mono)") { wfx::silence(waveMono.get(), a, b); for (int i=a; igetFrame(i); for (int k=0; kgetChannels(); k++) REQUIRE(frame[k] == 0.0f); } } } SECTION("test cut") { int a = 47; int b = 210; int range = b - a; int prevSize = waveStereo->getSize(); REQUIRE(wfx::cut(waveStereo.get(), a, b) == G_RES_OK); REQUIRE(waveStereo->getSize() == prevSize - range); SECTION("test cut (mono)") { prevSize = waveMono->getSize(); REQUIRE(wfx::cut(waveMono.get(), a, b) == G_RES_OK); REQUIRE(waveMono->getSize() == prevSize - range); } } SECTION("test trim") { int a = 47; int b = 210; int area = b - a; REQUIRE(wfx::trim(waveStereo.get(), a, b) == G_RES_OK); REQUIRE(waveStereo->getSize() == area); SECTION("test trim (mono)") { REQUIRE(wfx::trim(waveMono.get(), a, b) == G_RES_OK); REQUIRE(waveMono->getSize() == area); } } SECTION("test fade") { int a = 47; int b = 500; wfx::fade(waveStereo.get(), a, b, wfx::FADE_IN); wfx::fade(waveStereo.get(), a, b, wfx::FADE_OUT); REQUIRE(waveStereo->getFrame(a)[0] == 0.0f); REQUIRE(waveStereo->getFrame(a)[1] == 0.0f); REQUIRE(waveStereo->getFrame(b)[0] == 0.0f); REQUIRE(waveStereo->getFrame(b)[1] == 0.0f); SECTION("test fade (mono)") { wfx::fade(waveMono.get(), a, b, wfx::FADE_IN); wfx::fade(waveMono.get(), a, b, wfx::FADE_OUT); REQUIRE(waveMono->getFrame(a)[0] == 0.0f); REQUIRE(waveMono->getFrame(b)[0] == 0.0f); } } SECTION("test smooth") { int a = 11; int b = 79; wfx::smooth(waveStereo.get(), a, b); REQUIRE(waveStereo->getFrame(a)[0] == 0.0f); REQUIRE(waveStereo->getFrame(a)[1] == 0.0f); REQUIRE(waveStereo->getFrame(b)[0] == 0.0f); REQUIRE(waveStereo->getFrame(b)[1] == 0.0f); SECTION("test smooth (mono)") { wfx::smooth(waveMono.get(), a, b); REQUIRE(waveMono->getFrame(a)[0] == 0.0f); REQUIRE(waveMono->getFrame(b)[0] == 0.0f); } } } giada-0.14.5/tests/waveManager.cpp000066400000000000000000000034301322662744500167640ustar00rootroot00000000000000#include #include "../src/core/waveManager.h" #include "../src/core/wave.h" #include "../src/core/const.h" #include "catch/single_include/catch.hpp" using std::string; using namespace giada::m; #define G_SAMPLE_RATE 44100 #define G_BUFFER_SIZE 4096 #define G_CHANNELS 2 TEST_CASE("Test waveManager") { /* Each SECTION the TEST_CASE is executed from the start. Any code between this comment and the first SECTION macro is exectuted before each SECTION. */ Wave* w; SECTION("test creation") { int res = waveManager::create("tests/resources/test.wav", &w); std::unique_ptr wave(w); REQUIRE(res == G_RES_OK); REQUIRE(wave->getRate() == G_SAMPLE_RATE); REQUIRE(wave->getChannels() == G_CHANNELS); REQUIRE(wave->isLogical() == false); REQUIRE(wave->isEdited() == false); } SECTION("test recording") { int res = waveManager::createEmpty(G_BUFFER_SIZE, G_SAMPLE_RATE, "test.wav", &w); std::unique_ptr wave(w); REQUIRE(res == G_RES_OK); REQUIRE(wave->getRate() == G_SAMPLE_RATE); REQUIRE(wave->getSize() == G_BUFFER_SIZE / wave->getChannels()); REQUIRE(wave->getChannels() == G_CHANNELS); REQUIRE(wave->isLogical() == true); REQUIRE(wave->isEdited() == false); } SECTION("test resampling") { int res = waveManager::create("tests/resources/test.wav", &w); std::unique_ptr wave(w); REQUIRE(res == G_RES_OK); int oldSize = wave->getSize(); res = waveManager::resample(wave.get(), 1, G_SAMPLE_RATE * 2); REQUIRE(res == G_RES_OK); REQUIRE(wave->getRate() == G_SAMPLE_RATE * 2); REQUIRE(wave->getSize() == oldSize * 2); REQUIRE(wave->getChannels() == G_CHANNELS); REQUIRE(wave->isLogical() == false); REQUIRE(wave->isEdited() == false); } }