--- /dev/null
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ 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.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+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:
+
+ <program> Copyright (C) <year> <name of author>
+ 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
+<http://www.gnu.org/licenses/>.
+
+ 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
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
--- /dev/null
+
+--------------------------------------------------------------------------------
+
+
+ Giada - Your Hardcore Loopmachine.
+
+ Developed by Monocasual Laboratories
+
+ www.giadamusic.com
+
+ CHANGELOG
+--------------------------------------------------------------------------------
+
+
+0.15.2 ---
+- New sample-accurate Action Editor
+- New MIDI Velocity Editor widget
+- Ability to move MIDI events vertically in piano roll (i.e. change note)
+- Remove mute action recording
+- Better handling of MIDI devices that send NOTEON + velocity 0 as NOTEOFF
+- Avoid calls to deprecated JUCE plug-ins methods
+- Removed useless pthreadGC2.dll from Windows package
+- Can't kill MIDI channels (fix #197)
+- Can't record MIDI actions (fix #202)
+- Fix missing first beat on metronome rendering
+- Fix crash on opening plug-in window on macOS
+
+
+0.15.1 --- 2018 . 07 . 03
+- Deep code refactoring, featuring Channels processors
+- Many new unit tests added
+- Simplify mutex mechanism
+- Fix wrong quantizer value on patch/project load
+- Remove the old, buggy and glitchy internal crossfade algorithm
+- Fix many potential plug-in crashes on Linux
+- Properly close plug-in window on plug-in removal
+- Improve BPM changes while running as JACK client
+
+
+0.15.0 --- 2018 . 04 . 18
+- Refactor audio engine into frame-based processing
+- Refactor channels readers/writers into channelManager namespace
+- Smarter Solo algorithm
+- Fix missing .wav extension on recorded audio takes
+- Fix wrong Channel status update after 'Clear all actions'
+
+
+0.14.6 --- 2018 . 03 . 15
+- MIDI velocity drives volume for one-shot sample channels
+- FLAC and Ogg support
+- Ability to use system-provided Catch library (GitHub #151)
+- Update Catch to version 2
+- Fix unreadable tabs title in Configuration Window (GitHub #168)
+- Fix crash on opening About window
+- Fix 'read actions' button behavior during ending and waiting statuses
+- Fix sound card initialization on MacOS
+- [Windows] Fix UI stuck on top-right corner
+- [Windows] Fix browsing for directories
+
+
+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 <name>-<version>.<ext>
+- 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)
--- /dev/null
+AUTOMAKE_OPTIONS = foreign
+
+# Define conditional variables -------------------------------------------------
+# WITH_VST, LINUX, WINDOWS and OSX are varibles defined via AM_CONDITIONAL
+# inside configure.ac.
+
+
+cppFlags =
+cxxFlags = -std=c++11 -Wall
+ldAdd =
+ldFlags =
+sourcesExtra =
+sourcesMain = src/main.cpp
+sourcesCore = \
+ src/core/const.h \
+ src/core/types.h \
+ src/core/range.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/audioBuffer.h \
+ src/core/audioBuffer.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/core/channelManager.h \
+ src/core/channelManager.cpp \
+ src/core/sampleChannelProc.h \
+ src/core/sampleChannelProc.cpp \
+ src/core/sampleChannelRec.h \
+ src/core/sampleChannelRec.cpp \
+ src/core/midiChannelProc.h \
+ src/core/midiChannelProc.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/about.h \
+ src/gui/dialogs/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/pluginChooser.h \
+ src/gui/dialogs/pluginChooser.cpp \
+ src/gui/dialogs/actionEditor/baseActionEditor.h \
+ src/gui/dialogs/actionEditor/baseActionEditor.cpp \
+ src/gui/dialogs/actionEditor/sampleActionEditor.h \
+ src/gui/dialogs/actionEditor/sampleActionEditor.cpp \
+ src/gui/dialogs/actionEditor/midiActionEditor.h \
+ src/gui/dialogs/actionEditor/midiActionEditor.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/plugin/pluginElement.h \
+ src/gui/elems/plugin/pluginElement.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/baseAction.h \
+ src/gui/elems/actionEditor/baseAction.cpp \
+ src/gui/elems/actionEditor/envelopeEditor.h \
+ src/gui/elems/actionEditor/envelopeEditor.cpp \
+ src/gui/elems/actionEditor/velocityEditor.h \
+ src/gui/elems/actionEditor/velocityEditor.cpp \
+ src/gui/elems/actionEditor/envelopePoint.h \
+ src/gui/elems/actionEditor/envelopePoint.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/pianoItem.h \
+ src/gui/elems/actionEditor/pianoItem.cpp \
+ src/gui/elems/actionEditor/sampleActionEditor.h \
+ src/gui/elems/actionEditor/sampleActionEditor.cpp \
+ src/gui/elems/actionEditor/sampleAction.h \
+ src/gui/elems/actionEditor/sampleAction.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/ver.h \
+ src/utils/ver.cpp \
+ src/utils/string.h \
+ src/utils/string.cpp \
+ src/deps/rtaudio-mod/RtAudio.h \
+ src/deps/rtaudio-mod/RtAudio.cpp
+sourcesTests = \
+ 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 \
+ tests/audioBuffer.cpp \
+ tests/sampleChannel.cpp \
+ tests/sampleChannelProc.cpp \
+ tests/sampleChannelRec.cpp
+
+if WITH_VST
+
+sourcesExtra += \
+ 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$(top_srcdir)/src/deps/juce/modules \
+ -I$(top_srcdir)/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 !WITH_SYSTEM_CATCH
+
+cppFlags += -I$(top_srcdir)/tests/catch2/single_include
+
+endif
+
+if WINDOWS
+
+sourcesExtra += \
+ 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$(top_srcdir)/src/deps/rtaudio-mod/include \
+ -D__WINDOWS_ASIO__ \
+ -D__WINDOWS_WASAPI__ \
+ -D__WINDOWS_DS__
+
+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 -lflac -lvorbis -logg -lvorbisenc
+
+# 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__
+
+ldAdd += -lsndfile -lfltk -lXext -lX11 -lXft -lXpm -lm -ljack -lasound \
+ -lpthread -ldl -lpulse-simple -lpulse -lsamplerate -lrtmidi -ljansson \
+ -lfreetype
+
+endif
+
+if OSX
+
+sourcesExtra += src/utils/cocoa.mm src/utils/cocoa.h
+
+# Add preprocessor flags to enable CoreAudio in RtAudio.
+cppFlags += -D__MACOSX_CORE__
+
+# -ObjC++: Juce requires to build some Objective C code
+cxxFlags += -ObjC++
+
+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 = $(sourcesCore) $(sourcesMain) $(sourcesExtra)
+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 = $(sourcesCore) $(sourcesExtra) $(sourcesTests)
+giada_tests_CPPFLAGS = $(cppFlags)
+giada_tests_CPPFLAGS += -DTESTS
+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
--- /dev/null
+# Giada - Your Hardcore Loopmachine
+
+Official website: http://www.giadamusic.com | Travis CI status: [](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)
+
+
+
+## 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](https://www.giadamusic.com/documentation-index).
+
+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](https://www.giadamusic.com/documentation-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](https://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 <http://www.gnu.org/licenses/>.
--- /dev/null
+#!/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 <morrison@brlcad.org>
+#
+# Patches:
+# Sebastian Pipping <sebastian@pipping.org>
+#
+######################################################################
+
+# 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 <<EOF
+$autoconf_output
+EOF
+ $ECHO "ERROR: $AUTOCONF failed"
+ exit 2
+ else
+ # autoconf sans -f and possibly sans unsupported options succeed so warn verbosely
+ $ECHO
+ $ECHO "Warning: autoconf seems to have succeeded by removing the following options:"
+ $ECHO " AUTOCONF_OPTIONS=\"$AUTOCONF_OPTIONS\""
+ $ECHO
+ $ECHO "Removing those options should not be necessary and indicate some other"
+ $ECHO "problem with the build system. The build preparation is highly suspect"
+ $ECHO "and may result in configuration or compilation errors. Consider"
+ if [ "x$VERBOSE_ECHO" = "x:" ] ; then
+ $ECHO "rerunning the build preparation with verbose output enabled."
+ $ECHO " $AUTOGEN_SH --verbose"
+ else
+ $ECHO "reviewing the minimum GNU Autotools version settings contained in"
+ $ECHO "this script along with the macros being used in your `basename \"$CONFIGURE\"` file."
+ fi
+ $ECHO
+ $ECHO $ECHO_N "Continuing build preparation ... $ECHO_C"
+ fi # autoconf ret = 0
+ fi # autoconf ret = 0
+
+ ##############
+ # autoheader #
+ ##############
+ need_autoheader=no
+ for feature in AM_CONFIG_HEADER AC_CONFIG_HEADER ; do
+ $VERBOSE_ECHO "Searching for $feature in $CONFIGURE"
+ found="`grep \"^$feature.*\" $CONFIGURE`"
+ if [ ! "x$found" = "x" ] ; then
+ need_autoheader=yes
+ break
+ fi
+ done
+ if [ "x$need_autoheader" = "xyes" ] ; then
+ $VERBOSE_ECHO "$AUTOHEADER $AUTOHEADER_OPTIONS"
+ autoheader_output="`$AUTOHEADER $AUTOHEADER_OPTIONS 2>&1`"
+ ret=$?
+ $VERBOSE_ECHO "$autoheader_output"
+ if [ ! $ret = 0 ] ; then $ECHO "ERROR: $AUTOHEADER failed" && exit 2 ; fi
+ fi # need_autoheader
+
+ ############
+ # automake #
+ ############
+ need_automake=no
+ for feature in AM_INIT_AUTOMAKE ; do
+ $VERBOSE_ECHO "Searching for $feature in $CONFIGURE"
+ found="`grep \"^$feature.*\" $CONFIGURE`"
+ if [ ! "x$found" = "x" ] ; then
+ need_automake=yes
+ break
+ fi
+ done
+
+ if [ "x$need_automake" = "xyes" ] ; then
+ $VERBOSE_ECHO "$AUTOMAKE $AUTOMAKE_OPTIONS"
+ automake_output="`$AUTOMAKE $AUTOMAKE_OPTIONS 2>&1`"
+ ret=$?
+ $VERBOSE_ECHO "$automake_output"
+
+ if [ ! $ret = 0 ] ; then
+
+ ###################
+ # automake, retry #
+ ###################
+ ALT_AUTOMAKE_OPTIONS="$ALT_AUTOMAKE_OPTIONS --add-missing"
+ $VERBOSE_ECHO
+ $VERBOSE_ECHO "$AUTOMAKE $ALT_AUTOMAKE_OPTIONS"
+ # retry without the -f
+ automake_output="`$AUTOMAKE $ALT_AUTOMAKE_OPTIONS 2>&1`"
+ ret=$?
+ $VERBOSE_ECHO "$automake_output"
+
+ if [ ! $ret = 0 ] ; then
+ # test if libtool is busted
+ libtool_failure "$automake_output"
+
+ # let the user know what went wrong
+ cat <<EOF
+$automake_output
+EOF
+ $ECHO "ERROR: $AUTOMAKE failed"
+ exit 2
+ fi # automake retry
+ fi # automake ret = 0
+ fi # need_automake
+} # end of manual_autogen
+
+
+#####################################
+# RECURSIVE_MANUAL_AUTOGEN FUNCTION #
+#####################################
+recursive_manual_autogen ( ) {
+
+ # run the build preparation steps manually for this directory
+ manual_autogen
+
+ # for projects using recursive configure, run the build
+ # preparation steps for the subdirectories.
+ if [ ! "x$CONFIG_SUBDIRS" = "x" ] ; then
+ $VERBOSE_ECHO "Recursively configuring the following directories:"
+ $VERBOSE_ECHO " $CONFIG_SUBDIRS"
+ for dir in $CONFIG_SUBDIRS ; do
+ $VERBOSE_ECHO "Processing recursive configure in $dir"
+ cd "$START_PATH"
+ cd "$dir"
+
+ # new directory, prepare
+ initialize
+
+ # run manual steps for the subdir and any others below
+ recursive_manual_autogen
+ done
+ fi
+}
+
+
+################################
+# run manual preparation steps #
+################################
+if [ "x$reconfigure_manually" = "xyes" ] ; then
+ $ECHO
+ $ECHO $ECHO_N "Preparing build ... $ECHO_C"
+
+ recursive_manual_autogen
+fi
+
+
+#########################
+# restore and summarize #
+#########################
+cd "$START_PATH"
+
+# restore COPYING and INSTALL from backup if necessary
+recursive_restore
+
+# make sure we end up with a configure script
+config_ac="`locate_configure_template`"
+config="`echo $config_ac | sed 's/\.ac$//' | sed 's/\.in$//'`"
+if [ "x$config" = "x" ] ; then
+ $VERBOSE_ECHO "Could not locate the configure template (from `pwd`)"
+fi
+
+# summarize
+$ECHO "done"
+$ECHO
+if test "x$config" = "x" -o ! -f "$config" ; then
+ $ECHO "WARNING: The $PROJECT build system should now be prepared but there"
+ $ECHO "does not seem to be a resulting configure file. This is unexpected"
+ $ECHO "and likely the result of an error. You should run $NAME_OF_AUTOGEN"
+ $ECHO "with the --verbose option to get more details on a potential"
+ $ECHO "misconfiguration."
+else
+ $ECHO "The $PROJECT build system is now prepared. To build here, run:"
+ $ECHO " $config"
+ $ECHO " make"
+fi
+
+
+# Local Variables:
+# mode: sh
+# tab-width: 8
+# sh-basic-offset: 4
+# sh-indentation: 4
+# indent-tabs-mode: t
+# End:
+# ex: shiftwidth=4 tabstop=8
--- /dev/null
+# -*- Autoconf -*-
+# Process this file with autoconf to produce a configure script.
+
+# prereq & init
+
+AC_PREREQ(2.60)
+AC_INIT([giada], [0.15], [giadaloopmachine@gmail.com])
+AC_CONFIG_SRCDIR([src/main.cpp])
+AM_INIT_AUTOMAKE([subdir-objects])
+
+# ------------------------------------------------------------------------------
+
+# test the build environment. These vars are used in Makefile.am during
+# the linking of the libraries.
+# Usage: ./configure --target=[windows | linux | osx]
+
+if test "$target" = ""; then
+ AC_MSG_ERROR(["target OS not specified. Please run ./configure --target=<windows | linux | osx>"])
+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)]
+)
+
+# ------------------------------------------------------------------------------
+
+# --enable-system-catch. If enabled, use the system-provided Catch. Use bundled
+# version otherwise (default mode).
+
+AC_ARG_ENABLE(
+ [system-catch],
+ AS_HELP_STRING([--enable-system-catch], [use system-provided Catch library]),
+ [AC_DEFINE(WITH_SYSTEM_CATCH) AM_CONDITIONAL(WITH_SYSTEM_CATCH, true)],
+ [AM_CONDITIONAL(WITH_SYSTEM_CATCH, 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
--- /dev/null
+#include <new>
+#include <cassert>
+#include <cstring>
+#include "audioBuffer.h"
+
+
+namespace giada {
+namespace m
+{
+AudioBuffer::AudioBuffer()
+ : m_data (nullptr),
+ m_size (0),
+ m_channels(0)
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+AudioBuffer::~AudioBuffer()
+{
+ free();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+float* AudioBuffer::operator [](int offset) const
+{
+ assert(m_data != nullptr);
+ assert(offset < m_size);
+ return m_data + (offset * m_channels);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void AudioBuffer::clear(int a, int b)
+{
+ if (m_data == nullptr)
+ return;
+ if (b == -1) b = m_size;
+ memset(m_data + (a * m_channels), 0, (b - a) * m_channels * sizeof(float));
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+int AudioBuffer::countFrames() const { return m_size; }
+int AudioBuffer::countSamples() const { return m_size * m_channels; }
+int AudioBuffer::countChannels() const { return m_channels; }
+bool AudioBuffer::isAllocd() const { return m_data != nullptr; }
+
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void AudioBuffer::alloc(int size, int channels)
+{
+ free();
+ m_size = size;
+ m_channels = channels;
+ m_data = new float[m_size * m_channels];
+ clear(); // does nothing if m_data == nullptr
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void AudioBuffer::free()
+{
+ delete[] m_data; // No check required, delete nullptr does nothing
+ setData(nullptr, 0, 0);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void AudioBuffer::setData(float* data, int size, int channels)
+{
+ m_data = data;
+ m_size = size;
+ m_channels = channels;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void AudioBuffer::moveData(AudioBuffer& b)
+{
+ free();
+ m_data = b[0];
+ m_size = b.countFrames();
+ m_channels = b.countChannels();
+ b.setData(nullptr, 0, 0);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void AudioBuffer::copyFrame(int frame, float* values)
+{
+ assert(m_data != nullptr);
+ memcpy(m_data + (frame * m_channels), values, m_channels * sizeof(float));
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+void AudioBuffer::copyData(float* data, int frames, int offset)
+{
+ assert(m_data != nullptr);
+ assert(frames <= m_size - offset);
+ memcpy(m_data + (offset * m_channels), data, frames * m_channels * sizeof(float));
+}
+
+}} // giada::m::
\ No newline at end of file
--- /dev/null
+#ifndef G_AUDIO_BUFFER_H
+#define G_AUDIO_BUFFER_H
+
+
+namespace giada {
+namespace m
+{
+class AudioBuffer
+{
+public:
+
+ AudioBuffer();
+ ~AudioBuffer();
+
+ /* operator []
+ Given a frame 'offset', returns a pointer to it. This is useful for digging
+ inside a frame, i.e. parsing each channel. How to use it:
+
+ for (int k=0; k<buffer->countFrames(), k++)
+ for (int i=0; i<buffer->countChannels(); i++)
+ ... buffer[k][i] ...
+
+ Also note that buffer[0] will give you a pointer to the whole internal data
+ array. */
+
+ float* operator [](int offset) const;
+
+ int countFrames() const;
+ int countSamples() const;
+ int countChannels() const;
+ bool isAllocd() const;
+
+ void alloc(int size, int channels);
+ void free();
+
+ /* copyData
+ Copies 'frames' frames from the new 'data' into m_data, and fills m_data
+ starting from frame 'offset'. It takes for granted that the new data contains
+ the same number of channels than m_channels. */
+
+ void copyData(float* data, int frames, int offset=0);
+
+ /* copyFrame
+ Copies data pointed by 'values' into m_data[frame]. It takes for granted that
+ 'values' contains the same number of channels than m_channels. */
+
+ void copyFrame(int frame, float* values);
+
+ /* setData
+ Borrow 'data' as new m_data. Makes sure not to delete the data 'data' points
+ to while using it. Set it back to nullptr when done. */
+
+ void setData(float* data, int size, int channels);
+
+ /* moveData
+ Moves data held by 'b' into this buffer. Then 'b' becomes an empty buffer. */
+
+ void moveData(AudioBuffer& b);
+
+ /* clear
+ Clears the internal data by setting all bytes to 0.0f. Optional parameters
+ 'a' and 'b' set the range. */
+
+ void clear(int a=0, int b=-1);
+
+private:
+
+ float* m_data;
+ int m_size; // in frames
+ int m_channels;
+};
+
+}} // giada::m::
+
+#endif
\ No newline at end of file
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <cassert>
+#include <cstring>
+#include "../utils/log.h"
+#include "../gui/elems/mainWindow/keyboard/channel.h"
+#include "const.h"
+#include "channelManager.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"
+#include "channel.h"
+
+
+using std::string;
+using namespace giada;
+using namespace giada::m;
+
+
+Channel::Channel(ChannelType type, ChannelStatus status, int bufferSize)
+: guiChannel (nullptr),
+ type (type),
+ status (status),
+ recStatus (ChannelStatus::OFF),
+ previewMode (PreviewMode::NONE),
+ pan (0.5f),
+ volume (G_DEFAULT_VOL),
+ armed (false),
+ key (0),
+ mute (false),
+ solo (false),
+ volume_i (1.0f),
+ volume_d (0.0f),
+ hasActions (false),
+ readActions (false),
+ midiIn (true),
+ midiInKeyPress (0x0),
+ midiInKeyRel (0x0),
+ midiInKill (0x0),
+ midiInArm (0x0),
+ midiInVolume (0x0),
+ midiInMute (0x0),
+ midiInSolo (0x0),
+ midiInFilter (-1),
+ midiOutL (false),
+ midiOutLplaying(0x0),
+ midiOutLmute (0x0),
+ midiOutLsolo (0x0)
+{
+ buffer.alloc(bufferSize, G_MAX_IO_CHANS);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void Channel::copy(const Channel* src, pthread_mutex_t* pluginMutex)
+{
+ using namespace giada::m;
+
+ key = src->key;
+ volume = src->volume;
+ volume_i = src->volume_i;
+ volume_d = src->volume_d;
+ pan = src->pan;
+ 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; i<src->plugins.size(); i++)
+ pluginHost::clonePlugin(src->plugins.at(i), pluginHost::CHANNEL,
+ pluginMutex, this);
+#endif
+
+ /* clone actions */
+
+ for (unsigned i=0; i<recorder::global.size(); i++) {
+ for (unsigned k=0; k<recorder::global.at(i).size(); k++) {
+ recorder::action* a = recorder::global.at(i).at(k);
+ if (a->chan == src->index) {
+ recorder::rec(index, a->type, a->frame, a->iValue, a->fValue);
+ hasActions = true;
+ }
+ }
+ }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+bool Channel::isPlaying() const
+{
+ return status == ChannelStatus::PLAY || status == ChannelStatus::ENDING;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void Channel::writePatch(int i, bool isProject)
+{
+ channelManager::writePatch(this, isProject);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void Channel::readPatch(const string& path, int i)
+{
+ channelManager::readPatch(this, i);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void Channel::sendMidiLmute()
+{
+ if (!midiOutL || midiOutLmute == 0x0)
+ return;
+ if (mute)
+ kernelMidi::sendMidiLightning(midiOutLmute, midimap::muteOn);
+ else
+ kernelMidi::sendMidiLightning(midiOutLmute, midimap::muteOff);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void Channel::sendMidiLsolo()
+{
+ if (!midiOutL || midiOutLsolo == 0x0)
+ return;
+ if (solo)
+ kernelMidi::sendMidiLightning(midiOutLsolo, midimap::soloOn);
+ else
+ kernelMidi::sendMidiLightning(midiOutLsolo, midimap::soloOff);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void Channel::sendMidiLstatus()
+{
+ if (!midiOutL || midiOutLplaying == 0x0)
+ return;
+ switch (status) {
+ case ChannelStatus::OFF:
+ kernelMidi::sendMidiLightning(midiOutLplaying, midimap::stopped);
+ break;
+ case ChannelStatus::PLAY:
+ kernelMidi::sendMidiLightning(midiOutLplaying, midimap::playing);
+ break;
+ case ChannelStatus::WAIT:
+ kernelMidi::sendMidiLightning(midiOutLplaying, midimap::waiting);
+ break;
+ case ChannelStatus::ENDING:
+ kernelMidi::sendMidiLightning(midiOutLplaying, midimap::stopping);
+ break;
+ default:
+ break;
+ }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+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) const
+{
+ 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::calcVolumeEnvelope()
+{
+ volume_i += volume_d;
+ if (volume_i < 0.0f)
+ volume_i = 0.0f;
+ else
+ if (volume_i > 1.0f)
+ volume_i = 1.0f;
+}
+
+
+bool Channel::isPreview() const
+{
+ return previewMode != PreviewMode::NONE;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+bool Channel::isReadingActions() const
+{
+ return hasActions && readActions;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+#ifdef WITH_VST
+
+juce::MidiBuffer &Channel::getPluginMidiEvents()
+{
+ return midiBuffer;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void Channel::clearMidiBuffer()
+{
+ midiBuffer.clear();
+}
+
+
+#endif
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef G_CHANNEL_H
+#define G_CHANNEL_H
+
+
+#include <vector>
+#include <string>
+#include <pthread.h>
+#include "types.h"
+#include "mixer.h"
+#include "midiMapConf.h"
+#include "midiEvent.h"
+#include "recorder.h"
+#include "audioBuffer.h"
+
+#ifdef WITH_VST
+ #include "../deps/juce-config.h"
+#endif
+
+
+class Plugin;
+class MidiMapConf;
+class geChannel;
+
+
+class Channel
+{
+public:
+
+ virtual ~Channel() {};
+
+ /* copy
+ Makes a shallow copy (no internal buffers allocation) of another channel. */
+
+ virtual void copy(const Channel* src, pthread_mutex_t* pluginMutex) = 0;
+
+ /* parseEvents
+ Prepares channel for rendering. This is called on each frame. */
+
+ virtual void parseEvents(giada::m::mixer::FrameEvents fe) = 0;
+
+ /* process
+ Merges working buffers into 'out', plus plugin processing (if any). Warning:
+ inBuffer might be nullptr if no input devices are available for recording. */
+
+ virtual void process(giada::m::AudioBuffer& out, const giada::m::AudioBuffer& in,
+ bool audible, bool running) = 0;
+
+ /* start
+ Action to do when channel starts. doQuantize = false (don't quantize)
+ when Mixer is reading actions from Recorder. */
+
+ virtual void start(int localFrame, bool doQuantize, int velocity) = 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 localFrame) = 0;
+
+ /* set
+ What to do when channel is un/muted. */
+
+ virtual void setMute(bool value) = 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;
+
+ /* rewind
+ Rewinds channel when rewind button is pressed. */
+
+ virtual void rewindBySeq() = 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;
+
+ virtual bool hasLogicalData() const { return false; };
+ virtual bool hasEditedData() const { return false; };
+ virtual bool hasData() const { return false; };
+
+ virtual bool recordStart(bool canQuantize) { return true; };
+ virtual bool recordKill() { return true; };
+ virtual void recordStop() {};
+
+ /* prepareBuffer
+ Fill audio buffer with audio data from the internal source. This is actually
+ useful to sample channels only. */
+
+ virtual void prepareBuffer(bool running) {};
+
+ virtual void startReadingActions(bool treatRecsAsLoops,
+ bool recsStopOnChanHalt) {};
+ virtual void stopReadingActions(bool running, bool treatRecsAsLoops,
+ bool recsStopOnChanHalt) {};
+
+ virtual void stopInputRec(int globalFrame) {};
+
+ virtual void readPatch(const std::string& basePath, int i);
+ virtual void writePatch(int i, bool isProject);
+
+ /* receiveMidi
+ Receives and processes midi messages from external devices. */
+
+ virtual void receiveMidi(const giada::m::MidiEvent& midiEvent) {};
+
+ /* calcPanning
+ Given an audio channel (stereo: 0 or 1) computes the current panning value. */
+
+ float calcPanning(int ch) const;
+
+ bool isPlaying() const;
+ float getPan() const;
+ bool isPreview() const;
+
+ /* isMidiInAllowed
+ 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;
+
+ /* isReadingActions
+ Tells whether the channel as actions and it is currently reading them. */
+
+ bool isReadingActions() const;
+
+ /* sendMidiL*
+ Sends MIDI lightning events to a physical device. */
+
+ void sendMidiLmute();
+ void sendMidiLsolo();
+ void sendMidiLstatus();
+
+ void setPan(float v);
+
+ void calcVolumeEnvelope();
+
+#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
+
+ /* guiChannel
+ Pointer to a gChannel object, part of the GUI. TODO - remove this and send
+ signals instead. */
+
+ geChannel* guiChannel;
+
+ /* buffer
+ Working buffer for internal processing. */
+
+ giada::m::AudioBuffer buffer;
+
+ giada::ChannelType type;
+ giada::ChannelStatus status;
+ giada::ChannelStatus recStatus;
+
+ /* previewMode
+ Whether the channel is in audio preview mode or not. */
+
+ giada::PreviewMode previewMode;
+
+ float pan;
+ float volume; // global volume
+ bool armed;
+ std::string name;
+ int index; // unique id
+ int key; // keyboard button
+ bool mute; // global mute
+ bool solo;
+
+ /* volume_*
+ Internal volume variables: volume_i for envelopes, volume_d keeps track of
+ the delta during volume changes (or the line slope between two volume
+ points). */
+
+ float volume_i;
+ float volume_d;
+
+ bool hasActions; // has something recorded
+ bool readActions; // read what's recorded
+
+ 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;
+
+ /* midiInFilter
+ Which MIDI channel should be filtered out when receiving MIDI messages. -1
+ means 'all'. */
+
+ int midiInFilter;
+
+ /* 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 <Plugin*> plugins;
+#endif
+
+protected:
+
+ Channel(giada::ChannelType type, giada::ChannelStatus status, int bufferSize);
+
+#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
+};
+
+
+#endif
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include "../gui/elems/mainWindow/keyboard/channel.h"
+#include "../utils/fs.h"
+#include "const.h"
+#include "channel.h"
+#include "patch.h"
+#include "mixer.h"
+#include "wave.h"
+#include "waveManager.h"
+#include "sampleChannel.h"
+#include "midiChannel.h"
+#include "pluginHost.h"
+#include "plugin.h"
+#include "channelManager.h"
+
+
+using std::string;
+
+
+namespace giada {
+namespace m {
+namespace channelManager
+{
+namespace
+{
+void writeActions_(int chanIndex, patch::channel_t& pch)
+{
+ recorder::forEachAction([&] (const recorder::action* a) {
+ if (a->chan != chanIndex)
+ return;
+ pch.actions.push_back(patch::action_t {
+ a->type, a->frame, a->fValue, a->iValue
+ });
+ });
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void writePlugins_(const Channel* ch, patch::channel_t& pch)
+{
+#ifdef WITH_VST
+
+ pluginHost::forEachPlugin(pluginHost::CHANNEL, ch, [&] (const Plugin* p) {
+ patch::plugin_t pp;
+ pp.path = p->getUniqueId();
+ pp.bypass = p->isBypassed();
+ for (int k=0; k<p->getNumParameters(); k++)
+ pp.params.push_back(p->getParameter(k));
+ for (uint32_t param : p->midiInParams)
+ pp.midiInParams.push_back(param);
+ pch.plugins.push_back(pp);
+ });
+
+#endif
+}
+} // {anonymous}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void readActions_(Channel* ch, const patch::channel_t& pch)
+{
+ for (const patch::action_t& ac : pch.actions) {
+ recorder::rec(ch->index, ac.type, ac.frame, ac.iValue, ac.fValue);
+ ch->hasActions = true;
+ }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void readPlugins_(Channel* ch, const patch::channel_t& pch)
+{
+#ifdef WITH_VST
+
+ for (const patch::plugin_t& ppl : pch.plugins) {
+ Plugin* plugin = pluginHost::addPlugin(ppl.path, pluginHost::CHANNEL,
+ &mixer::mutex, ch);
+ if (plugin == nullptr)
+ continue;
+
+ plugin->setBypass(ppl.bypass);
+ for (unsigned j=0; j<ppl.params.size(); j++)
+ plugin->setParameter(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);
+ }
+ }
+
+#endif
+}
+
+
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+
+
+int create(ChannelType type, int bufferSize, bool inputMonitorOn, Channel** out)
+{
+ if (type == ChannelType::SAMPLE)
+ *out = new SampleChannel(inputMonitorOn, bufferSize);
+ else
+ *out = new MidiChannel(bufferSize);
+ return G_RES_OK;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+int writePatch(const Channel* ch, bool isProject)
+{
+ patch::channel_t pch;
+ pch.type = static_cast<int>(ch->type);
+ pch.index = ch->index;
+ pch.size = ch->guiChannel->getSize();
+ pch.name = ch->name;
+ pch.key = ch->key;
+ pch.armed = ch->armed;
+ pch.column = ch->guiChannel->getColumnIndex();
+ pch.mute = ch->mute;
+ pch.solo = ch->solo;
+ pch.volume = ch->volume;
+ pch.pan = ch->pan;
+ pch.midiIn = ch->midiIn;
+ pch.midiInKeyPress = ch->midiInKeyPress;
+ pch.midiInKeyRel = ch->midiInKeyRel;
+ pch.midiInKill = ch->midiInKill;
+ pch.midiInArm = ch->midiInArm;
+ pch.midiInVolume = ch->midiInVolume;
+ pch.midiInMute = ch->midiInMute;
+ pch.midiInFilter = ch->midiInFilter;
+ pch.midiInSolo = ch->midiInSolo;
+ pch.midiOutL = ch->midiOutL;
+ pch.midiOutLplaying = ch->midiOutLplaying;
+ pch.midiOutLmute = ch->midiOutLmute;
+ pch.midiOutLsolo = ch->midiOutLsolo;
+
+ writeActions_(ch->index, pch);
+ writePlugins_(ch, pch);
+
+ patch::channels.push_back(pch);
+
+ return patch::channels.size() - 1;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void writePatch(const MidiChannel* ch, bool isProject, int index)
+{
+ patch::channel_t& pch = patch::channels.at(index);
+ pch.midiOut = ch->midiOut;
+ pch.midiOutChan = ch->midiOutChan;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void writePatch(const SampleChannel* ch, bool isProject, int index)
+{
+ patch::channel_t& pch = patch::channels.at(index);
+
+ if (ch->wave != nullptr) {
+ pch.samplePath = ch->wave->getPath();
+ if (isProject)
+ pch.samplePath = gu_basename(ch->wave->getPath()); // make it portable
+ }
+ else
+ pch.samplePath = "";
+
+ pch.mode = static_cast<int>(ch->mode);
+ pch.begin = ch->getBegin();
+ pch.end = ch->getEnd();
+ pch.boost = ch->getBoost();
+ pch.recActive = ch->readActions;
+ pch.pitch = ch->getPitch();
+ pch.inputMonitor = ch->inputMonitor;
+ pch.midiInReadActions = ch->midiInReadActions;
+ pch.midiInPitch = ch->midiInPitch;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void readPatch(Channel* ch, int i)
+{
+ const patch::channel_t& pch = patch::channels.at(i);
+
+ ch->key = pch.key;
+ ch->armed = pch.armed;
+ ch->type = static_cast<ChannelType>(pch.type);
+ ch->name = pch.name;
+ ch->index = pch.index;
+ ch->mute = pch.mute;
+ ch->solo = pch.solo;
+ ch->volume = pch.volume;
+ ch->pan = pch.pan;
+ ch->midiIn = pch.midiIn;
+ ch->midiInKeyPress = pch.midiInKeyPress;
+ ch->midiInKeyRel = pch.midiInKeyRel;
+ ch->midiInKill = pch.midiInKill;
+ ch->midiInVolume = pch.midiInVolume;
+ ch->midiInMute = pch.midiInMute;
+ ch->midiInFilter = pch.midiInFilter;
+ ch->midiInSolo = pch.midiInSolo;
+ ch->midiOutL = pch.midiOutL;
+ ch->midiOutLplaying = pch.midiOutLplaying;
+ ch->midiOutLmute = pch.midiOutLmute;
+ ch->midiOutLsolo = pch.midiOutLsolo;
+
+ readActions_(ch, pch);
+ readPlugins_(ch, pch);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void readPatch(SampleChannel* ch, const string& basePath, int i)
+{
+ const patch::channel_t& pch = patch::channels.at(i);
+
+ ch->mode = static_cast<ChannelMode>(pch.mode);
+ ch->readActions = pch.recActive;
+ ch->recStatus = pch.recActive ? ChannelStatus::PLAY : ChannelStatus::OFF;
+ ch->midiInVeloAsVol = pch.midiInVeloAsVol;
+ ch->midiInReadActions = pch.midiInReadActions;
+ ch->midiInPitch = pch.midiInPitch;
+ ch->inputMonitor = pch.inputMonitor;
+ ch->setBoost(pch.boost);
+
+ Wave* w = nullptr;
+ int res = waveManager::create(basePath + pch.samplePath, &w);
+
+ if (res == G_RES_OK) {
+ ch->pushWave(w);
+ ch->setBegin(pch.begin);
+ ch->setEnd(pch.end);
+ ch->setPitch(pch.pitch);
+ }
+ else {
+ if (res == G_RES_ERR_NO_DATA)
+ ch->status = ChannelStatus::EMPTY;
+ else
+ if (res == G_RES_ERR_IO)
+ ch->status = ChannelStatus::MISSING;
+ ch->sendMidiLstatus(); // FIXME - why sending MIDI lightning if sample status is wrong?
+ }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void readPatch(MidiChannel* ch, int i)
+{
+ const patch::channel_t& pch = patch::channels.at(i);
+
+ ch->midiOut = pch.midiOut;
+ ch->midiOutChan = pch.midiOutChan;
+}
+}}}; // giada::m::channelManager
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef G_CHANNEL_MANAGER_H
+#define G_CHANNEL_MANAGER_H
+
+
+#include <string>
+#include "types.h"
+
+
+class Channel;
+class SampleChannel;
+class MidiChannel;
+
+
+namespace giada {
+namespace m {
+namespace channelManager
+{
+int create(ChannelType type, int bufferSize, bool inputMonitorOn, Channel** out);
+
+int writePatch(const Channel* ch, bool isProject);
+void writePatch(const SampleChannel* ch, bool isProject, int index);
+void writePatch(const MidiChannel* ch, bool isProject, int index);
+
+void readPatch(Channel* ch, int index);
+void readPatch(SampleChannel* ch, const std::string& basePath, int index);
+void readPatch(MidiChannel* ch, int index);
+}}}; // giada::m::channelManager
+
+
+#endif
\ No newline at end of file
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <cassert>
+#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 framesInLoop = 0;
+int framesInBar = 0;
+int framesInBeat = 0;
+int framesInSeq = 0;
+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 = framesInBeat / quantize;
+}
+
+}; // {anonymous}
+
+
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+
+
+void init(int sampleRate, float midiTCfps)
+{
+ midiTCrate = (sampleRate / midiTCfps) * G_MAX_IO_CHANS; // 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 % framesInBar == 0 && currentFrame != 0;
+}
+
+
+bool isOnBeat()
+{
+ /* Skip frame 0: it is intended as 'first beat'. */
+ /* TODO - this is wrong! */
+ return currentFrame % framesInBeat == 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++;
+ if (currentFrame >= framesInLoop) {
+ currentFrame = 0;
+ currentBeat = 0;
+ }
+ else
+ if (isOnBeat())
+ currentBeat++;
+}
+
+
+void rewind()
+{
+ currentFrame = 0;
+ currentBeat = 0;
+ sendMIDIrewind();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void updateFrameBars()
+{
+ /* framesInLoop ... loop length in frames, or samplerate * # frames per
+ * current bpm * beats;
+ * framesInBar .... n. of frames within a bar;
+ * framesInBeat ... n. of frames within a beat;
+ * framesInSeq .... number of frames in the whole sequencer. */
+
+ framesInLoop = (conf::samplerate * (60.0f / bpm)) * beats;
+ framesInBar = framesInLoop / bars;
+ framesInBeat = framesInLoop / beats;
+ framesInSeq = framesInBeat * G_MAX_BEATS;
+
+ updateQuanto();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void sendMIDIsync()
+{
+ /* TODO - only Master (_M) is implemented so far. */
+
+ if (conf::midiSync == MIDI_SYNC_CLOCK_M) {
+ if (currentFrame % (framesInBeat/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
+
+
+/* -------------------------------------------------------------------------- */
+
+
+bool canQuantize()
+{
+ return getQuantize() > 0 && isRunning();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+int getCurrentFrame()
+{
+ return currentFrame;
+}
+
+
+int getFramesInLoop()
+{
+ return framesInLoop;
+}
+
+
+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 getFramesInBar()
+{
+ return framesInBar;
+}
+
+
+int getFramesInBeat()
+{
+ return framesInBeat;
+}
+
+
+int getFramesInSeq()
+{
+ return framesInSeq;
+}
+
+
+}}}; // giada::m::clock::
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef G_CLOCK_H
+#define G_CLOCK_H
+
+
+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 getCurrentBeat();
+int getCurrentFrame();
+int getFramesInBar();
+int getFramesInBeat();
+int getFramesInLoop();
+int getFramesInSeq();
+int getQuantize();
+int getQuanto();
+
+/* incrCurrentFrame
+Increases current frame of a single step (+1). */
+
+void incrCurrentFrame();
+
+/* quantoHasPassed
+Tells whether a quanto unit has passed yet. */
+
+bool quantoHasPassed();
+
+/* quantoHasPassed
+Whether the quantizer value is > 0 and the clock is running. */
+
+bool canQuantize();
+
+/* 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
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <string>
+#include <FL/Fl.H>
+#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 (sampleActionEditorH <= 0) sampleActionEditorH = 40;
+ if (velocityEditorH <= 0) velocityEditorH = 40;
+ if (envelopeEditorH <= 0) envelopeEditorH = 40;
+ 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;
+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 = (Fl::w() / 2) - (G_MIN_GUI_WIDTH / 2);
+int mainWindowY = (Fl::h() / 2) - (G_MIN_GUI_HEIGHT / 2);
+int mainWindowW = G_MIN_GUI_WIDTH;
+int mainWindowH = G_MIN_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 sampleActionEditorH = 40;
+int velocityEditorH = 40;
+int envelopeEditorH = 40;
+
+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::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_SAMPLE_ACTION_EDITOR_H, sampleActionEditorH)) return 0;
+ if (!storager::setInt(jRoot, CONF_KEY_VELOCITY_EDITOR_H, velocityEditorH)) return 0;
+ if (!storager::setInt(jRoot, CONF_KEY_ENVELOPE_EDITOR_H, envelopeEditorH)) 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_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_SAMPLE_ACTION_EDITOR_H, json_integer(sampleActionEditorH));
+ json_object_set_new(jRoot, CONF_KEY_VELOCITY_EDITOR_H, json_integer(velocityEditorH));
+ json_object_set_new(jRoot, CONF_KEY_ENVELOPE_EDITOR_H, json_integer(envelopeEditorH));
+ 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::
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef G_CONF_H
+#define G_CONF_H
+
+
+#include <string>
+
+
+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 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 sampleActionEditorH;
+extern int velocityEditorH;
+extern int envelopeEditorH;
+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
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#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.15.2"
+#define G_VERSION_MAJOR 0
+#define G_VERSION_MINOR 15
+#define G_VERSION_PATCH 2
+
+#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 ------------------------------------------------------------------- */
+#define G_GUI_REFRESH_RATE 1000/24
+#define G_GUI_PLUGIN_RATE 0.05 // refresh rate for plugin GUI
+#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_GUI_ZOOM_FACTOR 2
+
+
+#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_MIN_BPM_STR "20.0"
+#define G_MAX_BPM 999.0f
+#define G_MAX_BPM_STR "999.0"
+#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_MIN_PITCH 0.1f
+#define G_MAX_PITCH 4.0f
+#define G_MAX_GRID_VAL 64
+#define G_MIN_BUF_SIZE 8
+#define G_MAX_BUF_SIZE 4096
+#define G_MIN_GUI_WIDTH 816
+#define G_MIN_GUI_HEIGHT 510
+#define G_MAX_IO_CHANS 2
+#define G_MAX_VELOCITY 0x7F
+#define G_MAX_MIDI_CHANS 16
+
+
+
+/* -- 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_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_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_ACTION_SIZE 8192 // frames
+#define G_DEFAULT_ZOOM_RATIO 128
+
+
+
+/* -- 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_VOLUME 0x20 // 0010 0000
+#define G_ACTION_MIDI 0x40 // 0100 0000
+
+#define G_ACTION_KEYS 0x03 // 0000 0011 any key
+
+#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
+
+
+
+/* -- 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[G_MAX_MIDI_CHANS] = {
+ 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_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_VELO_AS_VOL "midi_in_velo_as_vol"
+#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_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_SAMPLE_ACTION_EDITOR_H "sample_action_editor_h"
+#define CONF_KEY_VELOCITY_EDITOR_H "velocity_editor_h"
+#define CONF_KEY_ENVELOPE_EDITOR_H "envelope_editor_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
--- /dev/null
+/* ---------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * ------------------------------------------------------------------ */
+
+#include "graphics.h"
+
+const char* giada_logo_xpm[] = {
+"245 86 12 1",
+" c #191919",
+". c #303030",
+"+ c #404040",
+"@ c #454545",
+"# c #5A5A5A",
+"$ c #686868",
+"% c #818181",
+"& c #9C9C9C",
+"* c #B8B8B8",
+"= c #CDCDCD",
+"- c #DDDDDD",
+"; c #FEFEFE",
+" ...+++@@@@++... ",
+" .+@@@@@@@@@@@@@@@@@@@@. ",
+" .+@@@@@@@@@@@@@@@@@@@@@@@@@@+. ",
+" .+@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@. ",
+" .+@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@. ",
+" @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@. ",
+" +@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@. ",
+" .@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@+ ",
+" .@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@. ",
+" @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@. ",
+" @@@@@@@@@@@@@@@@@@++$*--;;;;;;;;-=&@+@@@@@@@@@@@@@@@@@@+ ",
+" .@@@@@@@@@@@@@@@@+#&=;;;;;;;;;;;;;;;;;-*%++@@@@@@@@@@@@@@@+ ",
+" .@@@@@@@@@@@@@@@@$=;;;;;;;;;;;;;;;;;;;;;;;;&#+@@@@@@@@@@@@@@@ ",
+" .@@@@@@@@@@@@@@@$;;;;;;;;;;;;;;;;;;;;;;;;;;;;;*@@@@@@@@@@@@@@@@ ",
+" @@@@@@@@@@@@@@#-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;*+@@@@@@@@@@@@@+ ",
+" @@@@@@@@@@@@@+*;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;$+@@@@@@@@@@@@+ ",
+" @@@@@@@@@@@@@#-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;&@@@@@@@@@@@@@. ",
+" .@@@@@@@@@@@@$;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-+@@@@@@@@@@@@ ",
+" .@@@@@@@@@@@+%;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-@@@@@@@@@@@@@ ",
+" @@@@@@@@@@@+&;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;#@@@@@@@@@@@. ",
+" +@@@@@@@@@@@%;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;@@@@@@@@@@@@ ",
+" @@@@@@@@@@@$;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-+@@@@@@@@@@+ ",
+" @@@@@@@@@@@#;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;=@@@@@@@@@@@. ",
+" .@@@@@@@@@@+-;;;;;;;;;;;;;;;;;;;;;=*&%%%%&*-;;;;;;;;;;;;;;;;;;;;;%+@@@@@@@@@@ ",
+" +@@@@@@@@@@*;;;;;;;;;;;;;;;;;;;-%++@@@@@@@++@*-;;;;;;;;;;;;;;;;;;;#@@@@@@@@@@. ",
+" .@@@@@@@@@@#;;;;;;;;;;;;;;;;;;=@@@@@@@@@@@@@@@@@$;;;;;;;;;;;;;;;;;;=+@@@@@@@@@@ ",
+" +@@@@@@@@@@-;;;;;;;;;;;;;;;;-$+@@@@@@@@@@@@@@@@@@@&;;;;;;;;;;;;;;;;;%@@@@@@@@@@. %*------=*%. $%%%%%%% $%%%%%%%%. #%%%%%%%%%%%%%%$@ %%%%%%%%% ",
+" @@@@@@@@@@%;;;;;;;;;;;;;;;;*@@@@@@@@@@@@@@@@@@@@@@+$;;;;;;;;;;;;;;;;-+@@@@@@@@@+ #=;;;;;;;;;;;;;=$ .;;;;;;;;& &;;;;;;;;;; ;;;;;;;;;;;;;;;;;;-&# -;;;;;;;;;= ",
+" .@@@@@@@@@@;;;;;;;;;;;;;;;;*+@@@@@@@@@@@@@@@@@@@@@@@@@;;;;;;;;;;;;;;;;%@@@@@@@@@@ .=;;;;;;;;;;;;;;;;;;# +;;;;;;;;* ;;;;;;;;;;;& .;;;;;;;;;;;;;;;;;;;;;;@ .;;;;;;;;;;;. ",
+" +@@@@@@@@@%;;;;;;;;;;;;;;;*@@@@@@@@@@@@@@@@@@@@@@@@@@@@;;;;;;;;;;;;;;;;@@@@@@@@@@. @;;;;;;;;;;;;;;;;;;;;;* +;;;;;;;;* +;;;;;;;;;;;; .;;;;;;;;;;;;;;;;;;;;;;;* &;;;;;;;;;;;* ",
+" @@@@@@@@@+-;;;;;;;;;;;;;;*+@@@@@@@@@@@@@@@@@@@@@@@@@@@@#;;;;;;;;;;;;;;;$@@@@@@@@@+ @;;;;;;;;;;;;;;;;;;;;;;;= +;;;;;;;;* *;;;;;;;;;;;;. .;;;;;;;;;;;;;;;;;;;;;;;;= ;;;;;;;;;;;;; ",
+" .@@@@@@@@@#;;;;;;;;;;;;;;-@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%;;;;;;;;;;;;;;=+@@@@@@@@@ .;;;;;;;;;;;;;;;;;;;;;;;;;& +;;;;;;;;* .;;;;;;;;;;;;;& .;;;;;;;;;;;;;;;;;;;;;;;;;* #;;;;;;;;;;;;;+ ",
+" +@@@@@@@@@&;;;;;;;;;;;;;;#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@+*;;;;;;;;;;;;;;#@@@@@@@@@ *;;;;;;;;;;;;;;;;;;;;;;;;;;# +;;;;;;;;* $;;;;;;;;;;;;;; .;;;;;;;;;;;;;;;;;;;;;;;;;;# *;;;;;;;;;;;;;= ",
+" @@@@@@@@@+-;;;;;;;;;;;;;*@@@@@@@@@@@@+#*;;;-&@@@@@@@@@@@@@@;;;;;;;;;;;;;;%@@@@@@@@@. +;;;;;;;;;;-# #-;;;;;;;;;;= +;;;;;;;;* =;;;;;;;;;;;;;;# .;;;;;;;;-&&&&*-;;;;;;;;;;;; ;;;;;;;;;;;;;;;. ",
+" @@@@@@@@@+;;;;;;;;;;;;;;@@@@@@@@@@@+%-;;;;;;;;*+@@@@@@@@@@+&;;;;;;;;;;;;;*+@@@@@@@@+ &;;;;;;;;;% %;;;;;;;;;; +;;;;;;;;* .;;;;;;;;;;;;;;;* .;;;;;;;;= #;;;;;;;;;;# %;;;;;;;;;;;;;;;$ ",
+".@@@@@@@@@%;;;;;;;;;;;;;=@@@@@@@@@@+&;;;;;;;;;;;;#@@@@@@@@@@#;;;;;;;;;;;;;;+@@@@@@@@@ =;;;;;;;;= =;;;;;;;;;@ +;;;;;;;;* &;;;;;;;%;;;;;;;; .;;;;;;;;= ;;;;;;;;;& -;;;;;;;%;;;;;;;= ",
+".@@@@@@@@@=;;;;;;;;;;;;;$@@@@@@@@@+*;;;;;;;;;;;;;;#@@@@@@@@@@-;;;;;;;;;;;;;#@@@@@@@@@ ;;;;;;;;;@ -;;;;;;;;. +;;;;;;;;* -;;;;;;;+*;;;;;;;% .;;;;;;;;= %;;;;;;;;- ;;;;;;;; ;;;;;;;;. ",
+".@@@@@@@@@-;;;;;;;;;;;;;+@@@@@@@@@%;;;;;;;;;;;;;;;;+@@@@@@@@@%;;;;;;;;;;;;;%@@@@@@@@@ ;;;;;;;;; +;;;;;;;;* .;;;;;;;; #;;;;;;;= .;;;;;;;;= ;;;;;;;;; &;;;;;;;& &;;;;;;;& ",
+"+@@@@@@@@@;;;;;;;;;;;;;*+@@@@@@@@@;;;;;;;;;;;;;;;;;&@@@@@@@@@@;;;;;;;;;;;;;&@@@@@@@@@ .;;;;;;;;- +;;;;;;;;* *;;;;;;;% ;;;;;;;; .;;;;;;;;= -;;;;;;;; ;;;;;;;;. @;;;;;;;- ",
+"+@@@@@@@@@;;;;;;;;;;;;;&@@@@@@@@@%;;;;;;;;;;;;;;;;;-+@@@@@@@@+-;;;;;;;;;;;;*@@@@@@@@@. .;;;;;;;;- +;;;;;;;;* ;;;;;;;;. *;;;;;;;& .;;;;;;;;= =;;;;;;;;. .;;;;;;;; ;;;;;;;;. ",
+"+@@@@@@@@@;;;;;;;;;;;;;%@@@@@@@@+-;;;;;;;;;;;;;;;;;;$@@@@@@@@@-;;;;;;;;;;;;=@@@@@@@@@. .;;;;;;;;- .%%%%%%%%%%%%$ +;;;;;;;;* +;;;;;;;- .;;;;;;;; .;;;;;;;;= =;;;;;;;;. &;;;;;;;* %;;;;;;;* ",
+"@@@@@@@@@@;;;;;;;;;;;;;$@@@@@@@@@;;;;;;;;;;;;;;;;;;;&@@@@@@@@@=;;;;;;;;;;;;=+@@@@@@@@. +;;;;;;;;- -;;;;;;;;;;;;;% +;;;;;;;;* *;;;;;;;& ;;;;;;;;. .;;;;;;;;= *;;;;;;;;. ;;;;;;;;. .;;;;;;;; ",
+"@@@@@@@@@@;;;;;;;;;;;;;$@@@@@@@@#;;;;;;;;;;;;;;;;;;;*@@@@@@@@@=;;;;;;;;;;;;=+@@@@@@@@. +;;;;;;;;- -;;;;;;;;;;;;;& +;;;;;;;;* .;;;;;;;; *;;;;;;;& .;;;;;;;;= *;;;;;;;;+ #;;;;;;;- ;;;;;;;;+ ",
+"@@@@@@@@@@;;;;;;;;;;;;;$@@@@@@@@#;;;;;;;;;;;;;;;;;;;*@@@@@@@@@#$$&*-;;;;;;;=+@@@@@@@@. +;;;;;;;;- -;;;;;;;;;;;;;& +;;;;;;;;* $;;;;;;;= .;;;;;;;; .;;;;;;;;= *;;;;;;;;+ *;;;;;;;& %;;;;;;;* ",
+"@@@@@@@@@@;;;;;;;;;;;;;$@@@@@@@@@;;;;;;;;;;;;;;;;;;;&@@@@@@@@@@@@@@+@=;;;;;=@@@@@@@@@. .;;;;;;;;- -;;;;;;;;;;;;;& +;;;;;;;;* =;;;;;;;% -;;;;;;;# .;;;;;;;;= *;;;;;;;;. ;;;;;;;;+ ;;;;;;;;. ",
+"+@@@@@@@@@;;;;;;;;;;;;;%@@@@@@@@+-;;;;;;;;;;;;;;;;;;$@@@@@@@@@@@@@@@@@#-;;;=@@@@@@@@@. .;;;;;;;;- -;;;;;;;;;;;;;& +;;;;;;;;* .;;;;;;;;. &;;;;;;;* .;;;;;;;;= =;;;;;;;;. %;;;;;;;- =;;;;;;;# ",
+"+@@@@@@@@@;;;;;;;;;;;;;&@@@@@@@@@%;;;;;;;;;;;;;;;;;;+@@@@@+++++++@@@@@@@&;;*@@@@@@@@@. .;;;;;;;;- @%%%%*;;;;;;;;& +;;;;;;;;* &;;;;;;;=.......#;;;;;;;; .;;;;;;;;= =;;;;;;;;. -;;;;;;;%.......&;;;;;;;= ",
+"+@@@@@@@@@;;;;;;;;;;;;;*+@@@@@@@@@;;;;;;;;;;;;;;;;;&@@@+#%*-;;-=&$@@@@@@@%;&@@@@@@@@@ ;;;;;;;;; %;;;;;;;;% +;;;;;;;;* -;;;;;;;;;;;;;;;;;;;;;;;;% .;;;;;;;;= -;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;. ",
+".@@@@@@@@@-;;;;;;;;;;;;;+@@@@@@@@@%;;;;;;;;;;;;;;;;+@@@*;;;;;;;;;;;%@@@@@@&%@@@@@@@@@ ;;;;;;;;; &;;;;;;;;% +;;;;;;;;* ;;;;;;;;;;;;;;;;;;;;;;;;;= .;;;;;;;;= ;;;;;;;;; %;;;;;;;;;;;;;;;;;;;;;;;;;& ",
+".@@@@@@@@@=;;;;;;;;;;;;;$@@@@@@@@@+*;;;;;;;;;;;;;;#@+%;;;;;;;;;;;;;;-@@@@@@@@@@@@@@@@ -;;;;;;;;# =;;;;;;;;@ +;;;;;;;;* *;;;;;;;;;;;;;;;;;;;;;;;;;; .;;;;;;;;= $;;;;;;;;- ;;;;;;;;;;;;;;;;;;;;;;;;;;- ",
+".@@@@@@@@@%;;;;;;;;;;;;;=@@@@@@@@@@+*;;;;;;;;;;;;#@+*;;;;;;;;;;;;;;;;;$+@@@@@@@@@@@@@ =;;;;;;;;= @;;;;;;;;; +;;;;;;;;* ;;;;;;;;;;;;;;;;;;;;;;;;;;;& .;;;;;;;;= -;;;;;;;;& .;;;;;;;;;;;;;;;;;;;;;;;;;;;. ",
+" @@@@@@@@@+;;;;;;;;;;;;;;@@@@@@@@@@@+%-;;;;;;;;*@@+*;;;;;;;;;;;;;;;;;;;$@@@@@@@@@@@@+ &;;;;;;;;;% +;;;;;;;;;= +;;;;;;;;* +;;;;;;;;;;;;;;;;;;;;;;;;;;;; .;;;;;;;;= +-;;;;;;;;;$ &;;;;;;;;;;;;;;;;;;;;;;;;;;;* ",
+" @@@@@@@@@+-;;;;;;;;;;;;;*@@@@@@@@@@@@+$=;;;-&@@@@&;;;;;;;;;;;;;;;;;;;;;@@@@@@@@@@@@. .;;;;;;;;;;-@ .*;;;;;;;;;;% +;;;;;;;;* *;;;;;;;;;;;;;;;;;;;;;;;;;;;;. .;;;;;;;;-&&&&&*;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ",
+" +@@@@@@@@@*;;;;;;;;;;;;;;#@@@@@@@@@@@@@@@@@+@@@@@;;;;;;;;;;;;;;;;;;;;;;-@@@@@@@@@@@ &;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;* .;;;;;;;;;;;;;;;;;;;;;;;;;;;;;& .;;;;;;;;;;;;;;;;;;;;;;;;;;$ #;;;;;;;;;;;;;;;;;;;;;;;;;;;;;. ",
+" .@@@@@@@@@#;;;;;;;;;;;;;;-@@@@@@@@@@@@@@@@@@@@@@-;;;;;;;;;;;;;;;;;;;;;;;$@@@@@@@@@@ .;;;;;;;;;;;;;;;;;;;;;;;;;@ +;;;;;;;;* $;;;;;;;;% ;;;;;;;;; .;;;;;;;;;;;;;;;;;;;;;;;;;- *;;;;;;;; .;;;;;;;;* ",
+" @@@@@@@@@+-;;;;;;;;;;;;;;*+@@@@@@@@@@@@@@@@@@@#;;;;;;;;;;;;;;;;;;;;;;;;-+@@@@@@@@+ @;;;;;;;;;;;;;;;;;;;;;;;& +;;;;;;;;* *;;;;;;;; &;;;;;;;;# .;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;= -;;;;;;;;.",
+" +@@@@@@@@@%;;;;;;;;;;;;;;;&@@@@@@@@@@@@@@@@@@+%;;;;;;;;;;;;;;;;;;;;;;;;;#@@@@@@@@. #;;;;;;;;;;;;;;;;;;;;;% +;;;;;;;;* ;;;;;;;;* ;;;;;;;;* .;;;;;;;;;;;;;;;;;;;;;;;- %;;;;;;;;% %;;;;;;;;#",
+" .@@@@@@@@@@;;;;;;;;;;;;;;;;&+@@@@@@@@@@@@@@@@+-;;;;;;;;;;;;;;;;;;;;;;;;;%@@@@@@@@ .-;;;;;;;;;;;;;;;;;;+ +;;;;;;;;* &;;;;;;;;$ =;;;;;;;; .;;;;;;;;;;;;;;;;;;;;;;% -;;;;;;;; ;;;;;;;;*",
+" @@@@@@@@@@%;;;;;;;;;;;;;;;;*@@@@@@@@@@@@@@@@+;;;;;;;;;;;;;;;;;;;;;;;;;;&+@@@@@@+ $=;;;;;;;;;;;;;=$ .;;;;;;;;* =;;;;;;;; $;;;;;;;;# .;;;;;;;;;;;;;;;;;;;=%. ;;;;;;;;& *;;;;;;;;",
+" +@@@@@@@@@@-;;;;;;;;;;;;;;;;-#+@@@@@@@@@@@@@#;;;;;;;;;;;;;;;;;;;;;;;;;;*+@@@@@@. .%=---;---=%. %&&&&&&&. +&&&&&&&. $&&&&&&% #&&&&&&&&&&&&&&%$+ $&&&&&&% %&&&&&&#",
+" .@@@@@@@@@@#;;;;;;;;;;;;;;;;;;*@@@@@@@@@@@@@#;;;;;;;;;;;;;;;;;;;;;;;;;;=+@@@@@@ ",
+" +@@@@@@@@@@*;;;;;;;;;;;;;;;;;;;=%++@@@@@@@+@;;;;;;;;;;;;;;;;;;;;;;;;;;*+@@@@@. ",
+" .@@@@@@@@@@@-;;;;;;;;;;;;;;;;;;;;;=&%%$%%&*-;;;;;;;;;;;;;;;;;;;;;;;;;;&+@@@@@ ",
+" +@@@@@@@@@@#;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;%@@@@@. ",
+" @@@@@@@@@@@$;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;#@@@@+ ",
+" +@@@@@@@@@@@%;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-+@@@@ ",
+" @@@@@@@@@@@+&;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;$@@@@. ",
+" .@@@@@@@@@@@+%;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-@@@@@ ",
+" .@@@@@@@@@@@@$;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;@@@@@ ",
+" +@@@@@@@@@@@@#-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;$@@@@. ",
+" @@@@@@@@@@@@@@*;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;%+@@@+ ",
+" @@@@@@@@@@@@@+#-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-#@@@@+ ",
+" .@@@@@@@@@@@@@@@%;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;%@@@@@@ ",
+" .@@@@@@@@@@@@@@@@$-;;;;;;;;;;;;;;;;;;;;;;;;*$%*-;;;-*$@@@@@@@ ",
+" .@@@@@@@@@@@@@@@@+#&-;;;;;;;;;;;;;;;;;;=%@+@@+++.+++@@@@@@+ ",
+" @@@@@@@@@@@@@@@@@@++%*--;;;;;;;;--&#++@@@@@@@@@@@@@@@@@+ ",
+" @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@. ",
+" .@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@. ",
+" .@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@+ ",
+" +@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@. ",
+" @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@. ",
+" .+@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@. ",
+" .+@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@. ",
+" .+@@@@@@@@@@@@@@@@@@@@@@@@@@+. ",
+" .+@@@@@@@@@@@@@@@@@@@@. ",
+" ...+++@@@@++... "};
+
+
+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",
+" ",
+" ",
+" ",
+" ",
+" ",
+" +# ",
+" .#%& ",
+" @%&&& ",
+" +$&&&&& ",
+" .#&&&&&&& ",
+" @%&&&&&&&& ",
+" #&&&&&&&&&& ",
+" .@%&&&&&&&& ",
+" .#&&&&&&& ",
+" +$&&&&& ",
+" @%&&& ",
+" .#&& ",
+" +$ ",
+" ",
+" ",
+" ",
+" ",
+" "};
+
+
+const char* giada_icon[] = {
+"65 65 8 1",
+" c #444643",
+". c #565755",
+"+ c #6C6E6B",
+"@ c #898B88",
+"# c #A3A5A2",
+"$ c #BEC0BD",
+"% c #D7DAD6",
+"& c #FCFEFB",
+" ",
+" &&&&&&&&&&&&&&&&&&&&%$@ .@$%&&&&&&&&&&&&&&&&&&&& ",
+" &&&&&&&&&&&&&&&&&%$@. .#%&&&&&&&&&&&&&&&&&& ",
+" &&&&&&&&&&&&&&&&#+ .@$&&&&&&&&&&&&&&&& ",
+" &&&&&&&&&&&&&&$+ .@%&&&&&&&&&&&&&& ",
+" &&&&&&&&&&&&&@ .#&&&&&&&&&&&&& ",
+" &&&&&&&&&&&%. @&&&&&&&&&&&& ",
+" &&&&&&&&&&$ ...+@@#@@+.. +%&&&&&&&&&& ",
+" &&&&&&&&&# .+#$&&&&&&&&&&%$@+. .%&&&&&&&&& ",
+" &&&&&&&&# +#%&&&&&&&&&&&&&&&&&$@. .%&&&&&&&& ",
+" &&&&&&&# .#%&&&&&&&&&&&&&&&&&&&&&%@ .%&&&&&&& ",
+" &&&&&&$ @%&&&&&&&&&&&&&&&&&&&&&&&&&%+ .&&&&&&& ",
+" &&&&&% $&&&&&&&&&&&&&&&&&&&&&&&&&&&&&@ +&&&&&& ",
+" &&&&&. .%&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&# @&&&&& ",
+" &&&&@ +%&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&# $&&&& ",
+" &&&$ .%&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&# +&&&& ",
+" &&&+ %&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&# #&&& ",
+" &&# $&&&&&&&&&&&&&&&&%$###$%&&&&&&&&&&&&&&&&@ +%&& ",
+" &%+ @&&&&&&&&&&&&&&%@.. ..+#&&&&&&&&&&&&&&%. #&& ",
+" &$ .%&&&&&&&&&&&&%+ #&&&&&&&&&&&&&# +&& ",
+" &@ $&&&&&&&&&&&&# +%&&&&&&&&&&&%+ $& ",
+" %. +%&&&&&&&&&&&@ .$&&&&&&&&&&&# @& ",
+" $ #&&&&&&&&&&&@ .$&&&&&&&&&&&+ & ",
+" @ .%&&&&&&&&&&# .%&&&&&&&&&&@ $ ",
+" . +&&&&&&&&&&% +&&&&&&&&&&%. @ ",
+" #&&&&&&&&&&+ @%&&%$+ #&&&&&&&&&&. . ",
+" .%&&&&&&&&&$ +%&&&&&&&# .&&&&&&&&&&@ ",
+" .&&&&&&&&&&+ +&&&&&&&&&&$ $&&&&&&&&&$ ",
+" +&&&&&&&&&& .%&&&&&&&&&&&# @&&&&&&&&&% ",
+" @&&&&&&&&&$ @&&&&&&&&&&&&&. +&&&&&&&&&& ",
+" @&&&&&&&&&# %&&&&&&&&&&&&&# .%&&&&&&&&&. ",
+" #&&&&&&&&&@ .&&&&&&&&&&&&&&$ .%&&&&&&&&&. ",
+" #&&&&&&&&&@ .&&&&&&&&&&&&&&% .#$%&&&&&&&+ ",
+" #&&&&&&&&&@ .&&&&&&&&&&&&&&$ @%&&&&. ",
+" @&&&&&&&&&# %&&&&&&&&&&&&&# @%&&. ",
+" @&&&&&&&&&$ #&&&&&&&&&&&&&. +@@@@+. .$& ",
+" +&&&&&&&&&& .%&&&&&&&&&&&# +$%&&&&&%#. .$ ",
+" .&&&&&&&&&&+ +&&&&&&&&&&$ $&&&&&&&&&&%@ . ",
+" .%&&&&&&&&&$ +%&&&&&&&#. %&&&&&&&&&&&&&# ",
+" #&&&&&&&&&&+ @%&&&$+ $&&&&&&&&&&&&&&&@ . ",
+" . +&&&&&&&&&&% @&&&&&&&&&&&&&&&&%. @ ",
+" @ .%&&&&&&&&&&# %&&&&&&&&&&&&&&&&&@ $ ",
+" $ #&&&&&&&&&&&@ @&&&&&&&&&&&&&&&&&&%. & ",
+" %. +%&&&&&&&&&&&@ $&&&&&&&&&&&&&&&&&&&. @& ",
+" &@ $&&&&&&&&&&&&# %&&&&&&&&&&&&&&&&&&&+ $& ",
+" &$ .%&&&&&&&&&&&&%+ %&&&&&&&&&&&&&&&&&&&@ +&& ",
+" &%+ @&&&&&&&&&&&&&&%@.. .%&&&&&&&&&&&&&&&&&&&@ #&& ",
+" &&# $&&&&&&&&&&&&&&&&%$###$%&&&&&&&&&&&&&&&&&&&&+ +%&& ",
+" &&&+ .%&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&. #&&& ",
+" &&&$ .%&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&# +&&&& ",
+" &&&&@ +%&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&+ $&&&& ",
+" &&&&&. .%&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&# @&&&&& ",
+" &&&&&% .$&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&%. +&&&&&& ",
+" &&&&&&$ @%&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&%. .&&&&&&& ",
+" &&&&&&&# .$%&&&&&&&&&&&&&&&&&&&&&&&&&&&&%# .%&&&&&&& ",
+" &&&&&&&&# +#%&&&&&&&&&&&&&&&&&$@#$%%$$@+ .%&&&&&&&& ",
+" &&&&&&&&&# .+#%&&&&&&&&&&&$@+. .... .%&&&&&&&&& ",
+" &&&&&&&&&&$ ..+@@###@+... +%&&&&&&&&&& ",
+" &&&&&&&&&&&%. @&&&&&&&&&&&& ",
+" &&&&&&&&&&&&&@ .#&&&&&&&&&&&&& ",
+" &&&&&&&&&&&&&&$+ .@%&&&&&&&&&&&&&& ",
+" &&&&&&&&&&&&&&&&#+ .@$&&&&&&&&&&&&&&&& ",
+" &&&&&&&&&&&&&&&&&%$@. .#%&&&&&&&&&&&&&&&&&& ",
+" &&&&&&&&&&&&&&&&&&&&%$@ .@$%&&&&&&&&&&&&&&&&&&&& ",
+" "};
+
+
+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",
+" ",
+" ",
+" ",
+" ",
+" .#%%#. ",
+" +&&&&&&+ ",
+" .&&&&&&&&. ",
+" #&&&&&&&&@ ",
+" %&&&&&&&&% ",
+" %&&&&&&&&% ",
+" #&&&&&&&&$ ",
+" .&&&&&&&&. ",
+" +&&&&&&+ ",
+" .@%%$. ",
+" ",
+" ",
+" ",
+" "};
--- /dev/null
+/* ---------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * ------------------------------------------------------------------ */
+
+#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
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <ctime>
+#ifdef __APPLE__
+ #include <pwd.h>
+#endif
+#if defined(__linux__) && defined(WITH_VST)
+ #include <X11/Xlib.h> // For XInitThreads
+#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::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::getFramesInLoop(), 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)
+{
+ /* This is of paramount importance on Linux with VST enabled, otherwise many
+ plug-ins go nuts and crash hard. It seems that some plug-ins or our Juce-based
+ PluginHost use Xlib concurrently. */
+
+#if defined(__linux__) && defined(WITH_VST)
+ XInitThreads();
+#endif
+
+ G_MainWin = new gdMainWindow(G_MIN_GUI_WIDTH, G_MIN_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");
+
+ recorder::clearAll();
+ gu_log("[init] Recorder cleaned up\n");
+
+#ifdef WITH_VST
+
+ pluginHost::freeAllStacks(&mixer::channels, &mixer::mutex);
+ pluginHost::close();
+ gu_log("[init] PluginHost cleaned up\n");
+
+#endif
+
+ if (kernelAudio::getStatus()) {
+ kernelAudio::closeDevice();
+ gu_log("[init] KernelAudio closed\n");
+ mixer::close();
+ gu_log("[init] Mixer closed\n");
+ }
+
+ gu_log("[init] Giada " G_VERSION_STR " closed\n\n");
+ gu_logClose();
+}
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#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
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#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<jack_client_t*>(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; i<numDevs; i++)
+ gu_log(" %d) %s\n", i, getDeviceName(i).c_str());
+ }
+
+ RtAudio::StreamParameters outParams;
+ RtAudio::StreamParameters inParams;
+
+ outParams.deviceId = conf::soundDeviceOut == G_DEFAULT_SOUNDDEV_OUT ? getDefaultOut() : conf::soundDeviceOut;
+ outParams.nChannels = G_MAX_IO_CHANS;
+ outParams.firstChannel = conf::channelsOut * G_MAX_IO_CHANS; // chan 0=0, 1=2, 2=4, ...
+
+ /* inDevice can be disabled. */
+
+ if (conf::soundDeviceIn != -1) {
+ inParams.deviceId = conf::soundDeviceIn;
+ inParams.nChannels = G_MAX_IO_CHANS;
+ inParams.firstChannel = conf::channelsIn * G_MAX_IO_CHANS; // chan 0=0, 1=2, 2=4, ...
+ inputEnabled = true;
+ }
+ else
+ inputEnabled = false;
+
+ RtAudio::StreamOptions options;
+ options.streamName = G_APP_NAME;
+ options.numberOfBuffers = 4;
+
+ realBufsize = conf::buffersize;
+
+#if defined(__linux__) || defined(__APPLE__)
+
+ if (api == G_SYS_API_JACK) {
+ conf::samplerate = getFreq(conf::soundDeviceOut, 0);
+ gu_log("[KA] JACK in use, freq = %d\n", conf::samplerate);
+ }
+
+#endif
+
+ try {
+ rtSystem->openStream(
+ &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<RtAudio::DeviceInfo>(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<RtAudio::DeviceInfo>(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<RtAudio::DeviceInfo>(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<RtAudio::DeviceInfo>(rtSystem->getDeviceInfo(dev)).probed;
+ }
+ catch (RtAudioError &e) {
+ return 0;
+ }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+unsigned getDuplexChans(unsigned dev)
+{
+ try {
+ return static_cast<RtAudio::DeviceInfo>(rtSystem->getDeviceInfo(dev)).duplexChannels;
+ }
+ catch (RtAudioError &e) {
+ return 0;
+ }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+bool isDefaultIn(unsigned dev)
+{
+ try {
+ return static_cast<RtAudio::DeviceInfo>(rtSystem->getDeviceInfo(dev)).isDefaultInput;
+ }
+ catch (RtAudioError &e) {
+ return 0;
+ }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+bool isDefaultOut(unsigned dev)
+{
+ try {
+ return static_cast<RtAudio::DeviceInfo>(rtSystem->getDeviceInfo(dev)).isDefaultOutput;
+ }
+ catch (RtAudioError &e) {
+ return 0;
+ }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+int getTotalFreqs(unsigned dev)
+{
+ try {
+ return static_cast<RtAudio::DeviceInfo>(rtSystem->getDeviceInfo(dev)).sampleRates.size();
+ }
+ catch (RtAudioError &e) {
+ return 0;
+ }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+int getFreq(unsigned dev, int i)
+{
+ try {
+ return static_cast<RtAudio::DeviceInfo>(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<numDevs; i++)
+ if (name == getDeviceName(i))
+ return i;
+ return -1;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+bool hasAPI(int API)
+{
+ vector<RtAudio::Api> APIs;
+ RtAudio::getCompiledApi(APIs);
+ for (unsigned i=0; i<APIs.size(); i++)
+ if (APIs.at(i) == API)
+ return true;
+ return false;
+}
+
+
+int getAPI() { return api; }
+
+
+/* -------------------------------------------------------------------------- */
+
+
+#ifdef __linux__
+
+
+const JackState &jackTransportQuery()
+{
+ if (api != G_SYS_API_JACK)
+ return jackState;
+ jack_position_t position;
+ jack_transport_state_t ts = jack_transport_query(jackGetHandle(), &position);
+ jackState.running = ts != JackTransportStopped;
+ jackState.bpm = position.beats_per_minute;
+ jackState.frame = position.frame;
+ return jackState;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void jackStart()
+{
+ if (api == G_SYS_API_JACK)
+ jack_transport_start(jackGetHandle());
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void jackSetPosition(uint32_t frame)
+{
+ if (api != G_SYS_API_JACK)
+ return;
+ jack_position_t position;
+ jack_transport_query(jackGetHandle(), &position);
+ position.frame = frame;
+ jack_transport_reposition(jackGetHandle(), &position);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void jackSetBpm(double bpm)
+{
+ if (api != G_SYS_API_JACK)
+ return;
+ jack_position_t position;
+ jack_transport_query(jackGetHandle(), &position);
+ position.valid = jack_position_bits_t::JackPositionBBT;
+ position.bar = 0; // no such info from Giada
+ position.beat = 0; // no such info from Giada
+ position.tick = 0; // no such info from Giada
+ position.beats_per_minute = bpm;
+ jack_transport_reposition(jackGetHandle(), &position);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void jackStop()
+{
+ if (api == G_SYS_API_JACK)
+ jack_transport_stop(jackGetHandle());
+}
+
+#endif // #ifdef __linux__
+
+}}}; // giada::m::kernelAudio
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef G_KERNELAUDIO_H
+#define G_KERNELAUDIO_H
+
+
+#include <string>
+#ifdef __linux__
+ #include <jack/jack.h>
+ #include <jack/intclient.h>
+ #include <jack/transport.h>
+#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);
+int getAPI();
+
+#ifdef __linux__
+
+void jackStart();
+void jackStop();
+void jackSetPosition(uint32_t frame);
+void jackSetBpm(double bpm);
+const JackState &jackTransportQuery();
+
+#endif
+}}}; // giada::m::kernelAudio::
+
+#endif
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include "const.h"
+#ifdef G_OS_MAC
+ #include <RtMidi.h>
+#else
+ #include <rtmidi/RtMidi.h>
+#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<unsigned char>* 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; i<msg->size(); 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; i<midimap::initCommands.size(); i++) {
+ midimap::message_t msg = midimap::initCommands.at(i);
+ if (msg.value != 0x0 && msg.channel != -1) {
+ gu_log("[KM] MIDI send (init) - Channel %x - Event 0x%X\n", msg.channel, msg.value);
+ send(msg.value | MIDI_CHANS[msg.channel]);
+ }
+ }
+}
+
+}; // {anonymous}
+
+
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+
+
+void setApi(int _api)
+{
+ api = _api;
+ gu_log("[KM] using system 0x%x\n", api);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+int openOutDevice(int port)
+{
+ try {
+ midiOut = new RtMidiOut((RtMidi::Api) api, "Giada MIDI Output");
+ status = true;
+ }
+ catch (RtMidiError &error) {
+ gu_log("[KM] MIDI out device error: %s\n", error.getMessage().c_str());
+ status = false;
+ return 0;
+ }
+
+ /* print output ports */
+
+ numOutPorts = midiOut->getPortCount();
+ gu_log("[KM] %d output MIDI ports found\n", numOutPorts);
+ for (unsigned i=0; i<numOutPorts; i++)
+ gu_log(" %d) %s\n", i, getOutPortName(i).c_str());
+
+ /* try to open a port, if enabled */
+
+ if (port != -1 && numOutPorts > 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<numInPorts; i++)
+ gu_log(" %d) %s\n", i, getInPortName(i).c_str());
+
+ /* try to open a port, if enabled */
+
+ if (port != -1 && numInPorts > 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<RtMidi::Api> APIs;
+ RtMidi::getCompiledApi(APIs);
+ for (unsigned i=0; i<APIs.size(); i++)
+ if (APIs.at(i) == API)
+ return true;
+ return false;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+string getOutPortName(unsigned p)
+{
+ try { return midiOut->getPortName(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<unsigned char> 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<unsigned char> 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);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void sendMidiLightning(uint32_t learn, const midimap::message_t& msg)
+{
+ gu_log("[KM] 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);
+ send(out);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+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::
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef G_KERNELMIDI_H
+#define G_KERNELMIDI_H
+
+
+#include <cstdint>
+#include <string>
+#include "midiMapConf.h"
+
+
+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
+Sends a MIDI message 's' as uint32_t or as separate bytes. */
+
+void send(uint32_t s);
+void send(int b1, int b2=-1, int b3=-1);
+
+/* sendMidiLightning
+Sends a MIDI lightning message defined by 'msg'. */
+
+void sendMidiLightning(uint32_t learn, const midimap::message_t& msg);
+
+/* 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
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include "../utils/log.h"
+#include "midiChannelProc.h"
+#include "channelManager.h"
+#include "channel.h"
+#include "patch.h"
+#include "const.h"
+#include "clock.h"
+#include "conf.h"
+#include "mixer.h"
+#include "pluginHost.h"
+#include "kernelMidi.h"
+#include "midiChannel.h"
+
+
+using std::string;
+using namespace giada;
+using namespace giada::m;
+
+
+MidiChannel::MidiChannel(int bufferSize)
+ : Channel (ChannelType::MIDI, ChannelStatus::OFF, bufferSize),
+ midiOut (false),
+ midiOutChan(MIDI_CHANS[0])
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void MidiChannel::copy(const Channel* src_, pthread_mutex_t* pluginMutex)
+{
+ Channel::copy(src_, pluginMutex);
+ const MidiChannel* src = static_cast<const MidiChannel*>(src_);
+ midiOut = src->midiOut;
+ midiOutChan = src->midiOutChan;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void MidiChannel::parseEvents(mixer::FrameEvents fe)
+{
+ midiChannelProc::parseEvents(this, fe);
+}
+
+/* -------------------------------------------------------------------------- */
+
+
+void MidiChannel::process(giada::m::AudioBuffer& out,
+ const giada::m::AudioBuffer& in, bool audible, bool running)
+{
+ midiChannelProc::process(this, out, in, audible);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void MidiChannel::stopBySeq(bool chansStopOnSeqHalt)
+{
+ midiChannelProc::stopBySeq(this);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void MidiChannel::start(int frame, bool doQuantize, int velocity)
+{
+ midiChannelProc::start(this);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void MidiChannel::kill(int localFrame)
+{
+ midiChannelProc::kill(this, localFrame);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void MidiChannel::rewindBySeq()
+{
+ midiChannelProc::rewindBySeq(this);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void MidiChannel::setMute(bool value)
+{
+ midiChannelProc::setMute(this, value);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void MidiChannel::readPatch(const string& basePath, int i)
+{
+ Channel::readPatch("", i);
+ channelManager::readPatch(this, i);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void MidiChannel::writePatch(int i, bool isProject)
+{
+ Channel::writePatch(i, isProject);
+ channelManager::writePatch(this, isProject, i);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+#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::empty()
+{
+ hasActions = false;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void MidiChannel::sendMidi(recorder::action* a, int localFrame)
+{
+ if (isPlaying() && !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 (isPlaying() && !mute) {
+ if (midiOut)
+ kernelMidi::send(data | MIDI_CHANS[midiOutChan]);
+#ifdef WITH_VST
+ addVstMidiEvent(data, 0);
+#endif
+ }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+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("[MidiChannel::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
+}
\ No newline at end of file
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#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);
+
+ void copy(const Channel* src, pthread_mutex_t* pluginMutex) override;
+ void parseEvents(giada::m::mixer::FrameEvents fe) override;
+ void process(giada::m::AudioBuffer& out, const giada::m::AudioBuffer& in,
+ bool audible, bool running) override;
+ void start(int frame, bool doQuantize, int velocity) override;
+ void kill(int localFrame) override;
+ void empty() override;
+ void stopBySeq(bool chansStopOnSeqHalt) override;
+ void stop() override {};
+ void rewindBySeq() override;
+ void setMute(bool value) override;
+ void readPatch(const std::string& basePath, int i) override;
+ void writePatch(int i, bool isProject) 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
+
+ bool midiOut; // enable midi output
+ uint8_t midiOutChan; // midi output channel
+};
+
+
+#endif
--- /dev/null
+#include "midiChannel.h"
+#include "pluginHost.h"
+#include "kernelMidi.h"
+#include "const.h"
+#include "midiChannelProc.h"
+
+
+namespace giada {
+namespace m {
+namespace midiChannelProc
+{
+namespace
+{
+void onFirstBeat_(MidiChannel* ch)
+{
+ if (ch->status == ChannelStatus::ENDING) {
+ ch->status = ChannelStatus::OFF;
+ ch->sendMidiLstatus();
+ }
+ else
+ if (ch->status == ChannelStatus::WAIT) {
+ ch->status = ChannelStatus::PLAY;
+ ch->sendMidiLstatus();
+ }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void parseAction_(MidiChannel* ch, const recorder::action* a, int localFrame)
+{
+ if (ch->isPlaying() && !ch->mute) {
+ if (ch->midiOut)
+ kernelMidi::send(a->iValue | MIDI_CHANS[ch->midiOutChan]);
+#ifdef WITH_VST
+ ch->addVstMidiEvent(a->iValue, localFrame);
+#endif
+ }
+}
+
+}; // {anonymous}
+
+
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+
+
+void parseEvents(MidiChannel* ch, mixer::FrameEvents fe)
+{
+ if (fe.onFirstBeat)
+ onFirstBeat_(ch);
+ for (const recorder::action* action : fe.actions)
+ if (action->chan == ch->index && action->type == G_ACTION_MIDI)
+ parseAction_(ch, action, fe.frameLocal);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void process(MidiChannel* ch, giada::m::AudioBuffer& out,
+ const giada::m::AudioBuffer& in, bool audible)
+{
+ #ifdef WITH_VST
+ pluginHost::processStack(ch->buffer, pluginHost::CHANNEL, ch);
+ #endif
+
+ /* Process the plugin stack first, then quit if the channel is muted/soloed.
+ This way there's no risk of cutting midi event pairs such as note-on and
+ note-off while triggering a mute/solo. */
+
+ /* TODO - this is meaningful only if WITH_VST is defined */
+ if (audible)
+ for (int i=0; i<out.countFrames(); i++)
+ for (int j=0; j<out.countChannels(); j++)
+ out[i][j] += ch->buffer[i][j] * ch->volume;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void start(MidiChannel* ch)
+{
+ switch (ch->status) {
+ case ChannelStatus::PLAY:
+ ch->status = ChannelStatus::ENDING;
+ ch->sendMidiLstatus();
+ break;
+
+ case ChannelStatus::ENDING:
+ case ChannelStatus::WAIT:
+ ch->status = ChannelStatus::OFF;
+ ch->sendMidiLstatus();
+ break;
+
+ case ChannelStatus::OFF:
+ ch->status = ChannelStatus::WAIT;
+ ch->sendMidiLstatus();
+ break;
+
+ default: break;
+ }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void kill(MidiChannel* ch, int localFrame)
+{
+ if (ch->isPlaying()) {
+ if (ch->midiOut)
+ kernelMidi::send(MIDI_ALL_NOTES_OFF);
+#ifdef WITH_VST
+ ch->addVstMidiEvent(MIDI_ALL_NOTES_OFF, 0);
+#endif
+ }
+ ch->status = ChannelStatus::OFF;
+ ch->sendMidiLstatus();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void rewindBySeq(MidiChannel* ch)
+{
+ if (ch->midiOut)
+ kernelMidi::send(MIDI_ALL_NOTES_OFF);
+#ifdef WITH_VST
+ ch->addVstMidiEvent(MIDI_ALL_NOTES_OFF, 0);
+#endif
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void setMute(MidiChannel* ch, bool v)
+{
+ ch->mute = v;
+ if (ch->mute) {
+ if (ch->midiOut)
+ kernelMidi::send(MIDI_ALL_NOTES_OFF);
+ #ifdef WITH_VST
+ ch->addVstMidiEvent(MIDI_ALL_NOTES_OFF, 0);
+ #endif
+ }
+ ch->sendMidiLmute();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void stopBySeq(MidiChannel* ch)
+{
+ kill(ch, 0);
+}
+}}};
\ No newline at end of file
--- /dev/null
+#ifndef G_MIDI_CHANNEL_PROC_H
+#define G_MIDI_CHANNEL_PROC_H
+
+
+#include "mixer.h"
+#include "audioBuffer.h"
+
+
+class MidiChannel;
+
+
+namespace giada {
+namespace m {
+namespace midiChannelProc
+{
+/* parseEvents
+Parses events gathered by Mixer::masterPlay(). */
+
+void parseEvents(MidiChannel* ch, mixer::FrameEvents ev);
+
+/**/
+void process(MidiChannel* ch, giada::m::AudioBuffer& out,
+ const giada::m::AudioBuffer& in, bool audible);
+
+/* kill
+Stops a channel abruptly. */
+
+void kill(MidiChannel* ch, int localFrame);
+
+/* start
+Starts a channel. */
+
+void start(MidiChannel* ch);
+
+/* stopBySeq
+Stops a channel when the stop button on main transport is pressed. */
+
+void stopBySeq(MidiChannel* ch);
+
+/* rewind
+Rewinds channel when rewind button on main transport is pressed. */
+
+void rewindBySeq(MidiChannel* ch);
+
+/* mute|unmute
+Mutes/unmutes a channel. */
+
+void setMute(MidiChannel* ch, bool v);
+}}};
+
+
+#endif
\ No newline at end of file
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <vector>
+#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)
+{
+ uint32_t pure = midiEvent.getRawNoVelocity();
+
+ /* 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<Plugin*>* plugins. */
+
+ vector<Plugin*>* plugins = pluginHost::getStack(pluginHost::CHANNEL, ch);
+
+ for (Plugin* plugin : *plugins) {
+ for (unsigned k=0; k<plugin->midiInParams.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)
+{
+ uint32_t pure = midiEvent.getRawNoVelocity();
+
+ 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);
+ c::io::keyPress(ch, false, false, midiEvent.getVelocity());
+ }
+ else if (pure == ch->midiInKeyRel) {
+ gu_log(" >>> keyRel ch=%d (pure=0x%X)\n", ch->index, pure);
+ c::io::keyRelease(ch, false, false);
+ }
+ else if (pure == ch->midiInMute) {
+ gu_log(" >>> mute ch=%d (pure=0x%X)\n", ch->index, pure);
+ c::channel::toggleMute(ch, false);
+ }
+ else if (pure == ch->midiInKill) {
+ gu_log(" >>> kill ch=%d (pure=0x%X)\n", ch->index, pure);
+ c::channel::kill(ch);
+ }
+ else if (pure == ch->midiInArm) {
+ gu_log(" >>> arm ch=%d (pure=0x%X)\n", ch->index, pure);
+ c::channel::toggleArm(ch, false);
+ }
+ else if (pure == ch->midiInSolo) {
+ gu_log(" >>> solo ch=%d (pure=0x%X)\n", ch->index, pure);
+ c::channel::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);
+ c::channel::setVolume(ch, vf, false);
+ }
+ else {
+ SampleChannel* sch = static_cast<SampleChannel*>(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);
+ c::channel::setPitch(sch, vf);
+ }
+ else
+ if (pure == sch->midiInReadActions) {
+ gu_log(" >>> toggle read actions ch=%d (pure=0x%X)\n", sch->index, pure);
+ c::channel::toggleReadingActions(sch, false);
+ }
+ }
+
+#ifdef WITH_VST
+
+ /* Process learned plugins parameters. */
+ processPlugins(ch, midiEvent);
+
+#endif
+
+ /* Redirect full midi message (pure + velocity) to plugins. */
+ ch->receiveMidi(midiEvent.getRaw());
+ }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void processMaster(const MidiEvent& midiEvent)
+{
+ uint32_t pure = midiEvent.getRawNoVelocity();
+
+ 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);
+ c::io::startStopActionRec(false);
+ }
+ else if (pure == conf::midiInInputRec) {
+ gu_log(" >>> inputRec (master) (pure=0x%X)\n", pure);
+ c::io::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.
+ We must also fix the velocity zero issue for those devices that sends NOTE
+ OFF events as NOTE ON + velocity zero. Let's make it a real NOTE OFF event. */
+
+ MidiEvent midiEvent(byte1, byte2, byte3);
+ midiEvent.fixVelocityZero();
+
+ 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, i.e. with
+ velocity value stripped 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.getRawNoVelocity(), cb_data);
+ else {
+ processMaster(midiEvent);
+ processChannels(midiEvent);
+ }
+}
+}}}; // giada::m::midiDispatcher::
+
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef G_MIDI_DISPATCHER_H
+#define G_MIDI_DISPATCHER_H
+
+
+#ifdef __APPLE__ // our Clang still doesn't know about cstdint (c++11 stuff)
+ #include <stdint.h>
+#else
+ #include <cstdint>
+#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
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <cassert>
+#include "midiEvent.h"
+
+
+namespace giada {
+namespace m
+{
+MidiEvent::MidiEvent()
+ : m_status (0),
+ m_channel (0),
+ m_note (0),
+ m_velocity(0),
+ m_delta (0)
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+MidiEvent::MidiEvent(uint32_t 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)
+{
+ assert(c >= 0 && c < G_MAX_MIDI_CHANS);
+ m_channel = c;
+}
+
+
+void MidiEvent::setVelocity(int v)
+{
+ assert(v >= 0 && v < G_MAX_VELOCITY);
+ m_velocity = v;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void MidiEvent::fixVelocityZero()
+{
+ if (m_status == NOTE_ON && m_velocity == 0)
+ m_status = NOTE_OFF;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+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() const
+{
+ return (m_status << 24) | (m_channel << 24) | (m_note << 16) | (m_velocity << 8) | (0x00);
+}
+
+
+uint32_t MidiEvent::getRawNoVelocity() const
+{
+ return (m_status << 24) | (m_channel << 24) | (m_note << 16) | (0x00 << 8) | (0x00);
+}
+
+
+}} // giada::m::
\ No newline at end of file
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef G_MIDI_EVENT_H
+#define G_MIDI_EVENT_H
+
+
+#include <cstdint>
+
+
+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(), getRawNoVelocity()
+ Returns the raw MIDI message. If getRawNoVelocity(), the velocity value is
+ stripped off (i.e. velocity == 0). */
+
+ uint32_t getRaw() const;
+ uint32_t getRawNoVelocity() const;
+
+ void resetDelta();
+ void setChannel(int c);
+ void setVelocity(int v);
+
+ /* fixVelocityZero()
+ According to the MIDI standard, there is a special case if the velocity is
+ set to zero. The NOTE ON message then has the same meaning as a NOTE OFF
+ message, switching the note off. Let's fix it. Sometime however you do want
+ a NOTE ON with velocity zero: setting velocity to 0 in MIDI action editor to
+ mute a specific event. */
+
+ void fixVelocityZero();
+
+private:
+
+ int m_status;
+ int m_channel;
+ int m_note;
+ int m_velocity;
+ int m_delta;
+};
+
+}} // giada::m::
+
+
+#endif
\ No newline at end of file
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <vector>
+#include <string>
+#include <cstring>
+#include <dirent.h>
+#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; i<input.length(); i++, p-=4) {
+ if (input[i] == 'n') {
+ output += '0';
+ if (message->offset == -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<message_t> 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<string> 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::
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef G_MIDIMAPCONF_H
+#define G_MIDIMAPCONF_H
+
+
+#include <vector>
+#include <string>
+
+
+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<message_t> 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<std::string> 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
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <cassert>
+#include <cstring>
+#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 "audioBuffer.h"
+#include "mixer.h"
+
+
+namespace giada {
+namespace m {
+namespace mixer
+{
+namespace
+{
+constexpr Frame 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
+};
+
+AudioBuffer vChanInput; // virtual channel for recording
+AudioBuffer vChanInToOut; // virtual channel in->out bridge (hear what you're playin)
+
+Frame tickTracker = 0;
+Frame tockTracker = 0;
+bool tickPlay = false;
+bool tockPlay = false;
+
+/* inputTracker
+Sample position while recording. */
+
+Frame inputTracker = 0;
+
+
+/* -------------------------------------------------------------------------- */
+
+/* computePeak */
+
+void computePeak(const AudioBuffer& buf, float& peak, Frame frame)
+{
+ for (int i=0; i<buf.countChannels(); i++)
+ if (buf[frame][i] > peak)
+ peak = buf[frame][i];
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+/* lineInRec
+Records from line in. */
+
+void lineInRec(const AudioBuffer& 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++;
+ return;
+ }
+
+ for (int i=0; i<vChanInput.countChannels(); i++)
+ vChanInput[inputTracker][i] += inBuf[frame][i] * inVol; // adding: overdub!
+
+ inputTracker++;
+ if (inputTracker >= clock::getFramesInLoop())
+ inputTracker = 0;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+/* ProcessLineIn
+Computes line in peaks, plus handles "hear what you're playin'" thing. */
+
+void processLineIn(const AudioBuffer& inBuf, unsigned frame)
+{
+ if (!kernelAudio::isInputEnabled())
+ return;
+
+ computePeak(inBuf, peakIn, frame);
+
+ /* "hear what you're playing" - process, copy and paste the input buffer onto
+ the output buffer. */
+
+ if (inToOut)
+ for (int i=0; i<vChanInToOut.countChannels(); i++)
+ vChanInToOut[frame][i] = inBuf[frame][i] * inVol;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+/* prepareBuffers
+Cleans up every buffer, both in Mixer and in channels. */
+
+void prepareBuffers(AudioBuffer& outBuf)
+{
+ outBuf.clear();
+ vChanInToOut.clear();
+ for (Channel* channel : channels)
+ channel->prepareBuffer(clock::isRunning());
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+/* 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();
+ }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+/* renderMetronome
+Generates metronome when needed and pastes it to the output buffer. */
+
+void renderMetronome(AudioBuffer& outBuf, unsigned frame)
+{
+ if (tockPlay) {
+ for (int i=0; i<outBuf.countChannels(); i++)
+ outBuf[frame][i] += tock[tockTracker];
+ tockTracker++;
+ if (tockTracker >= TICKSIZE-1) {
+ tockPlay = false;
+ tockTracker = 0;
+ }
+ }
+ if (tickPlay) {
+ for (int i=0; i<outBuf.countChannels(); i++)
+ outBuf[frame][i] += 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(AudioBuffer& outBuf, const AudioBuffer& inBuf)
+{
+ for (Channel* channel : channels)
+ channel->process(outBuf, inBuf, isChannelAudible(channel), clock::isRunning());
+
+#ifdef WITH_VST
+ pluginHost::processStack(outBuf, pluginHost::MASTER_OUT);
+ pluginHost::processStack(vChanInToOut, pluginHost::MASTER_IN);
+#endif
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+/* limitOutput
+Applies a very dumb hard limiter. */
+
+void limitOutput(AudioBuffer& outBuf, unsigned frame)
+{
+ for (int i=0; i<outBuf.countChannels(); i++)
+ if (outBuf[frame][i] > 1.0f)
+ outBuf[frame][i] = 1.0f;
+ else if (outBuf[frame][i] < -1.0f)
+ outBuf[frame][i] = -1.0f;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+/* finalizeOutput
+Last touches after the output has been rendered: apply inToOut if any, apply
+output volume. */
+
+void finalizeOutput(AudioBuffer& outBuf, unsigned frame)
+{
+ /* Merge vChanInToOut, if enabled. */
+
+ if (inToOut)
+ outBuf.copyFrame(frame, vChanInToOut[frame]);
+
+ for (int i=0; i<outBuf.countChannels(); i++)
+ outBuf[frame][i] *= outVol;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void renderMetronome()
+{
+ if (!metronome)
+ return;
+ if (clock::isOnBar() || clock::isOnFirstBeat())
+ tickPlay = true;
+ else
+ if (clock::isOnBeat())
+ tockPlay = true;
+}
+}; // {anonymous}
+
+
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+
+
+std::vector<Channel*> channels;
+
+bool recording = false;
+bool ready = true;
+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;
+bool rewindWait = false;
+bool hasSolos = false;
+bool inToOut = false;
+
+pthread_mutex_t mutex;
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void init(Frame framesInSeq, Frame framesInBuffer)
+{
+ /* Allocate virtual input channels. vChanInput has variable size: it depends
+ on how many frames there are in sequencer. */
+
+ vChanInput.alloc(framesInSeq, G_MAX_IO_CHANS);
+ vChanInToOut.alloc(framesInBuffer, G_MAX_IO_CHANS);
+
+ gu_log("[Mixer::init] buffers ready - framesInSeq=%d, framesInBuffer=%d\n",
+ framesInSeq, framesInBuffer);
+
+ hasSolos = false;
+
+ pthread_mutex_init(&mutex, nullptr);
+
+ rewind();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void allocVirtualInput(Frame frames)
+{
+ vChanInput.alloc(frames, G_MAX_IO_CHANS);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+int masterPlay(void* outBuf, void* inBuf, unsigned bufferSize,
+ double streamTime, RtAudioStreamStatus status, void* userData)
+{
+ if (!ready)
+ return 0;
+
+ pthread_mutex_lock(&mutex);
+
+#ifdef __linux__
+ clock::recvJackSync();
+#endif
+
+ AudioBuffer out, in;
+ out.setData((float*) outBuf, bufferSize, G_MAX_IO_CHANS);
+ if (kernelAudio::isInputEnabled())
+ in.setData((float*) inBuf, bufferSize, G_MAX_IO_CHANS);
+
+ peakOut = 0.0f; // reset peak calculator
+ peakIn = 0.0f; // reset peak calculator
+
+ prepareBuffers(out);
+
+ for (unsigned j=0; j<bufferSize; j++) {
+ processLineIn(in, j); // TODO - can go outside this loop
+
+ if (clock::isRunning()) {
+ FrameEvents fe;
+ fe.frameLocal = j;
+ fe.frameGlobal = clock::getCurrentFrame();
+ fe.doQuantize = clock::getQuantize() == 0 || !clock::quantoHasPassed();
+ fe.onBar = clock::isOnBar();
+ fe.onFirstBeat = clock::isOnFirstBeat();
+ fe.quantoPassed = clock::quantoHasPassed();
+ fe.actions = recorder::getActionsOnFrame(clock::getCurrentFrame());
+
+ for (Channel* channel : channels)
+ channel->parseEvents(fe);
+
+ lineInRec(in, j); // TODO - can go outside this loop
+ doQuantize(j);
+ renderMetronome();
+ clock::incrCurrentFrame();
+ clock::sendMIDIsync();
+ }
+ }
+
+ renderIO(out, in);
+
+ /* Post processing. */
+ for (unsigned j=0; j<bufferSize; j++) {
+ finalizeOutput(out, j);
+ if (conf::limitOutput)
+ limitOutput(out, j);
+ computePeak(out, peakOut, j);
+ renderMetronome(out, j);
+ }
+
+ /* Unset data in buffers. If you don't do this, buffers go out of scope and
+ destroy memory allocated by RtAudio ---> havoc. */
+ out.setData(nullptr, 0, 0);
+ in.setData(nullptr, 0, 0);
+
+ pthread_mutex_unlock(&mutex);
+
+ return 0;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void close()
+{
+ clock::stop();
+ while (channels.size() > 0)
+ mh::deleteChannel(channels.at(0));
+ pthread_mutex_destroy(&mutex);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+bool isSilent()
+{
+ for (const Channel* ch : channels)
+ if (ch->isPlaying())
+ return false;
+ return true;
+}
+
+
+
+/* -------------------------------------------------------------------------- */
+
+
+bool isChannelAudible(Channel* ch)
+{
+ return !hasSolos || (hasSolos && ch->solo);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void rewind()
+{
+ clock::rewind();
+ if (clock::isRunning())
+ for (Channel* ch : channels)
+ ch->rewindBySeq();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void startInputRec()
+{
+ /* Start inputTracker from the current frame, not the beginning. */
+ recording = true;
+ inputTracker = clock::getCurrentFrame();
+}
+
+/* -------------------------------------------------------------------------- */
+
+
+void mergeVirtualInput()
+{
+ for (Channel* ch : channels) {
+ /* TODO - move this to audioProc::*/
+ if (ch->type == ChannelType::MIDI)
+ continue;
+ SampleChannel* sch = static_cast<SampleChannel*>(ch);
+ if (sch->armed)
+ sch->wave->copyData(vChanInput[0], vChanInput.countFrames());
+ }
+ vChanInput.clear();
+}
+}}}; // giada::m::mixer::
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef G_MIXER_H
+#define G_MIXER_H
+
+
+#include <pthread.h>
+#include <vector>
+#include "recorder.h"
+#include "types.h"
+#include "../deps/rtaudio-mod/RtAudio.h"
+
+
+class Channel;
+
+
+namespace giada {
+namespace m {
+namespace mixer
+{
+struct FrameEvents
+{
+ Frame frameLocal;
+ Frame frameGlobal;
+ bool doQuantize;
+ bool onBar;
+ bool onFirstBeat;
+ bool quantoPassed;
+ std::vector<recorder::action*> actions;
+};
+
+extern std::vector<Channel*> channels;
+
+extern bool recording; // is recording something?
+extern bool ready;
+extern float outVol;
+extern float inVol;
+extern float peakOut;
+extern float peakIn;
+extern bool metronome;
+extern int waitRec; // delayComp guard
+extern bool rewindWait; // rewind guard, if quantized
+extern bool hasSolos; // more than 0 channels soloed
+
+/* 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;
+
+void init(Frame framesInSeq, Frame framesInBuffer);
+
+/* allocVirtualInput
+Allocates new memory for the virtual input channel. Call this whenever you
+shrink or resize the sequencer. */
+
+void allocVirtualInput(Frame frames);
+
+void 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();
+
+bool isChannelAudible(Channel* ch);
+
+/* rewind
+Rewinds sequencer to frame 0. */
+
+void rewind();
+
+/* startInputRec
+Starts input recording on frame clock::getCurrentFrame(). */
+
+void startInputRec();
+
+/* mergeVirtualInput
+Copies the virtual channel input in the channels designed for input recording.
+Called by mixerHandler on stopInputRec(). */
+
+void mergeVirtualInput();
+}}} // giada::m::mixer::;
+
+
+#endif
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <vector>
+#include <algorithm>
+#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 "channelManager.h"
+#include "mixerHandler.h"
+
+
+using std::vector;
+using std::string;
+
+
+namespace giada {
+namespace m {
+namespace mh
+{
+namespace
+{
+#ifdef WITH_VST
+
+int readPatchPlugins(vector<patch::plugin_t>* list, int type)
+{
+ int ret = 1;
+ for (unsigned i=0; i<list->size(); i++) {
+ patch::plugin_t *ppl = &list->at(i);
+ // TODO use glue_addPlugin()
+ Plugin *plugin = pluginHost::addPlugin(ppl->path.c_str(), type,
+ &mixer::mutex, nullptr);
+ if (plugin != nullptr) {
+ plugin->setBypass(ppl->bypass);
+ for (unsigned j=0; j<ppl->params.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; i<mixer::channels.size()-1; i++) {
+ if (mixer::channels.at(i)->index > 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 != ChannelType::SAMPLE) // skip itself and MIDI channels
+ continue;
+ const SampleChannel* sch = static_cast<const SampleChannel*>(ch);
+ if (sch->wave != nullptr && path == sch->wave->getPath())
+ return false;
+ }
+ return true;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+Channel* addChannel(ChannelType type)
+{
+ Channel* ch = nullptr;
+ channelManager::create(type, kernelAudio::getRealBufSize(),
+ conf::inputMonitorDefaultOn, &ch);
+ if (ch == nullptr)
+ return nullptr;
+
+ while (true) {
+ if (pthread_mutex_trylock(&mixer::mutex) != 0)
+ continue;
+ mixer::channels.push_back(ch);
+ pthread_mutex_unlock(&mixer::mutex);
+ 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;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void deleteChannel(Channel* target)
+{
+ while (true) {
+ if (pthread_mutex_trylock(&mixer::mutex) != 0)
+ continue;
+ auto it = std::find(mixer::channels.begin(), mixer::channels.end(), target);
+ if (it != mixer::channels.end())
+ mixer::channels.erase(it);
+ pthread_mutex_unlock(&mixer::mutex);
+ return;
+ }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+Channel* getChannelByIndex(int index)
+{
+ for (Channel* ch : mixer::channels)
+ if (ch->index == index)
+ return ch;
+ gu_log("[getChannelByIndex] channel at index %d not found!\n", index);
+ return nullptr;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+bool hasLogicalSamples()
+{
+ for (const Channel* ch : mixer::channels)
+ if (ch->hasLogicalData())
+ return true;
+ return false;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+bool hasEditedSamples()
+{
+ for (const Channel* ch : mixer::channels)
+ if (ch->hasEditedData())
+ return true;
+ return false;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void stopSequencer()
+{
+ clock::stop();
+ for (Channel* ch : mixer::channels)
+ ch->stopBySeq(conf::chansStopOnSeqHalt);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void updateSoloCount()
+{
+ for (const Channel* ch : mixer::channels)
+ if (ch->solo) {
+ mixer::hasSolos = true;
+ return;
+ }
+ mixer::hasSolos = false;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+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::getFramesInLoop());
+ 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* ch : mixer::channels) {
+
+ if (!ch->canInputRec())
+ continue;
+
+ SampleChannel* sch = static_cast<SampleChannel*>(ch);
+
+ /* Allocate empty sample for the current channel. */
+
+ Wave* wave = nullptr;
+ string name = string("TAKE-" + gu_iToString(patch::lastTakeId++)); // Increase lastTakeId
+
+ waveManager::createEmpty(clock::getFramesInLoop(), G_MAX_IO_CHANS,
+ conf::samplerate, name + ".wav", &wave);
+
+ sch->pushWave(wave);
+ sch->name = name;
+ channelsReady++;
+
+ gu_log("[startInputRec] start input recs using chan %d with size %d "
+ "on frame=%d\n", sch->index, clock::getFramesInLoop(), clock::getCurrentFrame());
+ }
+
+ /** FIXME: mixer::startInputRec() should be called before wave allocation */
+ /** FIXME: mixer::startInputRec() should be called before wave allocation */
+ /** FIXME: mixer::startInputRec() should be called before wave allocation */
+ if (channelsReady == 0)
+ return false;
+ mixer::startInputRec();
+ return true;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void stopInputRec()
+{
+ mixer::mergeVirtualInput();
+ mixer::recording = false;
+ mixer::waitRec = 0; // in case delay compensation is in use
+
+ for (Channel* ch : mixer::channels)
+ ch->stopInputRec(clock::getCurrentFrame());
+
+ gu_log("[mh] stop input recs\n");
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+bool hasArmedSampleChannels()
+{
+ for (const Channel* ch : mixer::channels)
+ if (ch->type == ChannelType::SAMPLE && ch->armed)
+ return true;
+ return false;
+}
+
+
+}}}; // giada::m::mh::
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef G_MIXER_HANDLER_H
+#define G_MIXER_HANDLER_H
+
+
+#include <string>
+#include "types.h"
+
+
+class Channel;
+class SampleChannel;
+
+
+namespace giada {
+namespace m {
+namespace mh
+{
+/* addChannel
+Adds a new channel of type 'type' into mixer's stack. */
+
+Channel* addChannel(ChannelType type);
+
+/* deleteChannel
+Completely removes a channel from the stack. */
+
+void 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();
+
+/* updateSoloCount
+Updates the number of solo-ed channels in mixer. */
+
+void updateSoloCount();
+
+/* 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
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include "../utils/log.h"
+#include "../utils/string.h"
+#include "../utils/ver.h"
+#include "const.h"
+#include "types.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; i<columns.size(); i++) {
+ column_t* col = &columns.at(i);
+ col->index = col->index < 0 ? 0 : col->index;
+ col->width = col->width < G_MIN_COLUMN_WIDTH ? G_MIN_COLUMN_WIDTH : col->width;
+ }
+
+ for (unsigned i=0; i<channels.size(); i++) {
+ channel_t* ch = &channels.at(i);
+ ch->size = 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;
+ }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+/* modernize
+Makes sure an older patch is compatible with the current version. */
+
+void modernize()
+{
+ /* Starting from 0.15.0 actions are recorded on frames, not samples. */
+ if (u::ver::isLess(versionMajor, versionMinor, versionPatch, 0, 15, 0)) {
+ for (channel_t& ch : channels)
+ for (action_t& a : ch.actions)
+ a.frame /= 2;
+ }
+
+ /* Starting from 0.15.1 Channel Modes have different values. */
+ if (u::ver::isLess(versionMajor, versionMinor, versionPatch, 0, 15, 1)) {
+ for (channel_t& ch : channels) {
+ if (ch.mode == 0x04) ch.mode = static_cast<int>(ChannelMode::SINGLE_BASIC);
+ else if (ch.mode == 0x08) ch.mode = static_cast<int>(ChannelMode::SINGLE_PRESS);
+ else if (ch.mode == 0x10) ch.mode = static_cast<int>(ChannelMode::SINGLE_RETRIG);
+ else if (ch.mode == 0x20) ch.mode = static_cast<int>(ChannelMode::LOOP_REPEAT);
+ else if (ch.mode == 0x40) ch.mode = static_cast<int>(ChannelMode::SINGLE_ENDLESS);
+ else if (ch.mode == 0x80) ch.mode = static_cast<int>(ChannelMode::LOOP_ONCE_BAR);
+ }
+ }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+/* 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<plugin_t>* 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_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::setBool (jChannel, PATCH_KEY_CHANNEL_MIDI_IN_VELO_AS_VOL, channel.midiInVeloAsVol)) 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<plugin_t>* plugins, const char* key)
+{
+ json_t* jPlugins = json_array();
+ for (unsigned j=0; j<plugins->size(); 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<plugin.params.size(); z++)
+ json_array_append_new(jPluginParams, json_real(plugin.params.at(z)));
+ json_object_set_new(jPlugin, PATCH_KEY_PLUGIN_PARAMS, jPluginParams);
+
+ /* midiIn params (midi learning on plugins' parameters) */
+
+ json_t* jPluginMidiInParams = json_array();
+ for (unsigned z=0; z<plugin.midiInParams.size(); z++)
+ json_array_append_new(jPluginMidiInParams, json_integer(plugin.midiInParams.at(z)));
+ json_object_set_new(jPlugin, PATCH_KEY_PLUGIN_MIDI_IN_PARAMS, jPluginMidiInParams);
+ }
+ json_object_set_new(jContainer, key, jPlugins);
+}
+
+#endif
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void writeColumns(json_t* jContainer, vector<column_t>* columns)
+{
+ json_t* jColumns = json_array();
+ for (unsigned i=0; i<columns->size(); 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<action_t>*actions)
+{
+ json_t* jActions = json_array();
+ for (unsigned k=0; k<actions->size(); 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<channel_t>* channels)
+{
+ json_t* jChannels = json_array();
+ for (unsigned i=0; i<channels->size(); 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_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_VELO_AS_VOL, json_boolean(channel.midiInVeloAsVol));
+ 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<column_t> columns;
+std::vector<channel_t> channels;
+
+#ifdef WITH_VST
+std::vector<plugin_t> masterInPlugins;
+std::vector<plugin_t> 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();
+ modernize();
+
+ return PATCH_READ_OK;
+}
+
+
+}}}; // giada::m::patch::
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef G_PATCH_H
+#define G_PATCH_H
+
+
+#include <string>
+#include <vector>
+#include <cstdint>
+
+
+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<float> params;
+ std::vector<uint32_t> midiInParams;
+};
+#endif
+
+struct channel_t
+{
+ int type;
+ int index;
+ int size;
+ std::string name;
+ int column;
+ int mute;
+ int solo;
+ float volume;
+ float pan;
+ bool midiIn;
+ bool midiInVeloAsVol;
+ 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<action_t> actions;
+
+#ifdef WITH_VST
+ std::vector<plugin_t> plugins;
+#endif
+};
+
+struct column_t
+{
+ int index;
+ int width;
+ std::vector<int> 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<column_t> columns;
+extern std::vector<channel_t> channels;
+
+#ifdef WITH_VST
+extern std::vector<plugin_t> masterInPlugins;
+extern std::vector<plugin_t> 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
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifdef WITH_VST
+
+
+#include <cassert>
+#include <FL/Fl.H>
+#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)
+{
+ using namespace juce;
+
+ /* Init midiInParams. All values are empty (0x0): they will be filled during
+ midi learning process. */
+
+ const OwnedArray<AudioProcessorParameter>& params = plugin->getParameters();
+ for (int i=0; i<params.size(); 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;
+ }
+ 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().createIdentifierString().toStdString();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+int Plugin::getNumParameters() const
+{
+ return plugin->getParameters().size();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+float Plugin::getParameter(int paramIndex) const
+{
+ return plugin->getParameters()[paramIndex]->getValue();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void Plugin::setParameter(int paramIndex, float value) const
+{
+ plugin->getParameters()[paramIndex]->setValue(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 { assert(ui != nullptr); return ui->getWidth(); }
+int Plugin::getEditorH() const { assert(ui != nullptr); return ui->getHeight(); }
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void Plugin::process(juce::AudioBuffer<float>& 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->getParameters()[index]->getName(MAX_LABEL_SIZE).toStdString();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+string Plugin::getParameterText(int index) const
+{
+ return plugin->getParameters()[index]->getCurrentValueAsText().toStdString();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+string Plugin::getParameterLabel(int index) const
+{
+ return plugin->getParameters()[index]->getLabel().toStdString();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void Plugin::closeEditor()
+{
+ delete ui;
+ ui = nullptr;
+}
+
+#endif
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifdef WITH_VST
+
+#ifndef G_PLUGIN_H
+#define G_PLUGIN_H
+
+
+#include "../deps/juce-config.h"
+
+
+class Plugin
+{
+private:
+
+ static const int MAX_LABEL_SIZE = 64;
+
+ 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<float>& 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<uint32_t> midiInParams;
+};
+
+#endif
+
+#endif // #ifdef WITH_VST
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifdef WITH_VST
+
+
+#include <cassert>
+#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<string> unknownPluginList;
+
+vector<Plugin*> masterOut;
+vector<Plugin*> masterIn;
+
+/* Audio|MidiBuffer
+ * Dynamic buffers. */
+
+juce::AudioBuffer<float> audioBuffer;
+
+int samplerate;
+int buffersize;
+
+/* missingPlugins
+ * If some plugins from any stack are missing. */
+
+bool missingPlugins;
+
+void splitPluginDescription(const string& descr, vector<string>& 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<string> idParts;
+ splitPluginDescription(id, idParts);
+
+ for (const juce::PluginDescription* pd : knownPluginList) {
+ vector<string> 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();
+ pthread_mutex_destroy(&mutex_midi);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void init(int buffersize_, int samplerate_)
+{
+ messageManager = juce::MessageManager::getInstance();
+ audioBuffer.setSize(G_MAX_IO_CHANS, buffersize_);
+ samplerate = samplerate_;
+ buffersize = buffersize_;
+ missingPlugins = false;
+ //unknownPluginList.empty();
+ loadList(gu_getHomePath() + G_SLASH + "plugins.xml");
+
+ pthread_mutex_init(&mutex_midi, nullptr);
+
+ gu_log("[pluginHost::init] initialized with buffersize=%d, samplerate=%d\n",
+ buffersize, samplerate);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+int scanDirs(const string& dirs, const std::function<void(float)>& 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<string> 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<Plugin*>* 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<Plugin*>* 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<Plugin*>* 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<Plugin*>* pStack = getStack(stackType, ch);
+
+ if (pStack->size() == 0)
+ return;
+
+ while (true) {
+ if (pthread_mutex_trylock(mutex) != 0)
+ continue;
+ for (unsigned i=0; i<pStack->size(); i++)
+ delete pStack->at(i);
+ pStack->clear();
+ pthread_mutex_unlock(mutex);
+ break;
+ }
+ gu_log("[pluginHost::freeStack] stack type=%d freed\n", stackType);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void processStack(AudioBuffer& outBuf, int stackType, Channel* ch)
+{
+ vector<Plugin*>* pStack = getStack(stackType, ch);
+
+ /* Empty stack, stack not found or mixer not ready: do nothing. */
+
+ if (pStack == nullptr || pStack->size() == 0)
+ return;
+
+ assert(outBuf.countFrames() == audioBuffer.getNumSamples());
+
+ /* 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 == ChannelType::MIDI)
+ audioBuffer.clear();
+ else
+ for (int i=0; i<outBuf.countFrames(); i++)
+ for (int j=0; j<outBuf.countChannels(); j++)
+ audioBuffer.setSample(j, i, outBuf[i][j]);
+
+ /* Hardcore processing. At the end we swap input and output, so that he N-th
+ plugin will process the result of the plugin N-1. Part of this loop must be
+ guarded by mutexes, i.e. the MIDI process part. You definitely don't want
+ a situation like the following one:
+ this::processStack()
+ [a new midi event comes in from kernelMidi thread]
+ channel::clearMidiBuffer()
+ The midi event in between would be surely lost, deleted by the last call to
+ channel::clearMidiBuffer()! */
+
+ if (ch != nullptr)
+ pthread_mutex_lock(&mutex_midi);
+
+ for (const Plugin* plugin : *pStack) {
+ if (plugin->isSuspended() || 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<float> tmp(audioBuffer.getNumChannels(), buffersize);
+ plugin->process(tmp, ch->getPluginMidiEvents());
+ for (int i=0; i<audioBuffer.getNumSamples(); i++)
+ for (int j=0; j<audioBuffer.getNumChannels(); j++)
+ audioBuffer.addSample(j, i, tmp.getSample(j, i));
+ }
+ else
+ plugin->process(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<outBuf.countFrames(); i++)
+ for (int j=0; j<outBuf.countChannels(); j++)
+ outBuf[i][j] = audioBuffer.getSample(j, i);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+Plugin* getPluginByIndex(int index, int stackType, Channel* ch)
+{
+ vector<Plugin*>* 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<Plugin*>* pStack = getStack(stackType, ch);
+ for (unsigned i=0; i<pStack->size(); 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<Plugin*>* 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<Plugin*>* pStack = getStack(stackType, ch);
+ for (unsigned i=0; i<pStack->size(); 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<Channel*>* channels, pthread_mutex_t* mutex)
+{
+ freeStack(pluginHost::MASTER_OUT, mutex);
+ freeStack(pluginHost::MASTER_IN, mutex);
+ for (unsigned i=0; i<channels->size(); 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; k<src->getNumParameters(); 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;
+ }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void forEachPlugin(int stackType, const Channel* ch, std::function<void(const Plugin* p)> f)
+{
+ /* TODO - Remove const is ugly. This is a temporary workaround until all
+ PluginHost functions params will be const-correct. */
+ vector<Plugin*>* stack = getStack(stackType, const_cast<Channel*>(ch));
+ for (const Plugin* p : *stack)
+ f(p);
+}
+
+
+}}}; // giada::m::pluginHost::
+
+
+#endif // #ifdef WITH_VST
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifdef WITH_VST
+
+#ifndef G_PLUGIN_HOST_H
+#define G_PLUGIN_HOST_H
+
+
+#include <functional>
+#include <pthread.h>
+#include "../deps/juce-config.h"
+#include "audioBuffer.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;
+};
+
+extern pthread_mutex_t mutex_midi;
+
+void init(int bufSize, int samplerate);
+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<void(float)>& 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
+Applies the fx list to the buffer. */
+
+void processStack(AudioBuffer& outBuf, int stackType, Channel* ch=nullptr);
+
+/* getStack
+* Return a std::vector <Plugin *> given the stackType. If stackType == CHANNEL
+* a pointer to Channel is also required. */
+
+std::vector<Plugin*>* 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<Channel*>* 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);
+
+void forEachPlugin(int stackType, const Channel* ch, std::function<void(const Plugin* p)> f);
+
+}}}; // giada::m::pluginHost::
+
+
+#endif
+
+#endif // #ifdef WITH_VST
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef G_RANGE_H
+#define G_RANGE_H
+
+
+#include <cassert>
+
+
+namespace giada
+{
+template<typename T>
+class Range
+{
+private:
+
+ T m_a;
+ T m_b;
+
+public:
+
+ Range() : m_a(0), m_b(0) {}
+ Range(T a, T b) : m_a(a), m_b(b) { assert(a < b); }
+
+ T getBegin() const { return m_a; }
+ T getEnd() const { return m_b; }
+ T getLength() const { return m_b - m_a; }
+};
+} // giada::
+
+
+#endif
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <cassert>
+#include <cmath>
+#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<int> frames;
+vector<vector<action*>> global;
+vector<action*> actions; // used internally
+
+bool active = false;
+bool sortedActions = false;
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void init()
+{
+ active = false;
+ sortedActions = false;
+ clearAll();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+bool canRec(Channel* ch, bool clockRunning, bool mixerRecording)
+{
+ /* Can record on a channel if:
+ - recorder is on
+ - mixer is running
+ - mixer is not recording a take somewhere
+ - channel is MIDI or SAMPLE type with data in it */
+ return active && clockRunning && !mixerRecording &&
+ (ch->type == ChannelType::MIDI || (ch->type == ChannelType::SAMPLE && ch->hasData()));
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void rec(int index, int type, int frame, uint32_t iValue, float fValue)
+{
+ /* allocating the action */
+
+ action* a = (action*) malloc(sizeof(action)); /* TODO - AAARRRGGHHHHHH!!!! */
+ 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; i<frameToExpand; i++)
+ if (frames.at(i) == frame) {
+ frameToExpand = i;
+ break;
+ }
+
+ /* espansione dello stack frames nel caso l'azione ricada in frame
+ * non precedentemente memorizzati (frameToExpand == frames.size()).
+ * Espandere frames è facile, basta aggiungere un frame in coda.
+ * Espandere global è più complesso: bisogna prima allocare una
+ * cella in global (per renderlo parallelo a frames) e poi
+ * inizializzare il suo sub-stack (di action). */
+
+ if (frameToExpand == (int) frames.size()) {
+ frames.push_back(frame);
+ global.push_back(actions); // array of actions added
+ global.at(global.size()-1).push_back(a); // action added
+ }
+ else {
+
+ /* no duplicates, please */
+
+ for (unsigned t=0; t<global.at(frameToExpand).size(); t++) {
+ action* ac = global.at(frameToExpand).at(t);
+ if (ac->chan == 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; i<global.size(); i++) { // for each frame i
+ unsigned j=0;
+ while (true) {
+ if (j == global.at(i).size()) break; // for each action j of frame i
+ action* a = global.at(i).at(j);
+ if (a->chan == 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; i<global.size(); i++) { // for each frame i
+ unsigned j=0;
+ while (true) { // for each action j of frame i
+ if (j == global.at(i).size())
+ break;
+ action* a = global.at(i).at(j);
+ if (a->chan == 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)
+{
+ /* find the frame 'frame' */
+
+ bool found = false;
+ for (unsigned i=0; i<frames.size() && !found; i++) {
+
+ if (frames.at(i) != frame)
+ continue;
+
+ /* find the action in frame i */
+
+ for (unsigned j=0; j<global.at(i).size(); j++) {
+ action* a = global.at(i).at(j);
+
+ /* action comparison logic */
+
+ bool doit = (a->chan == 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<int> dels;
+
+ for (unsigned i=0; i<frames.size(); i++)
+ if (frames.at(i) > frame_a && frames.at(i) < frame_b)
+ dels.push_back(frames.at(i));
+
+ for (unsigned i=0; i<dels.size(); i++)
+ deleteAction(chan, dels.at(i), type, false, mixerMutex); // false == don't check values
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void clearAll()
+{
+ while (global.size() > 0) {
+ for (unsigned i=0; i<global.size(); i++) {
+ for (unsigned k=0; k<global.at(i).size(); k++)
+ free(global.at(i).at(k)); // free action
+ global.at(i).clear(); // free action container
+ global.erase(global.begin() + i);
+ }
+ }
+ global.clear();
+ frames.clear();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void optimize()
+{
+ /* do something until the i frame is empty. */
+
+ unsigned i = 0;
+ while (true) {
+ if (i == global.size()) return;
+ if (global.at(i).size() == 0) {
+ global.erase(global.begin() + i);
+ frames.erase(frames.begin() + i);
+ }
+ else
+ i++;
+ }
+
+ sortActions();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void sortActions()
+{
+ if (sortedActions)
+ return;
+ for (unsigned i=0; i<frames.size(); i++)
+ for (unsigned j=0; j<frames.size(); j++)
+ if (frames.at(j) > 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<frames.size(); i++) {
+
+ float frame = ((float) frames.at(i)/newval) * oldval;
+ frames.at(i) = (int) frame;
+
+ /* the division up here cannot be precise. A new frame can be 44099
+ * and the quantizer set to 44100. That would mean two recs completely
+ * useless. So we compute a reject value ('scarto'): if it's lower
+ * than 6 frames the new frame is collapsed with a quantized frame. */
+ /** XXX - maybe 6 frames are too low */
+
+ if (frames.at(i) != 0) {
+ int scarto = oldquanto % frames.at(i);
+ if (scarto > 0 && scarto <= 6)
+ frames.at(i) = frames.at(i) + scarto;
+ }
+ }
+
+ /* update structs */
+
+ for (unsigned i=0; i<frames.size(); i++) {
+ for (unsigned j=0; j<global.at(i).size(); j++) {
+ action* a = global.at(i).at(j);
+ a->frame = 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; i<frames.size(); i++) {
+
+ gu_log("[recorder::updateSamplerate] oldFrame = %d", frames.at(i));
+
+ float newFrame = frames.at(i);
+ newFrame = floorf(newFrame * ratio);
+
+ frames.at(i) = (int) newFrame;
+
+ gu_log(", newFrame = %d\n", frames.at(i));
+ }
+
+ /* update structs */
+
+ for (unsigned i=0; i<frames.size(); i++) {
+ for (unsigned j=0; j<global.at(i).size(); j++) {
+ action* a = global.at(i).at(j);
+ a->frame = 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; i<init_fs; i++) {
+ unsigned newframe = frames.at(i) + (old_fpb*z);
+ frames.push_back(newframe);
+ global.push_back(actions);
+ for (unsigned k=0; k<global.at(i).size(); k++) {
+ action* a = global.at(i).at(k);
+ rec(a->chan, 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; k<global.at(i).size(); k++)
+ free(global.at(i).at(k)); // free action
+ global.at(i).clear(); // free action container
+ global.erase(global.begin() + i); // shrink global
+ frames.erase(frames.begin() + i); // shrink frames
+ }
+ else
+ i++;
+ }
+ optimize();
+ gu_log("[recorder::shrink] shrinked recs\n");
+ //print();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+bool hasActions(int chanIndex, int type)
+{
+ if (global.size() == 0)
+ return false;
+ for (unsigned i=0; i<global.size(); i++) {
+ for (unsigned j=0; j<global.at(i).size(); j++) {
+ if (global.at(i).at(j)->chan == chanIndex)
+ if (type == -1 || global.at(i).at(j)->type == type)
+ 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 (; i<global.size(); i++) {
+
+ for (unsigned j=0; j<global.at(i).size(); j++) {
+
+ action* a = global.at(i).at(j);
+
+ /* If the requested channel and type don't match, continue. */
+
+ if (a->chan != 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; i<global.size(); i++)
+ for (unsigned j=0; j<global.at(i).size(); j++)
+ if (frame == global.at(i).at(j)->frame &&
+ 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 */
+
+ cmp.a1.type = G_ACTION_KEYPRESS;
+ cmp.a2.type = G_ACTION_KEYREL;
+ 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);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+vector<action*> getActionsOnFrame(int frame)
+{
+ for (size_t i=0; i<frames.size(); i++) {
+ if (recorder::frames.at(i) != frame)
+ continue;
+ return global.at(i);
+ }
+ return vector<action*>();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void forEachAction(std::function<void(const action*)> f)
+{
+
+ for (const vector<action*> actions : recorder::global)
+ for (const action* action : actions)
+ f(action);
+}
+}}}; // giada::m::recorder::
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef G_RECORDER_H
+#define G_RECORDER_H
+
+
+#include <cstdint>
+#include <vector>
+#include <functional>
+#include <pthread.h>
+
+
+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<int> frames;
+
+/* global
+Contains the actual actions. E.g.:
+ global[0] = <actions>
+ global[1] = <actions> */
+
+extern std::vector<std::vector<action*>> global;
+/* TODO - this frames vs global madness must be replaced with a map:
+std::map<int, vector<actions>> */
+
+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. Type != -1: check if channel has actions of type 'type'.*/
+
+bool hasActions(int chanIndex, int type=-1);
+
+/* 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);
+
+/* getActionsOnFrame
+Returns a vector of actions that occur on frame 'frame'. */
+
+std::vector<action*> getActionsOnFrame(int frame);
+
+/* 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);
+
+/* forEachAction
+Applies a read-only callback on each action recorded. */
+
+void forEachAction(std::function<void(const action*)> f);
+}}}; // giada::m::recorder::
+
+#endif
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include "../utils/log.h"
+#include "sampleChannelProc.h"
+#include "sampleChannelRec.h"
+#include "channelManager.h"
+#include "const.h"
+#include "wave.h"
+#include "sampleChannel.h"
+
+
+using std::string;
+using namespace giada;
+using namespace giada::m;
+
+
+SampleChannel::SampleChannel(bool inputMonitor, int bufferSize)
+ : Channel (ChannelType::SAMPLE, ChannelStatus::EMPTY, bufferSize),
+ mode (ChannelMode::SINGLE_BASIC),
+ wave (nullptr),
+ tracker (0),
+ trackerPreview (0),
+ shift (0),
+ qWait (false),
+ inputMonitor (inputMonitor),
+ boost (G_DEFAULT_BOOST),
+ pitch (G_DEFAULT_PITCH),
+ begin (0),
+ end (0),
+ midiInReadActions(0x0),
+ midiInPitch (0x0),
+ rsmp_state (nullptr)
+{
+ rsmp_state = src_new(SRC_LINEAR, G_MAX_IO_CHANS, nullptr);
+ if (rsmp_state == nullptr) {
+ gu_log("[SampleChannel] unable to alloc memory for SRC_STATE!\n");
+ throw std::bad_alloc();
+ }
+ bufferPreview.alloc(bufferSize, G_MAX_IO_CHANS);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+SampleChannel::~SampleChannel()
+{
+ delete wave;
+ if (rsmp_state != nullptr)
+ src_delete(rsmp_state);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void SampleChannel::copy(const Channel* src_, pthread_mutex_t* pluginMutex)
+{
+ Channel::copy(src_, pluginMutex);
+ const SampleChannel* src = static_cast<const SampleChannel*>(src_);
+ tracker = src->tracker;
+ begin = src->begin;
+ end = src->end;
+ boost = src->boost;
+ mode = src->mode;
+ qWait = src->qWait;
+ setPitch(src->pitch);
+
+ if (src->wave)
+ pushWave(new Wave(*src->wave)); // invoke Wave's copy constructor
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void SampleChannel::parseEvents(m::mixer::FrameEvents fe)
+{
+ sampleChannelProc::parseEvents(this, fe);
+ sampleChannelRec::parseEvents(this, fe);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void SampleChannel::prepareBuffer(bool running)
+{
+ sampleChannelProc::prepareBuffer(this, running);
+}
+
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void SampleChannel::rewindBySeq()
+{
+ sampleChannelProc::rewindBySeq(this);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void SampleChannel::start(int localFrame, bool doQuantize, int velocity)
+{
+ sampleChannelProc::start(this, localFrame, doQuantize, velocity);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void SampleChannel::stop()
+{
+ sampleChannelProc::stop(this);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void SampleChannel::stopBySeq(bool chansStopOnSeqHalt)
+{
+ sampleChannelProc::stopBySeq(this, chansStopOnSeqHalt);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void SampleChannel::kill(int localFrame)
+{
+ sampleChannelProc::kill(this, localFrame);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+bool SampleChannel::recordStart(bool canQuantize)
+{
+ return sampleChannelRec::recordStart(this, canQuantize);
+}
+
+
+bool SampleChannel::recordKill()
+{
+ return sampleChannelRec::recordKill(this);
+}
+
+
+void SampleChannel::recordStop()
+{
+ sampleChannelRec::recordStop(this);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void SampleChannel::startReadingActions(bool treatRecsAsLoops, bool recsStopOnChanHalt)
+{
+ sampleChannelRec::startReadingActions(this, treatRecsAsLoops, recsStopOnChanHalt);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void SampleChannel::stopReadingActions(bool running, bool treatRecsAsLoops,
+ bool recsStopOnChanHalt)
+{
+ sampleChannelRec::stopReadingActions(this, running, treatRecsAsLoops,
+ recsStopOnChanHalt);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void SampleChannel::stopInputRec(int globalFrame)
+{
+ sampleChannelProc::stopInputRec(this, globalFrame);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void SampleChannel::setMute(bool value)
+{
+ sampleChannelProc::setMute(this, value);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void SampleChannel::process(giada::m::AudioBuffer& out,
+ const giada::m::AudioBuffer& in, bool audible, bool running)
+{
+ sampleChannelProc::process(this, out, in, audible, running);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void SampleChannel::readPatch(const string& basePath, int i)
+{
+ Channel::readPatch("", i);
+ channelManager::readPatch(this, basePath, i);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void SampleChannel::writePatch(int i, bool isProject)
+{
+ Channel::writePatch(i, isProject);
+ channelManager::writePatch(this, isProject, i);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void SampleChannel::setReadActions(bool v, bool recsStopOnChanHalt)
+{
+ sampleChannelRec::setReadActions(this, v, recsStopOnChanHalt);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+bool SampleChannel::hasLogicalData() const
+{
+ return wave != nullptr && wave->isLogical();
+};
+
+
+bool SampleChannel::hasEditedData() const
+{
+ return wave != nullptr && wave->isEdited();
+};
+
+
+bool SampleChannel::hasData() const
+{
+ return wave != nullptr;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void SampleChannel::setBegin(int f)
+{
+ if (f < 0)
+ begin = 0;
+ else
+ if (f > wave->getSize())
+ begin = wave->getSize();
+ else
+ if (f >= end)
+ begin = end - 1;
+ else
+ begin = f;
+
+ tracker = begin;
+ trackerPreview = begin;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void SampleChannel::setEnd(int f)
+{
+ if (f >= wave->getSize())
+ end = wave->getSize() - 1;
+ else
+ if (f <= begin)
+ end = begin + 1;
+ else
+ end = f;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+int SampleChannel::getBegin() const { return begin; }
+int SampleChannel::getEnd() const { return end; }
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void SampleChannel::setPitch(float v)
+{
+ if (v > G_MAX_PITCH)
+ pitch = G_MAX_PITCH;
+ else
+ if (v < G_MIN_PITCH)
+ pitch = G_MIN_PITCH;
+ else
+ pitch = v;
+
+// ???? /* 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() const { return pitch; }
+
+
+/* -------------------------------------------------------------------------- */
+
+
+int SampleChannel::getPosition() const
+{
+ if (status != ChannelStatus::EMPTY &&
+ status != ChannelStatus::MISSING &&
+ status != ChannelStatus::OFF)
+ return tracker - begin;
+ else
+ return -1;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+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() const
+{
+ return boost;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void SampleChannel::empty()
+{
+ status = ChannelStatus::EMPTY;
+ begin = 0;
+ end = 0;
+ tracker = 0;
+ volume = G_DEFAULT_VOL;
+ boost = G_DEFAULT_BOOST;
+ hasActions = false;
+ delete wave;
+ wave = nullptr;
+ sendMidiLstatus();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void SampleChannel::pushWave(Wave* w)
+{
+ status = ChannelStatus::OFF;
+ wave = w;
+ begin = 0;
+ end = wave->getSize() - 1;
+ name = wave->getBasename();
+ sendMidiLstatus();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+bool SampleChannel::canInputRec()
+{
+ return wave == nullptr && armed;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+int SampleChannel::fillBuffer(giada::m::AudioBuffer& dest, int start, int offset)
+{
+ if (pitch == 1.0) return fillBufferCopy(dest, start, offset);
+ else return fillBufferResampled(dest, start, offset);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+int SampleChannel::fillBufferResampled(giada::m::AudioBuffer& dest, int start, int offset)
+{
+ rsmp_data.data_in = wave->getFrame(start); // Source data
+ rsmp_data.input_frames = end - start; // How many readable frames
+ rsmp_data.data_out = dest[offset]; // Destination (processed data)
+ rsmp_data.output_frames = dest.countFrames() - offset; // How many frames to process
+ rsmp_data.end_of_input = false;
+ rsmp_data.src_ratio = 1 / pitch;
+
+ src_process(rsmp_state, &rsmp_data);
+
+ return rsmp_data.input_frames_used; // Returns used frames
+}
+
+/* -------------------------------------------------------------------------- */
+
+
+int SampleChannel::fillBufferCopy(giada::m::AudioBuffer& dest, int start, int offset)
+{
+ int used = dest.countFrames() - offset;
+ if (used + start > wave->getSize())
+ used = wave->getSize() - start;
+
+ dest.copyData(wave->getFrame(start), used, offset);
+
+ return used;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+bool SampleChannel::isAnyLoopMode() const
+{
+ return mode == ChannelMode::LOOP_BASIC || mode == ChannelMode::LOOP_ONCE ||
+ mode == ChannelMode::LOOP_REPEAT || mode == ChannelMode::LOOP_ONCE_BAR;
+}
+
+
+bool SampleChannel::isAnySingleMode() const
+{
+ return !isAnyLoopMode();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+bool SampleChannel::isOnLastFrame() const
+{
+ return tracker >= end;
+}
\ No newline at end of file
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef G_SAMPLE_CHANNEL_H
+#define G_SAMPLE_CHANNEL_H
+
+
+#include <functional>
+#include <samplerate.h>
+#include "types.h"
+#include "channel.h"
+
+
+class Patch;
+class Wave;
+
+
+class SampleChannel : public Channel
+{
+public:
+
+ SampleChannel(bool inputMonitor, int bufferSize);
+ ~SampleChannel();
+
+ void copy(const Channel* src, pthread_mutex_t* pluginMutex) override;
+ void prepareBuffer(bool running) override;
+ void parseEvents(giada::m::mixer::FrameEvents fe) override;
+ void process(giada::m::AudioBuffer& out, const giada::m::AudioBuffer& in,
+ bool audible, bool running) override;
+ void readPatch(const std::string& basePath, int i) override;
+ void writePatch(int i, bool isProject) override;
+
+ void start(int frame, bool doQuantize, int velocity) override;
+ void stop() override;
+ void kill(int frame) override;
+ bool recordStart(bool canQuantize) override;
+ bool recordKill() override;
+ void recordStop() override;
+ void setMute(bool value) override;
+ void startReadingActions(bool treatRecsAsLoops, bool recsStopOnChanHalt) override;
+ void stopReadingActions(bool running, bool treatRecsAsLoops,
+ bool recsStopOnChanHalt) override;
+ void empty() override;
+ void stopBySeq(bool chansStopOnSeqHalt) override;
+ void rewindBySeq() override;
+ bool canInputRec() override;
+ void stopInputRec(int globalFrame) override;
+ bool hasLogicalData() const override;
+ bool hasEditedData() const override;
+ bool hasData() const override;
+
+ float getBoost() const;
+ int getBegin() const;
+ int getEnd() const;
+ float getPitch() const;
+ bool isAnyLoopMode() const;
+ bool isAnySingleMode() const;
+ bool isOnLastFrame() const;
+
+ /* getPosition
+ Returns the position of an active sample. If EMPTY o MISSING returns -1. */
+
+ int getPosition() const;
+
+ /* fillBuffer
+ Fills 'dest' buffer at point 'offset' with Wave data taken from 'start'.
+ Returns how many frames have been used from the original Wave data. It also
+ resamples data if pitch != 1.0f. */
+
+ int fillBuffer(giada::m::AudioBuffer& dest, int start, int offset);
+
+ /* pushWave
+ Adds a new wave to an existing channel. */
+
+ void pushWave(Wave* w);
+
+ void setPitch(float v);
+ void setBegin(int f);
+ void setEnd(int f);
+ void setBoost(float v);
+
+ void setReadActions(bool v, bool recsStopOnChanHalt);
+
+ /* onPreviewEnd
+ A callback fired when audio preview ends. */
+
+ std::function<void()> onPreviewEnd;
+
+ /* bufferPreview
+ Extra buffer for audio preview. */
+
+ giada::m::AudioBuffer bufferPreview;
+
+ giada::ChannelMode mode;
+
+ Wave* wave;
+ int tracker; // chan position
+ int trackerPreview; // chan position for audio preview
+ int shift;
+ bool qWait; // quantizer wait
+ bool inputMonitor;
+ float boost;
+ float pitch;
+
+ /* begin, end
+ Begin/end point to read wave data from/to. */
+
+ int begin;
+ int end;
+
+ /* midi stuff */
+
+ bool midiInVeloAsVol;
+ uint32_t midiInReadActions;
+ uint32_t midiInPitch;
+
+private:
+
+ /* rsmp_state, rsmp_data
+ Structs from libsamplerate. */
+
+ SRC_STATE* rsmp_state;
+ SRC_DATA rsmp_data;
+
+ int fillBufferResampled(giada::m::AudioBuffer& dest, int start, int offset);
+ int fillBufferCopy (giada::m::AudioBuffer& dest, int start, int offset);
+};
+
+#endif
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <cassert>
+#include "../utils/math.h"
+#include "const.h"
+#include "pluginHost.h"
+#include "sampleChannel.h"
+#include "sampleChannelProc.h"
+
+
+namespace giada {
+namespace m {
+namespace sampleChannelProc
+{
+namespace
+{
+void rewind_(SampleChannel* ch, int localFrame)
+{
+ ch->tracker = ch->begin;
+ ch->qWait = false; // Was in qWait mode? Reset occured, no more qWait now.
+
+ /* On rewind, if channel is playing fill again buffer to create something like
+ this:
+ v-------------- localFrame
+ [old data-----]*[new data--] */
+
+ if (localFrame > 0 && ch->isPlaying())
+ ch->tracker += ch->fillBuffer(ch->buffer, ch->tracker, localFrame);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+/* quantize
+Starts channel according to quantizer. */
+
+void quantize_(SampleChannel* ch, int localFrame, bool quantoPassed)
+{
+ /* Skip if LOOP_ANY, not in quantizer-wait mode or still waiting for the
+ quantization time to end. */
+
+ if (ch->isAnyLoopMode() || !ch->qWait || !quantoPassed)
+ return;
+
+ switch (ch->status) {
+ case ChannelStatus::OFF:
+ ch->status = ChannelStatus::PLAY;
+ ch->qWait = false;
+ ch->tracker += ch->fillBuffer(ch->buffer, ch->tracker, localFrame);
+ ch->sendMidiLstatus();
+ break;
+
+ default:
+ rewind_(ch, localFrame);
+ break;
+ }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+/* onBar
+Things to do when the sequencer is on a bar. */
+
+void onBar_(SampleChannel* ch, int localFrame)
+{
+ switch (ch->status) {
+ case ChannelStatus::PLAY:
+ if (ch->mode == ChannelMode::LOOP_REPEAT)
+ rewind_(ch, localFrame);
+ break;
+
+ case ChannelStatus::WAIT:
+ if (ch->mode == ChannelMode::LOOP_ONCE_BAR) {
+ ch->status = ChannelStatus::PLAY;
+ ch->tracker += ch->fillBuffer(ch->buffer, ch->tracker, localFrame);
+ ch->sendMidiLstatus();
+ }
+ break;
+
+ default: break;
+ }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+/* onFirstBeat
+Things to do when the sequencer is on the first beat. */
+
+void onFirstBeat_(SampleChannel* ch, int localFrame)
+{
+ if (!ch->hasData())
+ return;
+
+ switch (ch->status) {
+ case ChannelStatus::PLAY:
+ if (ch->isAnyLoopMode())
+ rewind_(ch, localFrame);
+ break;
+
+ case ChannelStatus::WAIT:
+ ch->status = ChannelStatus::PLAY;
+ ch->tracker += ch->fillBuffer(ch->buffer, ch->tracker, localFrame);
+ ch->sendMidiLstatus();
+ break;
+
+ case ChannelStatus::ENDING:
+ if (ch->isAnyLoopMode())
+ kill(ch, localFrame);
+
+ default: break;
+ }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+/* onLastFrame
+Things to do when the sample has reached the end (i.e. last frame). Called by
+prepareBuffer(). */
+
+void onLastFrame_(SampleChannel* ch, int localFrame, bool running)
+{
+ switch (ch->status) {
+ case ChannelStatus::PLAY:
+ /* Stop LOOP_* when the sequencer is off, or SINGLE_* except for
+ SINGLE_ENDLESS, which runs forever unless it's in ENDING mode. */
+ if ((ch->mode == ChannelMode::SINGLE_BASIC ||
+ ch->mode == ChannelMode::SINGLE_PRESS ||
+ ch->mode == ChannelMode::SINGLE_RETRIG) ||
+ (ch->isAnyLoopMode() && !running))
+ ch->status = ChannelStatus::OFF;
+ ch->sendMidiLstatus();
+ break;
+
+ case ChannelStatus::ENDING:
+ /* LOOP_ONCE or LOOP_ONCE_BAR: if ending (i.e. the user requested their
+ termination), stop 'em. Let them wait otherwise. */
+ if (ch->mode == ChannelMode::LOOP_ONCE ||
+ ch->mode == ChannelMode::LOOP_ONCE_BAR)
+ ch->status = ChannelStatus::WAIT;
+ else {
+ ch->status = ChannelStatus::OFF;
+ ch->sendMidiLstatus();
+ }
+ break;
+
+ default: break;
+ }
+ rewind_(ch, localFrame);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void processData_(SampleChannel* ch, m::AudioBuffer& out, const m::AudioBuffer& in,
+ bool running)
+{
+ assert(out.countSamples() == ch->buffer.countSamples());
+ assert(in.countSamples() == ch->buffer.countSamples());
+
+ /* If armed and input buffer is not empty (i.e. input device available) and
+ input monitor is on, copy input buffer to channel buffer: this enables the
+ input monitoring. The channel buffer will be overwritten later on by
+ pluginHost::processStack, so that you would record "clean" audio
+ (i.e. not plugin-processed). */
+
+ if (ch->armed && in.isAllocd() && ch->inputMonitor) {
+ for (int i=0; i<ch->buffer.countFrames(); i++)
+ for (int j=0; j<ch->buffer.countChannels(); j++)
+ ch->buffer[i][j] += in[i][j]; // add, don't overwrite
+ }
+
+#ifdef WITH_VST
+ pluginHost::processStack(ch->buffer, pluginHost::CHANNEL, ch);
+#endif
+
+ for (int i=0; i<out.countFrames(); i++) {
+ if (running)
+ ch->calcVolumeEnvelope();
+ if (!ch->mute)
+ for (int j=0; j<out.countChannels(); j++)
+ out[i][j] += ch->buffer[i][j] * ch->volume * ch->volume_i * ch->calcPanning(j) * ch->boost;
+ }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void processPreview_(SampleChannel* ch, m::AudioBuffer& out)
+{
+ ch->bufferPreview.clear();
+
+ /* If the tracker exceedes the end point and preview is looped, split the
+ rendering as in SampleChannel::reset(). */
+
+ if (ch->trackerPreview + ch->bufferPreview.countFrames() >= ch->end) {
+ int offset = ch->end - ch->trackerPreview;
+ ch->trackerPreview += ch->fillBuffer(ch->bufferPreview, ch->trackerPreview, 0);
+ ch->trackerPreview = ch->begin;
+ if (ch->previewMode == PreviewMode::LOOP)
+ ch->trackerPreview += ch->fillBuffer(ch->bufferPreview, ch->begin, offset);
+ else
+ if (ch->previewMode == PreviewMode::NORMAL) {
+ ch->previewMode = PreviewMode::NONE;
+ if (ch->onPreviewEnd)
+ ch->onPreviewEnd();
+ }
+ }
+ else
+ ch->trackerPreview += ch->fillBuffer(ch->bufferPreview, ch->trackerPreview, 0);
+
+ for (int i=0; i<out.countFrames(); i++)
+ for (int j=0; j<out.countChannels(); j++)
+ out[i][j] += ch->bufferPreview[i][j] * ch->volume * ch->calcPanning(j) * ch->boost;
+}
+}; // {anonymous}
+
+
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+
+
+void kill(SampleChannel* ch, int localFrame)
+{
+ switch (ch->status) {
+ case ChannelStatus::WAIT:
+ case ChannelStatus::PLAY:
+ case ChannelStatus::ENDING:
+ /* Clear data in range [localFrame, (buffer.size)) if the kill event
+ occurs in the middle of the buffer. */
+ if (localFrame != 0)
+ ch->buffer.clear(localFrame);
+ ch->status = ChannelStatus::OFF;
+ ch->sendMidiLstatus();
+ rewind_(ch, localFrame);
+ break;
+
+ default: break;
+ }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void stop(SampleChannel* ch)
+{
+ switch (ch->status) {
+ case ChannelStatus::PLAY:
+ if (ch->mode == ChannelMode::SINGLE_PRESS)
+ kill(ch, 0);
+ break;
+
+ default:
+ /* If quantizing, stop a SINGLE_PRESS immediately. */
+ if (ch->mode == ChannelMode::SINGLE_PRESS && ch->qWait)
+ ch->qWait = false;
+ break;
+ }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void stopInputRec(SampleChannel* ch, int globalFrame)
+{
+ /* 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 global frame, not at the
+ next first beat. */
+ if (ch->isAnyLoopMode() && ch->status == ChannelStatus::OFF && ch->armed) {
+ ch->status = ChannelStatus::PLAY;
+ ch->tracker = globalFrame;
+ }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void stopBySeq(SampleChannel* ch, bool chansStopOnSeqHalt)
+{
+ switch (ch->status) {
+ case ChannelStatus::WAIT:
+ /* Loop-mode channels in wait status get stopped right away. */
+ if (ch->isAnyLoopMode())
+ ch->status = ChannelStatus::OFF;
+ break;
+
+ case ChannelStatus::PLAY:
+ /* Kill samples if a) chansStopOnSeqHalt == true (run the sample to end
+ otherwise); b) when a channel is reading (and playing) actions. */
+ if (chansStopOnSeqHalt)
+ if (ch->isAnyLoopMode() || ch->isReadingActions())
+ kill(ch, 0);
+ break;
+
+ default: break;
+ }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void rewindBySeq(SampleChannel* ch)
+{
+ /* Rewind LOOP_ANY or SINGLE_ANY only if it's in read-record-mode. Rewind by
+ sequencer is a user-generated event, it always occurs on local frame 0. */
+
+ if (ch->hasData()) {
+ if ((ch->isAnyLoopMode()) || (ch->recStatus == ChannelStatus::PLAY && (ch->isAnySingleMode())))
+ rewind_(ch, 0);
+ }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void setMute(SampleChannel* ch, bool value)
+{
+ ch->mute = value;
+ ch->sendMidiLmute();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void start(SampleChannel* ch, int localFrame, bool doQuantize, int velocity)
+{
+ /* For one-shot modes, velocity drives the internal volume. */
+ if (velocity != 0) {
+ if (ch->isAnySingleMode() && ch->midiInVeloAsVol)
+ ch->volume_i = u::math::map<int, float>(velocity, 0, G_MAX_VELOCITY, 0.0, 1.0);
+ }
+
+ switch (ch->status) {
+ case ChannelStatus::OFF:
+ if (ch->isAnyLoopMode()) {
+ ch->status = ChannelStatus::WAIT;
+ ch->sendMidiLstatus();
+ }
+ else {
+ if (doQuantize)
+ ch->qWait = true;
+ else {
+ ch->status = ChannelStatus::PLAY;
+ ch->sendMidiLstatus();
+ }
+ }
+ break;
+
+ case ChannelStatus::PLAY:
+ if (ch->mode == ChannelMode::SINGLE_RETRIG) {
+ if (doQuantize)
+ ch->qWait = true;
+ else
+ rewind_(ch, localFrame);
+ }
+ else
+ if (ch->isAnyLoopMode() || ch->mode == ChannelMode::SINGLE_ENDLESS) {
+ ch->status = ChannelStatus::ENDING;
+ ch->sendMidiLstatus();
+ }
+ else
+ if (ch->mode == ChannelMode::SINGLE_BASIC) {
+ rewind_(ch, localFrame);
+ ch->status = ChannelStatus::OFF;
+ ch->sendMidiLstatus();
+ }
+ break;
+
+ case ChannelStatus::WAIT:
+ ch->status = ChannelStatus::OFF;
+ ch->sendMidiLstatus();
+ break;
+
+ case ChannelStatus::ENDING:
+ ch->status = ChannelStatus::PLAY;
+ ch->sendMidiLstatus();
+ break;
+
+ default: break;
+ }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void prepareBuffer(SampleChannel* ch, bool running)
+{
+ if (!ch->hasData())
+ return;
+ ch->buffer.clear();
+ if (ch->isPlaying()) {
+ int framesUsed = ch->fillBuffer(ch->buffer, ch->tracker, 0);
+ ch->tracker += framesUsed;
+ if (ch->isOnLastFrame())
+ onLastFrame_(ch, framesUsed * (1 / ch->pitch), running);
+ }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void parseEvents(SampleChannel* ch, mixer::FrameEvents fe)
+{
+ quantize_(ch, fe.frameLocal, fe.quantoPassed);
+ if (fe.onBar)
+ onBar_(ch, fe.frameLocal);
+ if (fe.onFirstBeat)
+ onFirstBeat_(ch, fe.frameLocal);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void process(SampleChannel* ch, m::AudioBuffer& out, const m::AudioBuffer& in,
+ bool audible, bool running)
+{
+ if (audible)
+ processData_(ch, out, in, running);
+
+ if (ch->isPreview())
+ processPreview_(ch, out);
+}
+}}};
\ No newline at end of file
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef G_SAMPLE_CHANNEL_PROC_H
+#define G_SAMPLE_CHANNEL_PROC_H
+
+
+#include "mixer.h"
+#include "audioBuffer.h"
+#include "types.h"
+
+
+class SampleChannel;
+
+
+namespace giada {
+namespace m {
+namespace sampleChannelProc
+{
+/**/
+void prepareBuffer(SampleChannel* ch, bool running);
+
+/* parseEvents
+Parses events gathered by Mixer::masterPlay(). */
+
+void parseEvents(SampleChannel* ch, mixer::FrameEvents ev);
+
+/**/
+void process(SampleChannel* ch, giada::m::AudioBuffer& out,
+ const giada::m::AudioBuffer& in, bool audible, bool running);
+
+/* kill
+Stops a channel abruptly. */
+
+void kill(SampleChannel* ch, int localFrame);
+
+/* stop
+Stops a channel normally (via key or MIDI). */
+
+void stop(SampleChannel* ch);
+
+/* stopInputRec
+Prepare a channel for playing when the input recording is done. */
+
+void stopInputRec(SampleChannel* ch, int globalFrame);
+
+/* stopBySeq
+Stops a channel when the stop button on main transport is pressed. */
+
+void stopBySeq(SampleChannel* ch, bool chansStopOnSeqHalt);
+
+/* rewind
+Rewinds channel when rewind button on main transport is pressed. */
+
+void rewindBySeq(SampleChannel* ch);
+
+/* start
+Starts a channel. doQuantize = false (don't quantize) when Mixer is reading
+actions from Recorder. */
+
+void start(SampleChannel* ch, int localFrame, bool doQuantize, int velocity);
+
+void setMute(SampleChannel* ch, bool value);
+}}};
+
+
+#endif
\ No newline at end of file
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <cassert>
+#include "const.h"
+#include "conf.h"
+#include "clock.h"
+#include "kernelAudio.h"
+#include "sampleChannel.h"
+#include "sampleChannelRec.h"
+
+
+namespace giada {
+namespace m {
+namespace sampleChannelRec
+{
+namespace
+{
+/* onFirstBeat
+Things to do when the sequencer is on the first beat. */
+
+void onFirstBeat_(SampleChannel* ch, bool recsStopOnChanHalt)
+{
+ if (ch->wave == nullptr)
+ return;
+
+ switch (ch->recStatus) {
+ case ChannelStatus::ENDING:
+ ch->recStatus = ChannelStatus::OFF;
+ setReadActions(ch, false, recsStopOnChanHalt); // rec stop
+ break;
+
+ case ChannelStatus::WAIT:
+ ch->recStatus = ChannelStatus::PLAY;
+ setReadActions(ch, true, recsStopOnChanHalt); // rec start
+ break;
+
+ default: break;
+ }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+bool recorderCanRec_(SampleChannel* ch)
+{
+ return recorder::canRec(ch, clock::isRunning(), mixer::recording);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+/* calcVolumeEnv
+Computes any changes in volume done via envelope tool. */
+
+void calcVolumeEnv_(SampleChannel* ch, int globalFrame)
+{
+ /* 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(ch->index, G_ACTION_VOLUME, globalFrame, &a0);
+
+ assert(res != 0);
+
+ /* 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(ch->index, G_ACTION_VOLUME, globalFrame, &a1);
+ if (res == -1)
+ res = recorder::getAction(ch->index, G_ACTION_VOLUME, 0, &a1);
+
+ assert(res != -2);
+
+ ch->volume_i = a0->fValue;
+ ch->volume_d = ((a1->fValue - a0->fValue) / (a1->frame - a0->frame)) * 1.003f;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void parseAction_(SampleChannel* ch, const recorder::action* a, int localFrame,
+ int globalFrame)
+{
+ if (!ch->readActions)
+ return;
+
+ switch (a->type) {
+ case G_ACTION_KEYPRESS:
+ if (ch->isAnySingleMode()) {
+ ch->start(localFrame, false, 0);
+ /* This is not a user-generated event, so fill the first chunk of buffer.
+ Then, sampleChannelProc::prepareBuffer will take care of filling the
+ subsequent buffers from the next cycle on. */
+ if (ch->status == ChannelStatus::PLAY)
+ ch->tracker += ch->fillBuffer(ch->buffer, ch->tracker, localFrame);
+ }
+ break;
+ case G_ACTION_KEYREL:
+ if (ch->isAnySingleMode())
+ ch->stop();
+ break;
+ case G_ACTION_KILL:
+ if (ch->isAnySingleMode())
+ ch->kill(localFrame);
+ break;
+ case G_ACTION_VOLUME:
+ calcVolumeEnv_(ch, globalFrame);
+ break;
+ }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void recordKeyPressAction_(SampleChannel* ch)
+{
+ if (!recorderCanRec_(ch))
+ return;
+
+ /* SINGLE_PRESS mode needs overdub. Also, disable reading actions while
+ overdubbing. */
+ if (ch->mode == ChannelMode::SINGLE_PRESS) {
+ recorder::startOverdub(ch->index, G_ACTION_KEYS, clock::getCurrentFrame(),
+ kernelAudio::getRealBufSize());
+ ch->readActions = false;
+ }
+ else
+ recorder::rec(ch->index, G_ACTION_KEYPRESS, clock::getCurrentFrame());
+ ch->hasActions = true;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void quantize_(SampleChannel* ch, bool quantoPassed)
+{
+ /* Skip if LOOP_ANY or not in quantizer-wait mode. Otherwise the quantize wait
+ has expired: record the keypress. */
+
+ if (ch->isAnyLoopMode() || !ch->qWait || !quantoPassed)
+ return;
+ recordKeyPressAction_(ch);
+}
+}; // {anonymous}
+
+
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+
+
+void parseEvents(SampleChannel* ch, mixer::FrameEvents fe)
+{
+ quantize_(ch, fe.quantoPassed);
+ if (fe.onFirstBeat)
+ onFirstBeat_(ch, conf::recsStopOnChanHalt);
+ for (const recorder::action* action : fe.actions)
+ if (action->chan == ch->index)
+ parseAction_(ch, action, fe.frameLocal, fe.frameGlobal);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+bool recordStart(SampleChannel* ch, bool canQuantize)
+{
+ /* Record a 'start' event if the quantizer is off, otherwise let mixer to
+ handle it when a quantoWait has passed (see quantize_()). Also skip if
+ channel is in any loop mode, where KEYPRESS and KEYREL are meaningless. */
+
+ if (!canQuantize && !ch->isAnyLoopMode() && recorderCanRec_(ch))
+ {
+ recordKeyPressAction_(ch);
+
+ /* Why return here? You record an action and then you call ch->start:
+ 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.
+ The result: Mixer plays the channel (due to the new action) but the code
+ in the switch in start() kills it right away (because the sample is playing).
+ Fix: start channel only if you are not recording anything, i.e. let
+ Mixer play it. */
+
+ if (ch->readActions)
+ return false;
+ }
+ return true;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+bool recordKill(SampleChannel* ch)
+{
+ /* Don't record G_ACTION_KILL actions for LOOP channels. */
+ if (recorderCanRec_(ch) && !ch->isAnyLoopMode()) {
+ recorder::rec(ch->index, G_ACTION_KILL, clock::getCurrentFrame());
+ ch->hasActions = true;
+ }
+ return true;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void recordStop(SampleChannel* ch)
+{
+ /* Record a stop event only if channel is SINGLE_PRESS. For any other mode
+ the stop event is meaningless. */
+ if (recorderCanRec_(ch) && ch->mode == ChannelMode::SINGLE_PRESS) {
+ recorder::stopOverdub(clock::getCurrentFrame(), clock::getFramesInLoop(),
+ &mixer::mutex);
+ }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void setReadActions(SampleChannel* ch, bool v, bool recsStopOnChanHalt)
+{
+ ch->readActions = v;
+ if (!ch->readActions && recsStopOnChanHalt)
+ ch->kill(0); // FIXME - wrong frame value
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void startReadingActions(SampleChannel* ch, bool treatRecsAsLoops, bool recsStopOnChanHalt)
+{
+ if (treatRecsAsLoops)
+ ch->recStatus = ChannelStatus::WAIT;
+ else
+ setReadActions(ch, true, recsStopOnChanHalt);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void stopReadingActions(SampleChannel* ch, bool isClockRunning, bool treatRecsAsLoops,
+ bool recsStopOnChanHalt)
+{
+ /* First of all, if the clock 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 (!isClockRunning) {
+ ch->recStatus = ChannelStatus::OFF;
+ setReadActions(ch, false, false);
+ }
+ else
+ if (ch->recStatus == ChannelStatus::WAIT)
+ ch->recStatus = ChannelStatus::OFF;
+ else
+ if (ch->recStatus == ChannelStatus::ENDING)
+ ch->recStatus = ChannelStatus::PLAY;
+ else
+ if (treatRecsAsLoops)
+ ch->recStatus = ChannelStatus::ENDING;
+ else
+ setReadActions(ch, false, recsStopOnChanHalt);
+}
+}}};
\ No newline at end of file
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef G_SAMPLE_CHANNEL_REC_H
+#define G_SAMPLE_CHANNEL_REC_H
+
+
+class SampleChannel;
+
+
+namespace giada {
+namespace m {
+namespace sampleChannelRec
+{
+void parseEvents(SampleChannel* ch, mixer::FrameEvents fe);
+
+/* recordStart
+Records a G_ACTION_KEYPRESS if capable of. Returns true if a start() call can
+be performed. */
+
+bool recordStart(SampleChannel* ch, bool doQuantize);
+
+/* recordKill
+Records a G_ACTION_KILL if capable of. Returns true if a kill() call can
+be performed. */
+
+bool recordKill(SampleChannel* ch);
+
+/* recordStop
+Ends overdub mode SINGLE_PRESS channels. */
+
+void recordStop(SampleChannel* ch);
+
+/* setReadActions
+If enabled (v == true), Recorder will read actions from channel 'ch'. If
+recsStopOnChanHalt == true and v == false, will also kill the channel. */
+
+void setReadActions(SampleChannel* ch, bool v, bool recsStopOnChanHalt);
+
+void startReadingActions(SampleChannel* ch, bool treatRecsAsLoops,
+ bool recsStopOnChanHalt);
+void stopReadingActions(SampleChannel* ch, bool isClockRunning,
+ bool treatRecsAsLoops, bool recsStopOnChanHalt);
+}}};
+
+
+#endif
\ No newline at end of file
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <string>
+#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::
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef G_STORAGER_H
+#define G_STORAGER_H
+
+
+#include <jansson.h>
+
+
+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
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef G_TYPES_H
+#define G_TYPES_H
+
+
+namespace giada
+{
+using Pixel = int;
+using Frame = int;
+
+
+enum class ChannelType : int
+{
+ SAMPLE = 1, MIDI
+};
+
+
+enum class ChannelStatus : int
+{
+ ENDING = 1, WAIT, PLAY, OFF, EMPTY, MISSING, WRONG
+};
+
+
+enum class ChannelMode : int
+{
+ LOOP_BASIC = 1, LOOP_ONCE, LOOP_REPEAT, LOOP_ONCE_BAR,
+ SINGLE_BASIC, SINGLE_PRESS, SINGLE_RETRIG, SINGLE_ENDLESS
+};
+
+
+enum class PreviewMode : int { NONE = 0, NORMAL, LOOP };
+enum class EventType : int { AUTO = 0, MANUAL };
+};
+
+
+#endif
\ No newline at end of file
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <cassert>
+#include <cstring> // 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_rate (0),
+ m_bits (0),
+ m_logical(false),
+ m_edited (false)
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+float* Wave::operator [](int offset) const
+{
+ return buffer[offset];
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+Wave::Wave(const Wave& other)
+: 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)
+{
+ buffer.alloc(other.getSize(), other.getChannels());
+ buffer.copyData(other.getFrame(0), other.getSize(), other.getChannels());
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void Wave::alloc(int size, int channels, int rate, int bits, const std::string& path)
+{
+ buffer.alloc(size, channels);
+ m_rate = rate;
+ m_bits = bits;
+ m_path = path;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+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 buffer.countChannels(); }
+std::string Wave::getPath() const { return m_path; }
+int Wave::getSize() const { return buffer.countFrames(); }
+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 buffer.countFrames() / m_rate;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+std::string Wave::getExtension() const
+{
+ return gu_getExt(m_path);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+float* Wave::getFrame(int f) const
+{
+ return buffer[f];
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void Wave::setRate(int v) { m_rate = 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::copyData(float* data, int frames, int offset)
+{
+ buffer.copyData(data, frames, offset);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void Wave::moveData(giada::m::AudioBuffer& b)
+{
+ buffer.moveData(b);
+}
\ No newline at end of file
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef G_WAVE_H
+#define G_WAVE_H
+
+
+#include <sndfile.h>
+#include <string>
+#include "const.h"
+#include "audioBuffer.h"
+
+
+class Wave
+{
+public:
+
+ Wave();
+ Wave(const Wave& other);
+
+ float* operator [](int offset) const;
+
+ /* getFrame
+ Works like operator []. See AudioBuffer for reference. */
+
+ float* getFrame(int f) const;
+
+ std::string getBasename(bool ext=false) const;
+ std::string getExtension() const;
+ int getRate() const;
+ int getChannels() const;
+ std::string getPath() const;
+ int getBits() const;
+ int getSize() const; // in frames
+ int getDuration() const;
+ bool isLogical() const;
+ bool isEdited() const;
+
+ /* 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);
+
+ void setRate(int v);
+ void setLogical(bool l);
+ void setEdited(bool e);
+
+ /* moveData
+ Moves data held by 'b' into this buffer. Then 'b' becomes an empty buffer. */
+
+ void moveData(giada::m::AudioBuffer& b);
+
+ /* copyData
+ Copies 'frames' frames from the new 'data' into m_data, starting from frame
+ 'offset'. It takes for granted that the new data contains the same number of
+ channels than m_channels. */
+
+ void copyData(float* data, int frames, int offset=0);
+
+ void alloc(int size, int channels, int rate, int bits, const std::string& path);
+
+private:
+
+ giada::m::AudioBuffer buffer;
+ 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
+};
+
+#endif
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <cmath>
+#include <cassert>
+#include <algorithm>
+#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)
+{
+ for (int j=0; j<w.getChannels(); j++)
+ w[i][j] *= val;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+float getPeak(const Wave& w, int a, int b)
+{
+ float peak = 0.0f;
+ float abs = 0.0f;
+ for (int i=a; i<b; i++) {
+ for (int j=0; j<w.getChannels(); j++) // Find highest value in any channel
+ abs = fabs(w[i][j]);
+ if (abs > peak)
+ peak = abs;
+ }
+ return peak;
+}
+}; // {anonymous}
+
+
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+
+
+float normalizeSoft(const 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; i<b; i++) {
+ for (int j=0; j<w.getChannels(); j++)
+ w[i][j] = w[i][j] * (1.0f / peak);
+ }
+ w.setEdited(true);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+int monoToStereo(Wave& w)
+{
+ if (w.getChannels() >= G_MAX_IO_CHANS)
+ return G_RES_OK;
+
+ AudioBuffer newData;
+ newData.alloc(w.getSize(), G_MAX_IO_CHANS);
+
+ for (int i=0; i<newData.countFrames(); i++)
+ for (int j=0; j<newData.countChannels(); j++)
+ newData[i][j] = w[i][0];
+
+ w.moveData(newData);
+
+ 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; i<b; i++) {
+ for (int j=0; j<w.getChannels(); j++)
+ w[i][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. */
+
+ int newSize = w.getSize() - (b - a);
+
+ AudioBuffer newData;
+ newData.alloc(newSize, w.getChannels());
+
+ gu_log("[wfx::cut] cutting from %d to %d\n", a, b);
+
+ for (int i=0, k=0; i<w.getSize(); i++) {
+ if (i < a || i >= b) {
+ for (int j=0; j<w.getChannels(); j++)
+ newData[k][j] = w[i][j];
+ k++;
+ }
+ }
+
+ w.moveData(newData);
+ 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;
+
+ AudioBuffer newData;
+ newData.alloc(newSize, w.getChannels());
+
+ gu_log("[wfx::trim] trimming from %d to %d (area = %d)\n", a, b, b-a);
+
+ for (int i=0; i<newData.countFrames(); i++)
+ for (int j=0; j<newData.countChannels(); j++)
+ newData[i][j] = w[i+a][j];
+
+ w.moveData(newData);
+ w.setEdited(true);
+
+ return G_RES_OK;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+int paste(const Wave& src, Wave& des, int a)
+{
+ assert(src.getChannels() == des.getChannels());
+
+ AudioBuffer newData;
+ newData.alloc(src.getSize() + des.getSize(), des.getChannels());
+
+ /* |---original data---|///paste data///|---original data---|
+ des[0, a) src[0, src.size) des[a, des.size) */
+
+ newData.copyData(des[0], a, 0);
+ newData.copyData(src[0], src.getSize(), a);
+ newData.copyData(des[a], des.getSize() - a, src.getSize() + a);
+
+ des.moveData(newData);
+ 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.getFrame(0);
+ float* end = w.getFrame(0) + (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.getFrame(0) + (a * w.getChannels());
+ float* end = w.getFrame(0) + (b * w.getChannels());
+
+ std::reverse(begin, end);
+
+ w.setEdited(true);
+}
+
+}}}; // giada::m::wfx::
\ No newline at end of file
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#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(const 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);
+
+/* paste
+Pastes Wave 'src' into Wave 'dest', starting from frame 'a'. */
+
+int paste(const 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
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <cmath>
+#include <sndfile.h>
+#include <samplerate.h>
+#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 > G_MAX_IO_CHANS) {
+ gu_log("[waveManager::create] unsupported multi-channel sample\n");
+ return G_RES_ERR_WRONG_DATA;
+ }
+
+ Wave* wave = new Wave();
+ wave->alloc(header.frames, header.channels, header.samplerate, getBits(header), path);
+
+ if (sf_readf_float(fileIn, wave->getFrame(0), header.frames) != header.frames)
+ gu_log("[waveManager::create] warning: incomplete read!\n");
+
+ sf_close(fileIn);
+
+ 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;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void createEmpty(int frames, int channels, int samplerate, const string& name,
+ Wave** out)
+{
+ Wave* wave = new Wave();
+ wave->alloc(frames, channels, samplerate, G_DEFAULT_BIT_DEPTH, name);
+
+ wave->setLogical(true);
+
+ *out = wave;
+
+ gu_log("[waveManager::createEmpty] new empty Wave created, %d frames\n",
+ wave->getSize());
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void createFromWave(const Wave* src, int a, int b, Wave** out)
+{
+ int channels = src->getChannels();
+ int frames = b - a;
+
+ Wave* wave = new Wave();
+ wave->alloc(frames, channels, src->getRate(), src->getBits(), src->getPath());
+
+ wave->copyData(src->getFrame(a), frames);
+ wave->setLogical(true);
+
+ *out = wave;
+
+ gu_log("[waveManager::createFromWave] new Wave created, %d frames\n", frames);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+int resample(Wave* w, int quality, int samplerate)
+{
+ float ratio = samplerate / (float) w->getRate();
+ int newSizeFrames = ceil(w->getSize() * ratio);
+
+ AudioBuffer newData;
+ newData.alloc(newSizeFrames, w->getChannels());
+
+ SRC_DATA src_data;
+ src_data.data_in = w->getFrame(0);
+ src_data.input_frames = w->getSize();
+ src_data.data_out = newData[0];
+ src_data.output_frames = newSizeFrames;
+ src_data.src_ratio = ratio;
+
+ gu_log("[waveManager::resample] resampling: new size=%d frames\n", newSizeFrames);
+
+ int ret = src_simple(&src_data, quality, w->getChannels());
+ if (ret != 0) {
+ gu_log("[waveManager::resample] resampling error: %s\n", src_strerror(ret));
+ return G_RES_ERR_PROCESSING;
+ }
+
+ w->moveData(newData);
+ 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->getFrame(0), 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
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef G_WAVE_MANAGER_H
+#define G_WAVE_MANAGER_H
+
+
+#include <string>
+
+
+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. */
+
+void createEmpty(int frames, int channels, int samplerate, const std::string& name,
+ Wave** out);
+
+/* createFromWave
+Creates a new Wave from an existing one, copying the data in range a - b. */
+
+void 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
+
+#endif
\ No newline at end of file
--- /dev/null
+#ifndef JUCE_APPCONFIG_H
+#define JUCE_APPCONFIG_H
+
+
+#ifdef _WIN32
+ #include <sys/types.h>
+ #include <sys/time.h>
+#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
--- /dev/null
+/************************************************************************/\r
+/*! \class RtAudio\r
+ \brief Realtime audio i/o C++ classes.\r
+\r
+ RtAudio provides a common API (Application Programming Interface)\r
+ for realtime audio input/output across Linux (native ALSA, Jack,\r
+ and OSS), Macintosh OS X (CoreAudio and Jack), and Windows\r
+ (DirectSound, ASIO and WASAPI) operating systems.\r
+\r
+ RtAudio WWW site: http://www.music.mcgill.ca/~gary/rtaudio/\r
+\r
+ RtAudio: realtime audio i/o C++ classes\r
+ Copyright (c) 2001-2016 Gary P. Scavone\r
+\r
+ Permission is hereby granted, free of charge, to any person\r
+ obtaining a copy of this software and associated documentation files\r
+ (the "Software"), to deal in the Software without restriction,\r
+ including without limitation the rights to use, copy, modify, merge,\r
+ publish, distribute, sublicense, and/or sell copies of the Software,\r
+ and to permit persons to whom the Software is furnished to do so,\r
+ subject to the following conditions:\r
+\r
+ The above copyright notice and this permission notice shall be\r
+ included in all copies or substantial portions of the Software.\r
+\r
+ Any person wishing to distribute modifications to the Software is\r
+ asked to send the modifications to the original developer so that\r
+ they can be incorporated into the canonical version. This is,\r
+ however, not a binding provision of this license.\r
+\r
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,\r
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\r
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\r
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR\r
+ ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF\r
+ CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\r
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\r
+*/\r
+/************************************************************************/\r
+\r
+// RtAudio: Version 4.1.2\r
+\r
+#include "RtAudio.h"\r
+#include <iostream>\r
+#include <cstdlib>\r
+#include <cstring>\r
+#include <climits>\r
+#include <algorithm>\r
+\r
+// Static variable definitions.\r
+const unsigned int RtApi::MAX_SAMPLE_RATES = 14;\r
+const unsigned int RtApi::SAMPLE_RATES[] = {\r
+ 4000, 5512, 8000, 9600, 11025, 16000, 22050,\r
+ 32000, 44100, 48000, 88200, 96000, 176400, 192000\r
+};\r
+\r
+#if defined(__WINDOWS_DS__) || defined(__WINDOWS_ASIO__) || defined(__WINDOWS_WASAPI__)\r
+ #define MUTEX_INITIALIZE(A) InitializeCriticalSection(A)\r
+ #define MUTEX_DESTROY(A) DeleteCriticalSection(A)\r
+ #define MUTEX_LOCK(A) EnterCriticalSection(A)\r
+ #define MUTEX_UNLOCK(A) LeaveCriticalSection(A)\r
+\r
+ #include "tchar.h"\r
+\r
+ static std::string convertCharPointerToStdString(const char *text)\r
+ {\r
+ return std::string(text);\r
+ }\r
+\r
+ static std::string convertCharPointerToStdString(const wchar_t *text)\r
+ {\r
+ int length = WideCharToMultiByte(CP_UTF8, 0, text, -1, NULL, 0, NULL, NULL);\r
+ std::string s( length-1, '\0' );\r
+ WideCharToMultiByte(CP_UTF8, 0, text, -1, &s[0], length, NULL, NULL);\r
+ return s;\r
+ }\r
+\r
+#elif defined(__LINUX_ALSA__) || defined(__LINUX_PULSE__) || defined(__UNIX_JACK__) || defined(__LINUX_OSS__) || defined(__MACOSX_CORE__)\r
+ // pthread API\r
+ #define MUTEX_INITIALIZE(A) pthread_mutex_init(A, NULL)\r
+ #define MUTEX_DESTROY(A) pthread_mutex_destroy(A)\r
+ #define MUTEX_LOCK(A) pthread_mutex_lock(A)\r
+ #define MUTEX_UNLOCK(A) pthread_mutex_unlock(A)\r
+#else\r
+ #define MUTEX_INITIALIZE(A) abs(*A) // dummy definitions\r
+ #define MUTEX_DESTROY(A) abs(*A) // dummy definitions\r
+#endif\r
+\r
+// *************************************************** //\r
+//\r
+// RtAudio definitions.\r
+//\r
+// *************************************************** //\r
+\r
+std::string RtAudio :: getVersion( void ) throw()\r
+{\r
+ return RTAUDIO_VERSION;\r
+}\r
+\r
+void RtAudio :: getCompiledApi( std::vector<RtAudio::Api> &apis ) throw()\r
+{\r
+ apis.clear();\r
+\r
+ // The order here will control the order of RtAudio's API search in\r
+ // the constructor.\r
+#if defined(__UNIX_JACK__)\r
+ apis.push_back( UNIX_JACK );\r
+#endif\r
+#if defined(__LINUX_ALSA__)\r
+ apis.push_back( LINUX_ALSA );\r
+#endif\r
+#if defined(__LINUX_PULSE__)\r
+ apis.push_back( LINUX_PULSE );\r
+#endif\r
+#if defined(__LINUX_OSS__)\r
+ apis.push_back( LINUX_OSS );\r
+#endif\r
+#if defined(__WINDOWS_ASIO__)\r
+ apis.push_back( WINDOWS_ASIO );\r
+#endif\r
+#if defined(__WINDOWS_WASAPI__)\r
+ apis.push_back( WINDOWS_WASAPI );\r
+#endif\r
+#if defined(__WINDOWS_DS__)\r
+ apis.push_back( WINDOWS_DS );\r
+#endif\r
+#if defined(__MACOSX_CORE__)\r
+ apis.push_back( MACOSX_CORE );\r
+#endif\r
+#if defined(__RTAUDIO_DUMMY__)\r
+ apis.push_back( RTAUDIO_DUMMY );\r
+#endif\r
+}\r
+\r
+void RtAudio :: openRtApi( RtAudio::Api api )\r
+{\r
+ if ( rtapi_ )\r
+ delete rtapi_;\r
+ rtapi_ = 0;\r
+\r
+#if defined(__UNIX_JACK__)\r
+ if ( api == UNIX_JACK )\r
+ rtapi_ = new RtApiJack();\r
+#endif\r
+#if defined(__LINUX_ALSA__)\r
+ if ( api == LINUX_ALSA )\r
+ rtapi_ = new RtApiAlsa();\r
+#endif\r
+#if defined(__LINUX_PULSE__)\r
+ if ( api == LINUX_PULSE )\r
+ rtapi_ = new RtApiPulse();\r
+#endif\r
+#if defined(__LINUX_OSS__)\r
+ if ( api == LINUX_OSS )\r
+ rtapi_ = new RtApiOss();\r
+#endif\r
+#if defined(__WINDOWS_ASIO__)\r
+ if ( api == WINDOWS_ASIO )\r
+ rtapi_ = new RtApiAsio();\r
+#endif\r
+#if defined(__WINDOWS_WASAPI__)\r
+ if ( api == WINDOWS_WASAPI )\r
+ rtapi_ = new RtApiWasapi();\r
+#endif\r
+#if defined(__WINDOWS_DS__)\r
+ if ( api == WINDOWS_DS )\r
+ rtapi_ = new RtApiDs();\r
+#endif\r
+#if defined(__MACOSX_CORE__)\r
+ if ( api == MACOSX_CORE )\r
+ rtapi_ = new RtApiCore();\r
+#endif\r
+#if defined(__RTAUDIO_DUMMY__)\r
+ if ( api == RTAUDIO_DUMMY )\r
+ rtapi_ = new RtApiDummy();\r
+#endif\r
+}\r
+\r
+RtAudio :: RtAudio( RtAudio::Api api )\r
+{\r
+ rtapi_ = 0;\r
+\r
+ if ( api != UNSPECIFIED ) {\r
+ // Attempt to open the specified API.\r
+ openRtApi( api );\r
+ if ( rtapi_ ) return;\r
+\r
+ // No compiled support for specified API value. Issue a debug\r
+ // warning and continue as if no API was specified.\r
+ std::cerr << "\nRtAudio: no compiled support for specified API argument!\n" << std::endl;\r
+ }\r
+\r
+ // Iterate through the compiled APIs and return as soon as we find\r
+ // one with at least one device or we reach the end of the list.\r
+ std::vector< RtAudio::Api > apis;\r
+ getCompiledApi( apis );\r
+ for ( unsigned int i=0; i<apis.size(); i++ ) {\r
+ openRtApi( apis[i] );\r
+ if ( rtapi_ && rtapi_->getDeviceCount() ) break;\r
+ }\r
+\r
+ if ( rtapi_ ) return;\r
+\r
+ // It should not be possible to get here because the preprocessor\r
+ // definition __RTAUDIO_DUMMY__ is automatically defined if no\r
+ // API-specific definitions are passed to the compiler. But just in\r
+ // case something weird happens, we'll thow an error.\r
+ std::string errorText = "\nRtAudio: no compiled API support found ... critical error!!\n\n";\r
+ throw( RtAudioError( errorText, RtAudioError::UNSPECIFIED ) );\r
+}\r
+\r
+RtAudio :: ~RtAudio() throw()\r
+{\r
+ if ( rtapi_ )\r
+ delete rtapi_;\r
+}\r
+\r
+void RtAudio :: openStream( RtAudio::StreamParameters *outputParameters,\r
+ RtAudio::StreamParameters *inputParameters,\r
+ RtAudioFormat format, unsigned int sampleRate,\r
+ unsigned int *bufferFrames,\r
+ RtAudioCallback callback, void *userData,\r
+ RtAudio::StreamOptions *options,\r
+ RtAudioErrorCallback errorCallback )\r
+{\r
+ return rtapi_->openStream( outputParameters, inputParameters, format,\r
+ sampleRate, bufferFrames, callback,\r
+ userData, options, errorCallback );\r
+}\r
+\r
+// *************************************************** //\r
+//\r
+// Public RtApi definitions (see end of file for\r
+// private or protected utility functions).\r
+//\r
+// *************************************************** //\r
+\r
+RtApi :: RtApi()\r
+{\r
+ stream_.state = STREAM_CLOSED;\r
+ stream_.mode = UNINITIALIZED;\r
+ stream_.apiHandle = 0;\r
+ stream_.userBuffer[0] = 0;\r
+ stream_.userBuffer[1] = 0;\r
+ MUTEX_INITIALIZE( &stream_.mutex );\r
+ showWarnings_ = true;\r
+ firstErrorOccurred_ = false;\r
+}\r
+\r
+RtApi :: ~RtApi()\r
+{\r
+ MUTEX_DESTROY( &stream_.mutex );\r
+}\r
+\r
+void RtApi :: openStream( RtAudio::StreamParameters *oParams,\r
+ RtAudio::StreamParameters *iParams,\r
+ RtAudioFormat format, unsigned int sampleRate,\r
+ unsigned int *bufferFrames,\r
+ RtAudioCallback callback, void *userData,\r
+ RtAudio::StreamOptions *options,\r
+ RtAudioErrorCallback errorCallback )\r
+{\r
+ if ( stream_.state != STREAM_CLOSED ) {\r
+ errorText_ = "RtApi::openStream: a stream is already open!";\r
+ error( RtAudioError::INVALID_USE );\r
+ return;\r
+ }\r
+\r
+ // Clear stream information potentially left from a previously open stream.\r
+ clearStreamInfo();\r
+\r
+ if ( oParams && oParams->nChannels < 1 ) {\r
+ errorText_ = "RtApi::openStream: a non-NULL output StreamParameters structure cannot have an nChannels value less than one.";\r
+ error( RtAudioError::INVALID_USE );\r
+ return;\r
+ }\r
+\r
+ if ( iParams && iParams->nChannels < 1 ) {\r
+ errorText_ = "RtApi::openStream: a non-NULL input StreamParameters structure cannot have an nChannels value less than one.";\r
+ error( RtAudioError::INVALID_USE );\r
+ return;\r
+ }\r
+\r
+ if ( oParams == NULL && iParams == NULL ) {\r
+ errorText_ = "RtApi::openStream: input and output StreamParameters structures are both NULL!";\r
+ error( RtAudioError::INVALID_USE );\r
+ return;\r
+ }\r
+\r
+ if ( formatBytes(format) == 0 ) {\r
+ errorText_ = "RtApi::openStream: 'format' parameter value is undefined.";\r
+ error( RtAudioError::INVALID_USE );\r
+ return;\r
+ }\r
+\r
+ unsigned int nDevices = getDeviceCount();\r
+ unsigned int oChannels = 0;\r
+ if ( oParams ) {\r
+ oChannels = oParams->nChannels;\r
+ if ( oParams->deviceId >= nDevices ) {\r
+ errorText_ = "RtApi::openStream: output device parameter value is invalid.";\r
+ error( RtAudioError::INVALID_USE );\r
+ return;\r
+ }\r
+ }\r
+\r
+ unsigned int iChannels = 0;\r
+ if ( iParams ) {\r
+ iChannels = iParams->nChannels;\r
+ if ( iParams->deviceId >= nDevices ) {\r
+ errorText_ = "RtApi::openStream: input device parameter value is invalid.";\r
+ error( RtAudioError::INVALID_USE );\r
+ return;\r
+ }\r
+ }\r
+\r
+ bool result;\r
+\r
+ if ( oChannels > 0 ) {\r
+\r
+ result = probeDeviceOpen( oParams->deviceId, OUTPUT, oChannels, oParams->firstChannel,\r
+ sampleRate, format, bufferFrames, options );\r
+ if ( result == false ) {\r
+ error( RtAudioError::SYSTEM_ERROR );\r
+ return;\r
+ }\r
+ }\r
+\r
+ if ( iChannels > 0 ) {\r
+\r
+ result = probeDeviceOpen( iParams->deviceId, INPUT, iChannels, iParams->firstChannel,\r
+ sampleRate, format, bufferFrames, options );\r
+ if ( result == false ) {\r
+ if ( oChannels > 0 ) closeStream();\r
+ error( RtAudioError::SYSTEM_ERROR );\r
+ return;\r
+ }\r
+ }\r
+\r
+ stream_.callbackInfo.callback = (void *) callback;\r
+ stream_.callbackInfo.userData = userData;\r
+ stream_.callbackInfo.errorCallback = (void *) errorCallback;\r
+\r
+ if ( options ) options->numberOfBuffers = stream_.nBuffers;\r
+ stream_.state = STREAM_STOPPED;\r
+}\r
+\r
+unsigned int RtApi :: getDefaultInputDevice( void )\r
+{\r
+ // Should be implemented in subclasses if possible.\r
+ return 0;\r
+}\r
+\r
+unsigned int RtApi :: getDefaultOutputDevice( void )\r
+{\r
+ // Should be implemented in subclasses if possible.\r
+ return 0;\r
+}\r
+\r
+void RtApi :: closeStream( void )\r
+{\r
+ // MUST be implemented in subclasses!\r
+ return;\r
+}\r
+\r
+bool RtApi :: probeDeviceOpen( unsigned int /*device*/, StreamMode /*mode*/, unsigned int /*channels*/,\r
+ unsigned int /*firstChannel*/, unsigned int /*sampleRate*/,\r
+ RtAudioFormat /*format*/, unsigned int * /*bufferSize*/,\r
+ RtAudio::StreamOptions * /*options*/ )\r
+{\r
+ // MUST be implemented in subclasses!\r
+ return FAILURE;\r
+}\r
+\r
+void RtApi :: tickStreamTime( void )\r
+{\r
+ // Subclasses that do not provide their own implementation of\r
+ // getStreamTime should call this function once per buffer I/O to\r
+ // provide basic stream time support.\r
+\r
+ stream_.streamTime += ( stream_.bufferSize * 1.0 / stream_.sampleRate );\r
+\r
+#if defined( HAVE_GETTIMEOFDAY )\r
+ gettimeofday( &stream_.lastTickTimestamp, NULL );\r
+#endif\r
+}\r
+\r
+long RtApi :: getStreamLatency( void )\r
+{\r
+ verifyStream();\r
+\r
+ long totalLatency = 0;\r
+ if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX )\r
+ totalLatency = stream_.latency[0];\r
+ if ( stream_.mode == INPUT || stream_.mode == DUPLEX )\r
+ totalLatency += stream_.latency[1];\r
+\r
+ return totalLatency;\r
+}\r
+\r
+double RtApi :: getStreamTime( void )\r
+{\r
+ verifyStream();\r
+\r
+#if defined( HAVE_GETTIMEOFDAY )\r
+ // Return a very accurate estimate of the stream time by\r
+ // adding in the elapsed time since the last tick.\r
+ struct timeval then;\r
+ struct timeval now;\r
+\r
+ if ( stream_.state != STREAM_RUNNING || stream_.streamTime == 0.0 )\r
+ return stream_.streamTime;\r
+\r
+ gettimeofday( &now, NULL );\r
+ then = stream_.lastTickTimestamp;\r
+ return stream_.streamTime +\r
+ ((now.tv_sec + 0.000001 * now.tv_usec) -\r
+ (then.tv_sec + 0.000001 * then.tv_usec));\r
+#else\r
+ return stream_.streamTime;\r
+#endif\r
+}\r
+\r
+void RtApi :: setStreamTime( double time )\r
+{\r
+ verifyStream();\r
+\r
+ if ( time >= 0.0 )\r
+ stream_.streamTime = time;\r
+}\r
+\r
+unsigned int RtApi :: getStreamSampleRate( void )\r
+{\r
+ verifyStream();\r
+\r
+ return stream_.sampleRate;\r
+}\r
+\r
+\r
+// *************************************************** //\r
+//\r
+// OS/API-specific methods.\r
+//\r
+// *************************************************** //\r
+\r
+#if defined(__MACOSX_CORE__)\r
+\r
+// The OS X CoreAudio API is designed to use a separate callback\r
+// procedure for each of its audio devices. A single RtAudio duplex\r
+// stream using two different devices is supported here, though it\r
+// cannot be guaranteed to always behave correctly because we cannot\r
+// synchronize these two callbacks.\r
+//\r
+// A property listener is installed for over/underrun information.\r
+// However, no functionality is currently provided to allow property\r
+// listeners to trigger user handlers because it is unclear what could\r
+// be done if a critical stream parameter (buffer size, sample rate,\r
+// device disconnect) notification arrived. The listeners entail\r
+// quite a bit of extra code and most likely, a user program wouldn't\r
+// be prepared for the result anyway. However, we do provide a flag\r
+// to the client callback function to inform of an over/underrun.\r
+\r
+// A structure to hold various information related to the CoreAudio API\r
+// implementation.\r
+struct CoreHandle {\r
+ AudioDeviceID id[2]; // device ids\r
+#if defined( MAC_OS_X_VERSION_10_5 ) && ( MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 )\r
+ AudioDeviceIOProcID procId[2];\r
+#endif\r
+ UInt32 iStream[2]; // device stream index (or first if using multiple)\r
+ UInt32 nStreams[2]; // number of streams to use\r
+ bool xrun[2];\r
+ char *deviceBuffer;\r
+ pthread_cond_t condition;\r
+ int drainCounter; // Tracks callback counts when draining\r
+ bool internalDrain; // Indicates if stop is initiated from callback or not.\r
+\r
+ CoreHandle()\r
+ :deviceBuffer(0), drainCounter(0), internalDrain(false) { nStreams[0] = 1; nStreams[1] = 1; id[0] = 0; id[1] = 0; xrun[0] = false; xrun[1] = false; }\r
+};\r
+\r
+RtApiCore:: RtApiCore()\r
+{\r
+#if defined( AVAILABLE_MAC_OS_X_VERSION_10_6_AND_LATER )\r
+ // This is a largely undocumented but absolutely necessary\r
+ // requirement starting with OS-X 10.6. If not called, queries and\r
+ // updates to various audio device properties are not handled\r
+ // correctly.\r
+ CFRunLoopRef theRunLoop = NULL;\r
+ AudioObjectPropertyAddress property = { kAudioHardwarePropertyRunLoop,\r
+ kAudioObjectPropertyScopeGlobal,\r
+ kAudioObjectPropertyElementMaster };\r
+ OSStatus result = AudioObjectSetPropertyData( kAudioObjectSystemObject, &property, 0, NULL, sizeof(CFRunLoopRef), &theRunLoop);\r
+ if ( result != noErr ) {\r
+ errorText_ = "RtApiCore::RtApiCore: error setting run loop property!";\r
+ error( RtAudioError::WARNING );\r
+ }\r
+#endif\r
+}\r
+\r
+RtApiCore :: ~RtApiCore()\r
+{\r
+ // The subclass destructor gets called before the base class\r
+ // destructor, so close an existing stream before deallocating\r
+ // apiDeviceId memory.\r
+ if ( stream_.state != STREAM_CLOSED ) closeStream();\r
+}\r
+\r
+unsigned int RtApiCore :: getDeviceCount( void )\r
+{\r
+ // Find out how many audio devices there are, if any.\r
+ UInt32 dataSize;\r
+ AudioObjectPropertyAddress propertyAddress = { kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster };\r
+ OSStatus result = AudioObjectGetPropertyDataSize( kAudioObjectSystemObject, &propertyAddress, 0, NULL, &dataSize );\r
+ if ( result != noErr ) {\r
+ errorText_ = "RtApiCore::getDeviceCount: OS-X error getting device info!";\r
+ error( RtAudioError::WARNING );\r
+ return 0;\r
+ }\r
+\r
+ return dataSize / sizeof( AudioDeviceID );\r
+}\r
+\r
+unsigned int RtApiCore :: getDefaultInputDevice( void )\r
+{\r
+ unsigned int nDevices = getDeviceCount();\r
+ if ( nDevices <= 1 ) return 0;\r
+\r
+ AudioDeviceID id;\r
+ UInt32 dataSize = sizeof( AudioDeviceID );\r
+ AudioObjectPropertyAddress property = { kAudioHardwarePropertyDefaultInputDevice, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster };\r
+ OSStatus result = AudioObjectGetPropertyData( kAudioObjectSystemObject, &property, 0, NULL, &dataSize, &id );\r
+ if ( result != noErr ) {\r
+ errorText_ = "RtApiCore::getDefaultInputDevice: OS-X system error getting device.";\r
+ error( RtAudioError::WARNING );\r
+ return 0;\r
+ }\r
+\r
+ dataSize *= nDevices;\r
+ AudioDeviceID deviceList[ nDevices ];\r
+ property.mSelector = kAudioHardwarePropertyDevices;\r
+ result = AudioObjectGetPropertyData( kAudioObjectSystemObject, &property, 0, NULL, &dataSize, (void *) &deviceList );\r
+ if ( result != noErr ) {\r
+ errorText_ = "RtApiCore::getDefaultInputDevice: OS-X system error getting device IDs.";\r
+ error( RtAudioError::WARNING );\r
+ return 0;\r
+ }\r
+\r
+ for ( unsigned int i=0; i<nDevices; i++ )\r
+ if ( id == deviceList[i] ) return i;\r
+\r
+ errorText_ = "RtApiCore::getDefaultInputDevice: No default device found!";\r
+ error( RtAudioError::WARNING );\r
+ return 0;\r
+}\r
+\r
+unsigned int RtApiCore :: getDefaultOutputDevice( void )\r
+{\r
+ unsigned int nDevices = getDeviceCount();\r
+ if ( nDevices <= 1 ) return 0;\r
+\r
+ AudioDeviceID id;\r
+ UInt32 dataSize = sizeof( AudioDeviceID );\r
+ AudioObjectPropertyAddress property = { kAudioHardwarePropertyDefaultOutputDevice, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster };\r
+ OSStatus result = AudioObjectGetPropertyData( kAudioObjectSystemObject, &property, 0, NULL, &dataSize, &id );\r
+ if ( result != noErr ) {\r
+ errorText_ = "RtApiCore::getDefaultOutputDevice: OS-X system error getting device.";\r
+ error( RtAudioError::WARNING );\r
+ return 0;\r
+ }\r
+\r
+ dataSize = sizeof( AudioDeviceID ) * nDevices;\r
+ AudioDeviceID deviceList[ nDevices ];\r
+ property.mSelector = kAudioHardwarePropertyDevices;\r
+ result = AudioObjectGetPropertyData( kAudioObjectSystemObject, &property, 0, NULL, &dataSize, (void *) &deviceList );\r
+ if ( result != noErr ) {\r
+ errorText_ = "RtApiCore::getDefaultOutputDevice: OS-X system error getting device IDs.";\r
+ error( RtAudioError::WARNING );\r
+ return 0;\r
+ }\r
+\r
+ for ( unsigned int i=0; i<nDevices; i++ )\r
+ if ( id == deviceList[i] ) return i;\r
+\r
+ errorText_ = "RtApiCore::getDefaultOutputDevice: No default device found!";\r
+ error( RtAudioError::WARNING );\r
+ return 0;\r
+}\r
+\r
+RtAudio::DeviceInfo RtApiCore :: getDeviceInfo( unsigned int device )\r
+{\r
+ RtAudio::DeviceInfo info;\r
+ info.probed = false;\r
+\r
+ // Get device ID\r
+ unsigned int nDevices = getDeviceCount();\r
+ if ( nDevices == 0 ) {\r
+ errorText_ = "RtApiCore::getDeviceInfo: no devices found!";\r
+ error( RtAudioError::INVALID_USE );\r
+ return info;\r
+ }\r
+\r
+ if ( device >= nDevices ) {\r
+ errorText_ = "RtApiCore::getDeviceInfo: device ID is invalid!";\r
+ error( RtAudioError::INVALID_USE );\r
+ return info;\r
+ }\r
+\r
+ AudioDeviceID deviceList[ nDevices ];\r
+ UInt32 dataSize = sizeof( AudioDeviceID ) * nDevices;\r
+ AudioObjectPropertyAddress property = { kAudioHardwarePropertyDevices,\r
+ kAudioObjectPropertyScopeGlobal,\r
+ kAudioObjectPropertyElementMaster };\r
+ OSStatus result = AudioObjectGetPropertyData( kAudioObjectSystemObject, &property,\r
+ 0, NULL, &dataSize, (void *) &deviceList );\r
+ if ( result != noErr ) {\r
+ errorText_ = "RtApiCore::getDeviceInfo: OS-X system error getting device IDs.";\r
+ error( RtAudioError::WARNING );\r
+ return info;\r
+ }\r
+\r
+ AudioDeviceID id = deviceList[ device ];\r
+\r
+ // Get the device name.\r
+ info.name.erase();\r
+ CFStringRef cfname;\r
+ dataSize = sizeof( CFStringRef );\r
+ property.mSelector = kAudioObjectPropertyManufacturer;\r
+ result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, &cfname );\r
+ if ( result != noErr ) {\r
+ errorStream_ << "RtApiCore::probeDeviceInfo: system error (" << getErrorCode( result ) << ") getting device manufacturer.";\r
+ errorText_ = errorStream_.str();\r
+ error( RtAudioError::WARNING );\r
+ return info;\r
+ }\r
+\r
+ //const char *mname = CFStringGetCStringPtr( cfname, CFStringGetSystemEncoding() );\r
+ int length = CFStringGetLength(cfname);\r
+ char *mname = (char *)malloc(length * 3 + 1);\r
+#if defined( UNICODE ) || defined( _UNICODE )\r
+ CFStringGetCString(cfname, mname, length * 3 + 1, kCFStringEncodingUTF8);\r
+#else\r
+ CFStringGetCString(cfname, mname, length * 3 + 1, CFStringGetSystemEncoding());\r
+#endif\r
+ info.name.append( (const char *)mname, strlen(mname) );\r
+ info.name.append( ": " );\r
+ CFRelease( cfname );\r
+ free(mname);\r
+\r
+ property.mSelector = kAudioObjectPropertyName;\r
+ result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, &cfname );\r
+ if ( result != noErr ) {\r
+ errorStream_ << "RtApiCore::probeDeviceInfo: system error (" << getErrorCode( result ) << ") getting device name.";\r
+ errorText_ = errorStream_.str();\r
+ error( RtAudioError::WARNING );\r
+ return info;\r
+ }\r
+\r
+ //const char *name = CFStringGetCStringPtr( cfname, CFStringGetSystemEncoding() );\r
+ length = CFStringGetLength(cfname);\r
+ char *name = (char *)malloc(length * 3 + 1);\r
+#if defined( UNICODE ) || defined( _UNICODE )\r
+ CFStringGetCString(cfname, name, length * 3 + 1, kCFStringEncodingUTF8);\r
+#else\r
+ CFStringGetCString(cfname, name, length * 3 + 1, CFStringGetSystemEncoding());\r
+#endif\r
+ info.name.append( (const char *)name, strlen(name) );\r
+ CFRelease( cfname );\r
+ free(name);\r
+\r
+ // Get the output stream "configuration".\r
+ AudioBufferList *bufferList = nil;\r
+ property.mSelector = kAudioDevicePropertyStreamConfiguration;\r
+ property.mScope = kAudioDevicePropertyScopeOutput;\r
+ // property.mElement = kAudioObjectPropertyElementWildcard;\r
+ dataSize = 0;\r
+ result = AudioObjectGetPropertyDataSize( id, &property, 0, NULL, &dataSize );\r
+ if ( result != noErr || dataSize == 0 ) {\r
+ errorStream_ << "RtApiCore::getDeviceInfo: system error (" << getErrorCode( result ) << ") getting output stream configuration info for device (" << device << ").";\r
+ errorText_ = errorStream_.str();\r
+ error( RtAudioError::WARNING );\r
+ return info;\r
+ }\r
+\r
+ // Allocate the AudioBufferList.\r
+ bufferList = (AudioBufferList *) malloc( dataSize );\r
+ if ( bufferList == NULL ) {\r
+ errorText_ = "RtApiCore::getDeviceInfo: memory error allocating output AudioBufferList.";\r
+ error( RtAudioError::WARNING );\r
+ return info;\r
+ }\r
+\r
+ result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, bufferList );\r
+ if ( result != noErr || dataSize == 0 ) {\r
+ free( bufferList );\r
+ errorStream_ << "RtApiCore::getDeviceInfo: system error (" << getErrorCode( result ) << ") getting output stream configuration for device (" << device << ").";\r
+ errorText_ = errorStream_.str();\r
+ error( RtAudioError::WARNING );\r
+ return info;\r
+ }\r
+\r
+ // Get output channel information.\r
+ unsigned int i, nStreams = bufferList->mNumberBuffers;\r
+ for ( i=0; i<nStreams; i++ )\r
+ info.outputChannels += bufferList->mBuffers[i].mNumberChannels;\r
+ free( bufferList );\r
+\r
+ // Get the input stream "configuration".\r
+ property.mScope = kAudioDevicePropertyScopeInput;\r
+ result = AudioObjectGetPropertyDataSize( id, &property, 0, NULL, &dataSize );\r
+ if ( result != noErr || dataSize == 0 ) {\r
+ errorStream_ << "RtApiCore::getDeviceInfo: system error (" << getErrorCode( result ) << ") getting input stream configuration info for device (" << device << ").";\r
+ errorText_ = errorStream_.str();\r
+ error( RtAudioError::WARNING );\r
+ return info;\r
+ }\r
+\r
+ // Allocate the AudioBufferList.\r
+ bufferList = (AudioBufferList *) malloc( dataSize );\r
+ if ( bufferList == NULL ) {\r
+ errorText_ = "RtApiCore::getDeviceInfo: memory error allocating input AudioBufferList.";\r
+ error( RtAudioError::WARNING );\r
+ return info;\r
+ }\r
+\r
+ result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, bufferList );\r
+ if (result != noErr || dataSize == 0) {\r
+ free( bufferList );\r
+ errorStream_ << "RtApiCore::getDeviceInfo: system error (" << getErrorCode( result ) << ") getting input stream configuration for device (" << device << ").";\r
+ errorText_ = errorStream_.str();\r
+ error( RtAudioError::WARNING );\r
+ return info;\r
+ }\r
+\r
+ // Get input channel information.\r
+ nStreams = bufferList->mNumberBuffers;\r
+ for ( i=0; i<nStreams; i++ )\r
+ info.inputChannels += bufferList->mBuffers[i].mNumberChannels;\r
+ free( bufferList );\r
+\r
+ // If device opens for both playback and capture, we determine the channels.\r
+ if ( info.outputChannels > 0 && info.inputChannels > 0 )\r
+ info.duplexChannels = (info.outputChannels > info.inputChannels) ? info.inputChannels : info.outputChannels;\r
+\r
+ // Probe the device sample rates.\r
+ bool isInput = false;\r
+ if ( info.outputChannels == 0 ) isInput = true;\r
+\r
+ // Determine the supported sample rates.\r
+ property.mSelector = kAudioDevicePropertyAvailableNominalSampleRates;\r
+ if ( isInput == false ) property.mScope = kAudioDevicePropertyScopeOutput;\r
+ result = AudioObjectGetPropertyDataSize( id, &property, 0, NULL, &dataSize );\r
+ if ( result != kAudioHardwareNoError || dataSize == 0 ) {\r
+ errorStream_ << "RtApiCore::getDeviceInfo: system error (" << getErrorCode( result ) << ") getting sample rate info.";\r
+ errorText_ = errorStream_.str();\r
+ error( RtAudioError::WARNING );\r
+ return info;\r
+ }\r
+\r
+ UInt32 nRanges = dataSize / sizeof( AudioValueRange );\r
+ AudioValueRange rangeList[ nRanges ];\r
+ result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, &rangeList );\r
+ if ( result != kAudioHardwareNoError ) {\r
+ errorStream_ << "RtApiCore::getDeviceInfo: system error (" << getErrorCode( result ) << ") getting sample rates.";\r
+ errorText_ = errorStream_.str();\r
+ error( RtAudioError::WARNING );\r
+ return info;\r
+ }\r
+\r
+ // The sample rate reporting mechanism is a bit of a mystery. It\r
+ // seems that it can either return individual rates or a range of\r
+ // rates. I assume that if the min / max range values are the same,\r
+ // then that represents a single supported rate and if the min / max\r
+ // range values are different, the device supports an arbitrary\r
+ // range of values (though there might be multiple ranges, so we'll\r
+ // use the most conservative range).\r
+ Float64 minimumRate = 1.0, maximumRate = 10000000000.0;\r
+ bool haveValueRange = false;\r
+ info.sampleRates.clear();\r
+ for ( UInt32 i=0; i<nRanges; i++ ) {\r
+ if ( rangeList[i].mMinimum == rangeList[i].mMaximum ) {\r
+ unsigned int tmpSr = (unsigned int) rangeList[i].mMinimum;\r
+ info.sampleRates.push_back( tmpSr );\r
+\r
+ if ( !info.preferredSampleRate || ( tmpSr <= 48000 && tmpSr > info.preferredSampleRate ) )\r
+ info.preferredSampleRate = tmpSr;\r
+\r
+ } else {\r
+ haveValueRange = true;\r
+ if ( rangeList[i].mMinimum > minimumRate ) minimumRate = rangeList[i].mMinimum;\r
+ if ( rangeList[i].mMaximum < maximumRate ) maximumRate = rangeList[i].mMaximum;\r
+ }\r
+ }\r
+\r
+ if ( haveValueRange ) {\r
+ for ( unsigned int k=0; k<MAX_SAMPLE_RATES; k++ ) {\r
+ if ( SAMPLE_RATES[k] >= (unsigned int) minimumRate && SAMPLE_RATES[k] <= (unsigned int) maximumRate ) {\r
+ info.sampleRates.push_back( SAMPLE_RATES[k] );\r
+\r
+ if ( !info.preferredSampleRate || ( SAMPLE_RATES[k] <= 48000 && SAMPLE_RATES[k] > info.preferredSampleRate ) )\r
+ info.preferredSampleRate = SAMPLE_RATES[k];\r
+ }\r
+ }\r
+ }\r
+\r
+ // Sort and remove any redundant values\r
+ std::sort( info.sampleRates.begin(), info.sampleRates.end() );\r
+ info.sampleRates.erase( unique( info.sampleRates.begin(), info.sampleRates.end() ), info.sampleRates.end() );\r
+\r
+ if ( info.sampleRates.size() == 0 ) {\r
+ errorStream_ << "RtApiCore::probeDeviceInfo: No supported sample rates found for device (" << device << ").";\r
+ errorText_ = errorStream_.str();\r
+ error( RtAudioError::WARNING );\r
+ return info;\r
+ }\r
+\r
+ // CoreAudio always uses 32-bit floating point data for PCM streams.\r
+ // Thus, any other "physical" formats supported by the device are of\r
+ // no interest to the client.\r
+ info.nativeFormats = RTAUDIO_FLOAT32;\r
+\r
+ if ( info.outputChannels > 0 )\r
+ if ( getDefaultOutputDevice() == device ) info.isDefaultOutput = true;\r
+ if ( info.inputChannels > 0 )\r
+ if ( getDefaultInputDevice() == device ) info.isDefaultInput = true;\r
+\r
+ info.probed = true;\r
+ return info;\r
+}\r
+\r
+static OSStatus callbackHandler( AudioDeviceID inDevice,\r
+ const AudioTimeStamp* /*inNow*/,\r
+ const AudioBufferList* inInputData,\r
+ const AudioTimeStamp* /*inInputTime*/,\r
+ AudioBufferList* outOutputData,\r
+ const AudioTimeStamp* /*inOutputTime*/,\r
+ void* infoPointer )\r
+{\r
+ CallbackInfo *info = (CallbackInfo *) infoPointer;\r
+\r
+ RtApiCore *object = (RtApiCore *) info->object;\r
+ if ( object->callbackEvent( inDevice, inInputData, outOutputData ) == false )\r
+ return kAudioHardwareUnspecifiedError;\r
+ else\r
+ return kAudioHardwareNoError;\r
+}\r
+\r
+static OSStatus xrunListener( AudioObjectID /*inDevice*/,\r
+ UInt32 nAddresses,\r
+ const AudioObjectPropertyAddress properties[],\r
+ void* handlePointer )\r
+{\r
+ CoreHandle *handle = (CoreHandle *) handlePointer;\r
+ for ( UInt32 i=0; i<nAddresses; i++ ) {\r
+ if ( properties[i].mSelector == kAudioDeviceProcessorOverload ) {\r
+ if ( properties[i].mScope == kAudioDevicePropertyScopeInput )\r
+ handle->xrun[1] = true;\r
+ else\r
+ handle->xrun[0] = true;\r
+ }\r
+ }\r
+\r
+ return kAudioHardwareNoError;\r
+}\r
+\r
+static OSStatus rateListener( AudioObjectID inDevice,\r
+ UInt32 /*nAddresses*/,\r
+ const AudioObjectPropertyAddress /*properties*/[],\r
+ void* ratePointer )\r
+{\r
+ Float64 *rate = (Float64 *) ratePointer;\r
+ UInt32 dataSize = sizeof( Float64 );\r
+ AudioObjectPropertyAddress property = { kAudioDevicePropertyNominalSampleRate,\r
+ kAudioObjectPropertyScopeGlobal,\r
+ kAudioObjectPropertyElementMaster };\r
+ AudioObjectGetPropertyData( inDevice, &property, 0, NULL, &dataSize, rate );\r
+ return kAudioHardwareNoError;\r
+}\r
+\r
+bool RtApiCore :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels,\r
+ unsigned int firstChannel, unsigned int sampleRate,\r
+ RtAudioFormat format, unsigned int *bufferSize,\r
+ RtAudio::StreamOptions *options )\r
+{\r
+ // Get device ID\r
+ unsigned int nDevices = getDeviceCount();\r
+ if ( nDevices == 0 ) {\r
+ // This should not happen because a check is made before this function is called.\r
+ errorText_ = "RtApiCore::probeDeviceOpen: no devices found!";\r
+ return FAILURE;\r
+ }\r
+\r
+ if ( device >= nDevices ) {\r
+ // This should not happen because a check is made before this function is called.\r
+ errorText_ = "RtApiCore::probeDeviceOpen: device ID is invalid!";\r
+ return FAILURE;\r
+ }\r
+\r
+ AudioDeviceID deviceList[ nDevices ];\r
+ UInt32 dataSize = sizeof( AudioDeviceID ) * nDevices;\r
+ AudioObjectPropertyAddress property = { kAudioHardwarePropertyDevices,\r
+ kAudioObjectPropertyScopeGlobal,\r
+ kAudioObjectPropertyElementMaster };\r
+ OSStatus result = AudioObjectGetPropertyData( kAudioObjectSystemObject, &property,\r
+ 0, NULL, &dataSize, (void *) &deviceList );\r
+ if ( result != noErr ) {\r
+ errorText_ = "RtApiCore::probeDeviceOpen: OS-X system error getting device IDs.";\r
+ return FAILURE;\r
+ }\r
+\r
+ AudioDeviceID id = deviceList[ device ];\r
+\r
+ // Setup for stream mode.\r
+ bool isInput = false;\r
+ if ( mode == INPUT ) {\r
+ isInput = true;\r
+ property.mScope = kAudioDevicePropertyScopeInput;\r
+ }\r
+ else\r
+ property.mScope = kAudioDevicePropertyScopeOutput;\r
+\r
+ // Get the stream "configuration".\r
+ AudioBufferList *bufferList = nil;\r
+ dataSize = 0;\r
+ property.mSelector = kAudioDevicePropertyStreamConfiguration;\r
+ result = AudioObjectGetPropertyDataSize( id, &property, 0, NULL, &dataSize );\r
+ if ( result != noErr || dataSize == 0 ) {\r
+ errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") getting stream configuration info for device (" << device << ").";\r
+ errorText_ = errorStream_.str();\r
+ return FAILURE;\r
+ }\r
+\r
+ // Allocate the AudioBufferList.\r
+ bufferList = (AudioBufferList *) malloc( dataSize );\r
+ if ( bufferList == NULL ) {\r
+ errorText_ = "RtApiCore::probeDeviceOpen: memory error allocating AudioBufferList.";\r
+ return FAILURE;\r
+ }\r
+\r
+ result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, bufferList );\r
+ if (result != noErr || dataSize == 0) {\r
+ free( bufferList );\r
+ errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") getting stream configuration for device (" << device << ").";\r
+ errorText_ = errorStream_.str();\r
+ return FAILURE;\r
+ }\r
+\r
+ // Search for one or more streams that contain the desired number of\r
+ // channels. CoreAudio devices can have an arbitrary number of\r
+ // streams and each stream can have an arbitrary number of channels.\r
+ // For each stream, a single buffer of interleaved samples is\r
+ // provided. RtAudio prefers the use of one stream of interleaved\r
+ // data or multiple consecutive single-channel streams. However, we\r
+ // now support multiple consecutive multi-channel streams of\r
+ // interleaved data as well.\r
+ UInt32 iStream, offsetCounter = firstChannel;\r
+ UInt32 nStreams = bufferList->mNumberBuffers;\r
+ bool monoMode = false;\r
+ bool foundStream = false;\r
+\r
+ // First check that the device supports the requested number of\r
+ // channels.\r
+ UInt32 deviceChannels = 0;\r
+ for ( iStream=0; iStream<nStreams; iStream++ )\r
+ deviceChannels += bufferList->mBuffers[iStream].mNumberChannels;\r
+\r
+ if ( deviceChannels < ( channels + firstChannel ) ) {\r
+ free( bufferList );\r
+ errorStream_ << "RtApiCore::probeDeviceOpen: the device (" << device << ") does not support the requested channel count.";\r
+ errorText_ = errorStream_.str();\r
+ return FAILURE;\r
+ }\r
+\r
+ // Look for a single stream meeting our needs.\r
+ UInt32 firstStream, streamCount = 1, streamChannels = 0, channelOffset = 0;\r
+ for ( iStream=0; iStream<nStreams; iStream++ ) {\r
+ streamChannels = bufferList->mBuffers[iStream].mNumberChannels;\r
+ if ( streamChannels >= channels + offsetCounter ) {\r
+ firstStream = iStream;\r
+ channelOffset = offsetCounter;\r
+ foundStream = true;\r
+ break;\r
+ }\r
+ if ( streamChannels > offsetCounter ) break;\r
+ offsetCounter -= streamChannels;\r
+ }\r
+\r
+ // If we didn't find a single stream above, then we should be able\r
+ // to meet the channel specification with multiple streams.\r
+ if ( foundStream == false ) {\r
+ monoMode = true;\r
+ offsetCounter = firstChannel;\r
+ for ( iStream=0; iStream<nStreams; iStream++ ) {\r
+ streamChannels = bufferList->mBuffers[iStream].mNumberChannels;\r
+ if ( streamChannels > offsetCounter ) break;\r
+ offsetCounter -= streamChannels;\r
+ }\r
+\r
+ firstStream = iStream;\r
+ channelOffset = offsetCounter;\r
+ Int32 channelCounter = channels + offsetCounter - streamChannels;\r
+\r
+ if ( streamChannels > 1 ) monoMode = false;\r
+ while ( channelCounter > 0 ) {\r
+ streamChannels = bufferList->mBuffers[++iStream].mNumberChannels;\r
+ if ( streamChannels > 1 ) monoMode = false;\r
+ channelCounter -= streamChannels;\r
+ streamCount++;\r
+ }\r
+ }\r
+\r
+ free( bufferList );\r
+\r
+ // Determine the buffer size.\r
+ AudioValueRange bufferRange;\r
+ dataSize = sizeof( AudioValueRange );\r
+ property.mSelector = kAudioDevicePropertyBufferFrameSizeRange;\r
+ result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, &bufferRange );\r
+\r
+ if ( result != noErr ) {\r
+ errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") getting buffer size range for device (" << device << ").";\r
+ errorText_ = errorStream_.str();\r
+ return FAILURE;\r
+ }\r
+\r
+ if ( bufferRange.mMinimum > *bufferSize ) *bufferSize = (unsigned long) bufferRange.mMinimum;\r
+ else if ( bufferRange.mMaximum < *bufferSize ) *bufferSize = (unsigned long) bufferRange.mMaximum;\r
+ if ( options && options->flags & RTAUDIO_MINIMIZE_LATENCY ) *bufferSize = (unsigned long) bufferRange.mMinimum;\r
+\r
+ // Set the buffer size. For multiple streams, I'm assuming we only\r
+ // need to make this setting for the master channel.\r
+ UInt32 theSize = (UInt32) *bufferSize;\r
+ dataSize = sizeof( UInt32 );\r
+ property.mSelector = kAudioDevicePropertyBufferFrameSize;\r
+ result = AudioObjectSetPropertyData( id, &property, 0, NULL, dataSize, &theSize );\r
+\r
+ if ( result != noErr ) {\r
+ errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") setting the buffer size for device (" << device << ").";\r
+ errorText_ = errorStream_.str();\r
+ return FAILURE;\r
+ }\r
+\r
+ // If attempting to setup a duplex stream, the bufferSize parameter\r
+ // MUST be the same in both directions!\r
+ *bufferSize = theSize;\r
+ if ( stream_.mode == OUTPUT && mode == INPUT && *bufferSize != stream_.bufferSize ) {\r
+ errorStream_ << "RtApiCore::probeDeviceOpen: system error setting buffer size for duplex stream on device (" << device << ").";\r
+ errorText_ = errorStream_.str();\r
+ return FAILURE;\r
+ }\r
+\r
+ stream_.bufferSize = *bufferSize;\r
+ stream_.nBuffers = 1;\r
+\r
+ // Try to set "hog" mode ... it's not clear to me this is working.\r
+ if ( options && options->flags & RTAUDIO_HOG_DEVICE ) {\r
+ pid_t hog_pid;\r
+ dataSize = sizeof( hog_pid );\r
+ property.mSelector = kAudioDevicePropertyHogMode;\r
+ result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, &hog_pid );\r
+ if ( result != noErr ) {\r
+ errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") getting 'hog' state!";\r
+ errorText_ = errorStream_.str();\r
+ return FAILURE;\r
+ }\r
+\r
+ if ( hog_pid != getpid() ) {\r
+ hog_pid = getpid();\r
+ result = AudioObjectSetPropertyData( id, &property, 0, NULL, dataSize, &hog_pid );\r
+ if ( result != noErr ) {\r
+ errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") setting 'hog' state!";\r
+ errorText_ = errorStream_.str();\r
+ return FAILURE;\r
+ }\r
+ }\r
+ }\r
+\r
+ // Check and if necessary, change the sample rate for the device.\r
+ Float64 nominalRate;\r
+ dataSize = sizeof( Float64 );\r
+ property.mSelector = kAudioDevicePropertyNominalSampleRate;\r
+ result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, &nominalRate );\r
+ if ( result != noErr ) {\r
+ errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") getting current sample rate.";\r
+ errorText_ = errorStream_.str();\r
+ return FAILURE;\r
+ }\r
+\r
+ // Only change the sample rate if off by more than 1 Hz.\r
+ if ( fabs( nominalRate - (double)sampleRate ) > 1.0 ) {\r
+\r
+ // Set a property listener for the sample rate change\r
+ Float64 reportedRate = 0.0;\r
+ AudioObjectPropertyAddress tmp = { kAudioDevicePropertyNominalSampleRate, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster };\r
+ result = AudioObjectAddPropertyListener( id, &tmp, rateListener, (void *) &reportedRate );\r
+ if ( result != noErr ) {\r
+ errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") setting sample rate property listener for device (" << device << ").";\r
+ errorText_ = errorStream_.str();\r
+ return FAILURE;\r
+ }\r
+\r
+ nominalRate = (Float64) sampleRate;\r
+ result = AudioObjectSetPropertyData( id, &property, 0, NULL, dataSize, &nominalRate );\r
+ if ( result != noErr ) {\r
+ AudioObjectRemovePropertyListener( id, &tmp, rateListener, (void *) &reportedRate );\r
+ errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") setting sample rate for device (" << device << ").";\r
+ errorText_ = errorStream_.str();\r
+ return FAILURE;\r
+ }\r
+\r
+ // Now wait until the reported nominal rate is what we just set.\r
+ UInt32 microCounter = 0;\r
+ while ( reportedRate != nominalRate ) {\r
+ microCounter += 5000;\r
+ if ( microCounter > 5000000 ) break;\r
+ usleep( 5000 );\r
+ }\r
+\r
+ // Remove the property listener.\r
+ AudioObjectRemovePropertyListener( id, &tmp, rateListener, (void *) &reportedRate );\r
+\r
+ if ( microCounter > 5000000 ) {\r
+ errorStream_ << "RtApiCore::probeDeviceOpen: timeout waiting for sample rate update for device (" << device << ").";\r
+ errorText_ = errorStream_.str();\r
+ return FAILURE;\r
+ }\r
+ }\r
+\r
+ // Now set the stream format for all streams. Also, check the\r
+ // physical format of the device and change that if necessary.\r
+ AudioStreamBasicDescription description;\r
+ dataSize = sizeof( AudioStreamBasicDescription );\r
+ property.mSelector = kAudioStreamPropertyVirtualFormat;\r
+ result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, &description );\r
+ if ( result != noErr ) {\r
+ errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") getting stream format for device (" << device << ").";\r
+ errorText_ = errorStream_.str();\r
+ return FAILURE;\r
+ }\r
+\r
+ // Set the sample rate and data format id. However, only make the\r
+ // change if the sample rate is not within 1.0 of the desired\r
+ // rate and the format is not linear pcm.\r
+ bool updateFormat = false;\r
+ if ( fabs( description.mSampleRate - (Float64)sampleRate ) > 1.0 ) {\r
+ description.mSampleRate = (Float64) sampleRate;\r
+ updateFormat = true;\r
+ }\r
+\r
+ if ( description.mFormatID != kAudioFormatLinearPCM ) {\r
+ description.mFormatID = kAudioFormatLinearPCM;\r
+ updateFormat = true;\r
+ }\r
+\r
+ if ( updateFormat ) {\r
+ result = AudioObjectSetPropertyData( id, &property, 0, NULL, dataSize, &description );\r
+ if ( result != noErr ) {\r
+ errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") setting sample rate or data format for device (" << device << ").";\r
+ errorText_ = errorStream_.str();\r
+ return FAILURE;\r
+ }\r
+ }\r
+\r
+ // Now check the physical format.\r
+ property.mSelector = kAudioStreamPropertyPhysicalFormat;\r
+ result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, &description );\r
+ if ( result != noErr ) {\r
+ errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") getting stream physical format for device (" << device << ").";\r
+ errorText_ = errorStream_.str();\r
+ return FAILURE;\r
+ }\r
+\r
+ //std::cout << "Current physical stream format:" << std::endl;\r
+ //std::cout << " mBitsPerChan = " << description.mBitsPerChannel << std::endl;\r
+ //std::cout << " aligned high = " << (description.mFormatFlags & kAudioFormatFlagIsAlignedHigh) << ", isPacked = " << (description.mFormatFlags & kAudioFormatFlagIsPacked) << std::endl;\r
+ //std::cout << " bytesPerFrame = " << description.mBytesPerFrame << std::endl;\r
+ //std::cout << " sample rate = " << description.mSampleRate << std::endl;\r
+\r
+ if ( description.mFormatID != kAudioFormatLinearPCM || description.mBitsPerChannel < 16 ) {\r
+ description.mFormatID = kAudioFormatLinearPCM;\r
+ //description.mSampleRate = (Float64) sampleRate;\r
+ AudioStreamBasicDescription testDescription = description;\r
+ UInt32 formatFlags;\r
+\r
+ // We'll try higher bit rates first and then work our way down.\r
+ std::vector< std::pair<UInt32, UInt32> > physicalFormats;\r
+ formatFlags = (description.mFormatFlags | kLinearPCMFormatFlagIsFloat) & ~kLinearPCMFormatFlagIsSignedInteger;\r
+ physicalFormats.push_back( std::pair<Float32, UInt32>( 32, formatFlags ) );\r
+ formatFlags = (description.mFormatFlags | kLinearPCMFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked) & ~kLinearPCMFormatFlagIsFloat;\r
+ physicalFormats.push_back( std::pair<Float32, UInt32>( 32, formatFlags ) );\r
+ physicalFormats.push_back( std::pair<Float32, UInt32>( 24, formatFlags ) ); // 24-bit packed\r
+ formatFlags &= ~( kAudioFormatFlagIsPacked | kAudioFormatFlagIsAlignedHigh );\r
+ physicalFormats.push_back( std::pair<Float32, UInt32>( 24.2, formatFlags ) ); // 24-bit in 4 bytes, aligned low\r
+ formatFlags |= kAudioFormatFlagIsAlignedHigh;\r
+ physicalFormats.push_back( std::pair<Float32, UInt32>( 24.4, formatFlags ) ); // 24-bit in 4 bytes, aligned high\r
+ formatFlags = (description.mFormatFlags | kLinearPCMFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked) & ~kLinearPCMFormatFlagIsFloat;\r
+ physicalFormats.push_back( std::pair<Float32, UInt32>( 16, formatFlags ) );\r
+ physicalFormats.push_back( std::pair<Float32, UInt32>( 8, formatFlags ) );\r
+\r
+ bool setPhysicalFormat = false;\r
+ for( unsigned int i=0; i<physicalFormats.size(); i++ ) {\r
+ testDescription = description;\r
+ testDescription.mBitsPerChannel = (UInt32) physicalFormats[i].first;\r
+ testDescription.mFormatFlags = physicalFormats[i].second;\r
+ if ( (24 == (UInt32)physicalFormats[i].first) && ~( physicalFormats[i].second & kAudioFormatFlagIsPacked ) )\r
+ testDescription.mBytesPerFrame = 4 * testDescription.mChannelsPerFrame;\r
+ else\r
+ testDescription.mBytesPerFrame = testDescription.mBitsPerChannel/8 * testDescription.mChannelsPerFrame;\r
+ testDescription.mBytesPerPacket = testDescription.mBytesPerFrame * testDescription.mFramesPerPacket;\r
+ result = AudioObjectSetPropertyData( id, &property, 0, NULL, dataSize, &testDescription );\r
+ if ( result == noErr ) {\r
+ setPhysicalFormat = true;\r
+ //std::cout << "Updated physical stream format:" << std::endl;\r
+ //std::cout << " mBitsPerChan = " << testDescription.mBitsPerChannel << std::endl;\r
+ //std::cout << " aligned high = " << (testDescription.mFormatFlags & kAudioFormatFlagIsAlignedHigh) << ", isPacked = " << (testDescription.mFormatFlags & kAudioFormatFlagIsPacked) << std::endl;\r
+ //std::cout << " bytesPerFrame = " << testDescription.mBytesPerFrame << std::endl;\r
+ //std::cout << " sample rate = " << testDescription.mSampleRate << std::endl;\r
+ break;\r
+ }\r
+ }\r
+\r
+ if ( !setPhysicalFormat ) {\r
+ errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") setting physical data format for device (" << device << ").";\r
+ errorText_ = errorStream_.str();\r
+ return FAILURE;\r
+ }\r
+ } // done setting virtual/physical formats.\r
+\r
+ // Get the stream / device latency.\r
+ UInt32 latency;\r
+ dataSize = sizeof( UInt32 );\r
+ property.mSelector = kAudioDevicePropertyLatency;\r
+ if ( AudioObjectHasProperty( id, &property ) == true ) {\r
+ result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, &latency );\r
+ if ( result == kAudioHardwareNoError ) stream_.latency[ mode ] = latency;\r
+ else {\r
+ errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") getting device latency for device (" << device << ").";\r
+ errorText_ = errorStream_.str();\r
+ error( RtAudioError::WARNING );\r
+ }\r
+ }\r
+\r
+ // Byte-swapping: According to AudioHardware.h, the stream data will\r
+ // always be presented in native-endian format, so we should never\r
+ // need to byte swap.\r
+ stream_.doByteSwap[mode] = false;\r
+\r
+ // From the CoreAudio documentation, PCM data must be supplied as\r
+ // 32-bit floats.\r
+ stream_.userFormat = format;\r
+ stream_.deviceFormat[mode] = RTAUDIO_FLOAT32;\r
+\r
+ if ( streamCount == 1 )\r
+ stream_.nDeviceChannels[mode] = description.mChannelsPerFrame;\r
+ else // multiple streams\r
+ stream_.nDeviceChannels[mode] = channels;\r
+ stream_.nUserChannels[mode] = channels;\r
+ stream_.channelOffset[mode] = channelOffset; // offset within a CoreAudio stream\r
+ if ( options && options->flags & RTAUDIO_NONINTERLEAVED ) stream_.userInterleaved = false;\r
+ else stream_.userInterleaved = true;\r
+ stream_.deviceInterleaved[mode] = true;\r
+ if ( monoMode == true ) stream_.deviceInterleaved[mode] = false;\r
+\r
+ // Set flags for buffer conversion.\r
+ stream_.doConvertBuffer[mode] = false;\r
+ if ( stream_.userFormat != stream_.deviceFormat[mode] )\r
+ stream_.doConvertBuffer[mode] = true;\r
+ if ( stream_.nUserChannels[mode] < stream_.nDeviceChannels[mode] )\r
+ stream_.doConvertBuffer[mode] = true;\r
+ if ( streamCount == 1 ) {\r
+ if ( stream_.nUserChannels[mode] > 1 &&\r
+ stream_.userInterleaved != stream_.deviceInterleaved[mode] )\r
+ stream_.doConvertBuffer[mode] = true;\r
+ }\r
+ else if ( monoMode && stream_.userInterleaved )\r
+ stream_.doConvertBuffer[mode] = true;\r
+\r
+ // Allocate our CoreHandle structure for the stream.\r
+ CoreHandle *handle = 0;\r
+ if ( stream_.apiHandle == 0 ) {\r
+ try {\r
+ handle = new CoreHandle;\r
+ }\r
+ catch ( std::bad_alloc& ) {\r
+ errorText_ = "RtApiCore::probeDeviceOpen: error allocating CoreHandle memory.";\r
+ goto error;\r
+ }\r
+\r
+ if ( pthread_cond_init( &handle->condition, NULL ) ) {\r
+ errorText_ = "RtApiCore::probeDeviceOpen: error initializing pthread condition variable.";\r
+ goto error;\r
+ }\r
+ stream_.apiHandle = (void *) handle;\r
+ }\r
+ else\r
+ handle = (CoreHandle *) stream_.apiHandle;\r
+ handle->iStream[mode] = firstStream;\r
+ handle->nStreams[mode] = streamCount;\r
+ handle->id[mode] = id;\r
+\r
+ // Allocate necessary internal buffers.\r
+ unsigned long bufferBytes;\r
+ bufferBytes = stream_.nUserChannels[mode] * *bufferSize * formatBytes( stream_.userFormat );\r
+ // stream_.userBuffer[mode] = (char *) calloc( bufferBytes, 1 );\r
+ stream_.userBuffer[mode] = (char *) malloc( bufferBytes * sizeof(char) );\r
+ memset( stream_.userBuffer[mode], 0, bufferBytes * sizeof(char) );\r
+ if ( stream_.userBuffer[mode] == NULL ) {\r
+ errorText_ = "RtApiCore::probeDeviceOpen: error allocating user buffer memory.";\r
+ goto error;\r
+ }\r
+\r
+ // If possible, we will make use of the CoreAudio stream buffers as\r
+ // "device buffers". However, we can't do this if using multiple\r
+ // streams.\r
+ if ( stream_.doConvertBuffer[mode] && handle->nStreams[mode] > 1 ) {\r
+\r
+ bool makeBuffer = true;\r
+ bufferBytes = stream_.nDeviceChannels[mode] * formatBytes( stream_.deviceFormat[mode] );\r
+ if ( mode == INPUT ) {\r
+ if ( stream_.mode == OUTPUT && stream_.deviceBuffer ) {\r
+ unsigned long bytesOut = stream_.nDeviceChannels[0] * formatBytes( stream_.deviceFormat[0] );\r
+ if ( bufferBytes <= bytesOut ) makeBuffer = false;\r
+ }\r
+ }\r
+\r
+ if ( makeBuffer ) {\r
+ bufferBytes *= *bufferSize;\r
+ if ( stream_.deviceBuffer ) free( stream_.deviceBuffer );\r
+ stream_.deviceBuffer = (char *) calloc( bufferBytes, 1 );\r
+ if ( stream_.deviceBuffer == NULL ) {\r
+ errorText_ = "RtApiCore::probeDeviceOpen: error allocating device buffer memory.";\r
+ goto error;\r
+ }\r
+ }\r
+ }\r
+\r
+ stream_.sampleRate = sampleRate;\r
+ stream_.device[mode] = device;\r
+ stream_.state = STREAM_STOPPED;\r
+ stream_.callbackInfo.object = (void *) this;\r
+\r
+ // Setup the buffer conversion information structure.\r
+ if ( stream_.doConvertBuffer[mode] ) {\r
+ if ( streamCount > 1 ) setConvertInfo( mode, 0 );\r
+ else setConvertInfo( mode, channelOffset );\r
+ }\r
+\r
+ if ( mode == INPUT && stream_.mode == OUTPUT && stream_.device[0] == device )\r
+ // Only one callback procedure per device.\r
+ stream_.mode = DUPLEX;\r
+ else {\r
+#if defined( MAC_OS_X_VERSION_10_5 ) && ( MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 )\r
+ result = AudioDeviceCreateIOProcID( id, callbackHandler, (void *) &stream_.callbackInfo, &handle->procId[mode] );\r
+#else\r
+ // deprecated in favor of AudioDeviceCreateIOProcID()\r
+ result = AudioDeviceAddIOProc( id, callbackHandler, (void *) &stream_.callbackInfo );\r
+#endif\r
+ if ( result != noErr ) {\r
+ errorStream_ << "RtApiCore::probeDeviceOpen: system error setting callback for device (" << device << ").";\r
+ errorText_ = errorStream_.str();\r
+ goto error;\r
+ }\r
+ if ( stream_.mode == OUTPUT && mode == INPUT )\r
+ stream_.mode = DUPLEX;\r
+ else\r
+ stream_.mode = mode;\r
+ }\r
+\r
+ // Setup the device property listener for over/underload.\r
+ property.mSelector = kAudioDeviceProcessorOverload;\r
+ property.mScope = kAudioObjectPropertyScopeGlobal;\r
+ result = AudioObjectAddPropertyListener( id, &property, xrunListener, (void *) handle );\r
+\r
+ return SUCCESS;\r
+\r
+ error:\r
+ if ( handle ) {\r
+ pthread_cond_destroy( &handle->condition );\r
+ delete handle;\r
+ stream_.apiHandle = 0;\r
+ }\r
+\r
+ for ( int i=0; i<2; i++ ) {\r
+ if ( stream_.userBuffer[i] ) {\r
+ free( stream_.userBuffer[i] );\r
+ stream_.userBuffer[i] = 0;\r
+ }\r
+ }\r
+\r
+ if ( stream_.deviceBuffer ) {\r
+ free( stream_.deviceBuffer );\r
+ stream_.deviceBuffer = 0;\r
+ }\r
+\r
+ stream_.state = STREAM_CLOSED;\r
+ return FAILURE;\r
+}\r
+\r
+void RtApiCore :: closeStream( void )\r
+{\r
+ if ( stream_.state == STREAM_CLOSED ) {\r
+ errorText_ = "RtApiCore::closeStream(): no open stream to close!";\r
+ error( RtAudioError::WARNING );\r
+ return;\r
+ }\r
+\r
+ CoreHandle *handle = (CoreHandle *) stream_.apiHandle;\r
+ if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) {\r
+ if (handle) {\r
+ AudioObjectPropertyAddress property = { kAudioHardwarePropertyDevices,\r
+ kAudioObjectPropertyScopeGlobal,\r
+ kAudioObjectPropertyElementMaster };\r
+\r
+ property.mSelector = kAudioDeviceProcessorOverload;\r
+ property.mScope = kAudioObjectPropertyScopeGlobal;\r
+ if (AudioObjectRemovePropertyListener( handle->id[0], &property, xrunListener, (void *) handle ) != noErr) {\r
+ errorText_ = "RtApiCore::closeStream(): error removing property listener!";\r
+ error( RtAudioError::WARNING );\r
+ }\r
+ }\r
+ if ( stream_.state == STREAM_RUNNING )\r
+ AudioDeviceStop( handle->id[0], callbackHandler );\r
+#if defined( MAC_OS_X_VERSION_10_5 ) && ( MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 )\r
+ AudioDeviceDestroyIOProcID( handle->id[0], handle->procId[0] );\r
+#else\r
+ // deprecated in favor of AudioDeviceDestroyIOProcID()\r
+ AudioDeviceRemoveIOProc( handle->id[0], callbackHandler );\r
+#endif\r
+ }\r
+\r
+ if ( stream_.mode == INPUT || ( stream_.mode == DUPLEX && stream_.device[0] != stream_.device[1] ) ) {\r
+ if (handle) {\r
+ AudioObjectPropertyAddress property = { kAudioHardwarePropertyDevices,\r
+ kAudioObjectPropertyScopeGlobal,\r
+ kAudioObjectPropertyElementMaster };\r
+\r
+ property.mSelector = kAudioDeviceProcessorOverload;\r
+ property.mScope = kAudioObjectPropertyScopeGlobal;\r
+ if (AudioObjectRemovePropertyListener( handle->id[1], &property, xrunListener, (void *) handle ) != noErr) {\r
+ errorText_ = "RtApiCore::closeStream(): error removing property listener!";\r
+ error( RtAudioError::WARNING );\r
+ }\r
+ }\r
+ if ( stream_.state == STREAM_RUNNING )\r
+ AudioDeviceStop( handle->id[1], callbackHandler );\r
+#if defined( MAC_OS_X_VERSION_10_5 ) && ( MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 )\r
+ AudioDeviceDestroyIOProcID( handle->id[1], handle->procId[1] );\r
+#else\r
+ // deprecated in favor of AudioDeviceDestroyIOProcID()\r
+ AudioDeviceRemoveIOProc( handle->id[1], callbackHandler );\r
+#endif\r
+ }\r
+\r
+ for ( int i=0; i<2; i++ ) {\r
+ if ( stream_.userBuffer[i] ) {\r
+ free( stream_.userBuffer[i] );\r
+ stream_.userBuffer[i] = 0;\r
+ }\r
+ }\r
+\r
+ if ( stream_.deviceBuffer ) {\r
+ free( stream_.deviceBuffer );\r
+ stream_.deviceBuffer = 0;\r
+ }\r
+\r
+ // Destroy pthread condition variable.\r
+ pthread_cond_destroy( &handle->condition );\r
+ delete handle;\r
+ stream_.apiHandle = 0;\r
+\r
+ stream_.mode = UNINITIALIZED;\r
+ stream_.state = STREAM_CLOSED;\r
+}\r
+\r
+void RtApiCore :: startStream( void )\r
+{\r
+ verifyStream();\r
+ if ( stream_.state == STREAM_RUNNING ) {\r
+ errorText_ = "RtApiCore::startStream(): the stream is already running!";\r
+ error( RtAudioError::WARNING );\r
+ return;\r
+ }\r
+\r
+ OSStatus result = noErr;\r
+ CoreHandle *handle = (CoreHandle *) stream_.apiHandle;\r
+ if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) {\r
+\r
+ result = AudioDeviceStart( handle->id[0], callbackHandler );\r
+ if ( result != noErr ) {\r
+ errorStream_ << "RtApiCore::startStream: system error (" << getErrorCode( result ) << ") starting callback procedure on device (" << stream_.device[0] << ").";\r
+ errorText_ = errorStream_.str();\r
+ goto unlock;\r
+ }\r
+ }\r
+\r
+ if ( stream_.mode == INPUT ||\r
+ ( stream_.mode == DUPLEX && stream_.device[0] != stream_.device[1] ) ) {\r
+\r
+ result = AudioDeviceStart( handle->id[1], callbackHandler );\r
+ if ( result != noErr ) {\r
+ errorStream_ << "RtApiCore::startStream: system error starting input callback procedure on device (" << stream_.device[1] << ").";\r
+ errorText_ = errorStream_.str();\r
+ goto unlock;\r
+ }\r
+ }\r
+\r
+ handle->drainCounter = 0;\r
+ handle->internalDrain = false;\r
+ stream_.state = STREAM_RUNNING;\r
+\r
+ unlock:\r
+ if ( result == noErr ) return;\r
+ error( RtAudioError::SYSTEM_ERROR );\r
+}\r
+\r
+void RtApiCore :: stopStream( void )\r
+{\r
+ verifyStream();\r
+ if ( stream_.state == STREAM_STOPPED ) {\r
+ errorText_ = "RtApiCore::stopStream(): the stream is already stopped!";\r
+ error( RtAudioError::WARNING );\r
+ return;\r
+ }\r
+\r
+ OSStatus result = noErr;\r
+ CoreHandle *handle = (CoreHandle *) stream_.apiHandle;\r
+ if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) {\r
+\r
+ if ( handle->drainCounter == 0 ) {\r
+ handle->drainCounter = 2;\r
+ pthread_cond_wait( &handle->condition, &stream_.mutex ); // block until signaled\r
+ }\r
+\r
+ result = AudioDeviceStop( handle->id[0], callbackHandler );\r
+ if ( result != noErr ) {\r
+ errorStream_ << "RtApiCore::stopStream: system error (" << getErrorCode( result ) << ") stopping callback procedure on device (" << stream_.device[0] << ").";\r
+ errorText_ = errorStream_.str();\r
+ goto unlock;\r
+ }\r
+ }\r
+\r
+ if ( stream_.mode == INPUT || ( stream_.mode == DUPLEX && stream_.device[0] != stream_.device[1] ) ) {\r
+\r
+ result = AudioDeviceStop( handle->id[1], callbackHandler );\r
+ if ( result != noErr ) {\r
+ errorStream_ << "RtApiCore::stopStream: system error (" << getErrorCode( result ) << ") stopping input callback procedure on device (" << stream_.device[1] << ").";\r
+ errorText_ = errorStream_.str();\r
+ goto unlock;\r
+ }\r
+ }\r
+\r
+ stream_.state = STREAM_STOPPED;\r
+\r
+ unlock:\r
+ if ( result == noErr ) return;\r
+ error( RtAudioError::SYSTEM_ERROR );\r
+}\r
+\r
+void RtApiCore :: abortStream( void )\r
+{\r
+ verifyStream();\r
+ if ( stream_.state == STREAM_STOPPED ) {\r
+ errorText_ = "RtApiCore::abortStream(): the stream is already stopped!";\r
+ error( RtAudioError::WARNING );\r
+ return;\r
+ }\r
+\r
+ CoreHandle *handle = (CoreHandle *) stream_.apiHandle;\r
+ handle->drainCounter = 2;\r
+\r
+ stopStream();\r
+}\r
+\r
+// This function will be called by a spawned thread when the user\r
+// callback function signals that the stream should be stopped or\r
+// aborted. It is better to handle it this way because the\r
+// callbackEvent() function probably should return before the AudioDeviceStop()\r
+// function is called.\r
+static void *coreStopStream( void *ptr )\r
+{\r
+ CallbackInfo *info = (CallbackInfo *) ptr;\r
+ RtApiCore *object = (RtApiCore *) info->object;\r
+\r
+ object->stopStream();\r
+ pthread_exit( NULL );\r
+}\r
+\r
+bool RtApiCore :: callbackEvent( AudioDeviceID deviceId,\r
+ const AudioBufferList *inBufferList,\r
+ const AudioBufferList *outBufferList )\r
+{\r
+ if ( stream_.state == STREAM_STOPPED || stream_.state == STREAM_STOPPING ) return SUCCESS;\r
+ if ( stream_.state == STREAM_CLOSED ) {\r
+ errorText_ = "RtApiCore::callbackEvent(): the stream is closed ... this shouldn't happen!";\r
+ error( RtAudioError::WARNING );\r
+ return FAILURE;\r
+ }\r
+\r
+ CallbackInfo *info = (CallbackInfo *) &stream_.callbackInfo;\r
+ CoreHandle *handle = (CoreHandle *) stream_.apiHandle;\r
+\r
+ // Check if we were draining the stream and signal is finished.\r
+ if ( handle->drainCounter > 3 ) {\r
+ ThreadHandle threadId;\r
+\r
+ stream_.state = STREAM_STOPPING;\r
+ if ( handle->internalDrain == true )\r
+ pthread_create( &threadId, NULL, coreStopStream, info );\r
+ else // external call to stopStream()\r
+ pthread_cond_signal( &handle->condition );\r
+ return SUCCESS;\r
+ }\r
+\r
+ AudioDeviceID outputDevice = handle->id[0];\r
+\r
+ // Invoke user callback to get fresh output data UNLESS we are\r
+ // draining stream or duplex mode AND the input/output devices are\r
+ // different AND this function is called for the input device.\r
+ if ( handle->drainCounter == 0 && ( stream_.mode != DUPLEX || deviceId == outputDevice ) ) {\r
+ RtAudioCallback callback = (RtAudioCallback) info->callback;\r
+ double streamTime = getStreamTime();\r
+ RtAudioStreamStatus status = 0;\r
+ if ( stream_.mode != INPUT && handle->xrun[0] == true ) {\r
+ status |= RTAUDIO_OUTPUT_UNDERFLOW;\r
+ handle->xrun[0] = false;\r
+ }\r
+ if ( stream_.mode != OUTPUT && handle->xrun[1] == true ) {\r
+ status |= RTAUDIO_INPUT_OVERFLOW;\r
+ handle->xrun[1] = false;\r
+ }\r
+\r
+ int cbReturnValue = callback( stream_.userBuffer[0], stream_.userBuffer[1],\r
+ stream_.bufferSize, streamTime, status, info->userData );\r
+ if ( cbReturnValue == 2 ) {\r
+ stream_.state = STREAM_STOPPING;\r
+ handle->drainCounter = 2;\r
+ abortStream();\r
+ return SUCCESS;\r
+ }\r
+ else if ( cbReturnValue == 1 ) {\r
+ handle->drainCounter = 1;\r
+ handle->internalDrain = true;\r
+ }\r
+ }\r
+\r
+ if ( stream_.mode == OUTPUT || ( stream_.mode == DUPLEX && deviceId == outputDevice ) ) {\r
+\r
+ if ( handle->drainCounter > 1 ) { // write zeros to the output stream\r
+\r
+ if ( handle->nStreams[0] == 1 ) {\r
+ memset( outBufferList->mBuffers[handle->iStream[0]].mData,\r
+ 0,\r
+ outBufferList->mBuffers[handle->iStream[0]].mDataByteSize );\r
+ }\r
+ else { // fill multiple streams with zeros\r
+ for ( unsigned int i=0; i<handle->nStreams[0]; i++ ) {\r
+ memset( outBufferList->mBuffers[handle->iStream[0]+i].mData,\r
+ 0,\r
+ outBufferList->mBuffers[handle->iStream[0]+i].mDataByteSize );\r
+ }\r
+ }\r
+ }\r
+ else if ( handle->nStreams[0] == 1 ) {\r
+ if ( stream_.doConvertBuffer[0] ) { // convert directly to CoreAudio stream buffer\r
+ convertBuffer( (char *) outBufferList->mBuffers[handle->iStream[0]].mData,\r
+ stream_.userBuffer[0], stream_.convertInfo[0] );\r
+ }\r
+ else { // copy from user buffer\r
+ memcpy( outBufferList->mBuffers[handle->iStream[0]].mData,\r
+ stream_.userBuffer[0],\r
+ outBufferList->mBuffers[handle->iStream[0]].mDataByteSize );\r
+ }\r
+ }\r
+ else { // fill multiple streams\r
+ Float32 *inBuffer = (Float32 *) stream_.userBuffer[0];\r
+ if ( stream_.doConvertBuffer[0] ) {\r
+ convertBuffer( stream_.deviceBuffer, stream_.userBuffer[0], stream_.convertInfo[0] );\r
+ inBuffer = (Float32 *) stream_.deviceBuffer;\r
+ }\r
+\r
+ if ( stream_.deviceInterleaved[0] == false ) { // mono mode\r
+ UInt32 bufferBytes = outBufferList->mBuffers[handle->iStream[0]].mDataByteSize;\r
+ for ( unsigned int i=0; i<stream_.nUserChannels[0]; i++ ) {\r
+ memcpy( outBufferList->mBuffers[handle->iStream[0]+i].mData,\r
+ (void *)&inBuffer[i*stream_.bufferSize], bufferBytes );\r
+ }\r
+ }\r
+ else { // fill multiple multi-channel streams with interleaved data\r
+ UInt32 streamChannels, channelsLeft, inJump, outJump, inOffset;\r
+ Float32 *out, *in;\r
+\r
+ bool inInterleaved = ( stream_.userInterleaved ) ? true : false;\r
+ UInt32 inChannels = stream_.nUserChannels[0];\r
+ if ( stream_.doConvertBuffer[0] ) {\r
+ inInterleaved = true; // device buffer will always be interleaved for nStreams > 1 and not mono mode\r
+ inChannels = stream_.nDeviceChannels[0];\r
+ }\r
+\r
+ if ( inInterleaved ) inOffset = 1;\r
+ else inOffset = stream_.bufferSize;\r
+\r
+ channelsLeft = inChannels;\r
+ for ( unsigned int i=0; i<handle->nStreams[0]; i++ ) {\r
+ in = inBuffer;\r
+ out = (Float32 *) outBufferList->mBuffers[handle->iStream[0]+i].mData;\r
+ streamChannels = outBufferList->mBuffers[handle->iStream[0]+i].mNumberChannels;\r
+\r
+ outJump = 0;\r
+ // Account for possible channel offset in first stream\r
+ if ( i == 0 && stream_.channelOffset[0] > 0 ) {\r
+ streamChannels -= stream_.channelOffset[0];\r
+ outJump = stream_.channelOffset[0];\r
+ out += outJump;\r
+ }\r
+\r
+ // Account for possible unfilled channels at end of the last stream\r
+ if ( streamChannels > channelsLeft ) {\r
+ outJump = streamChannels - channelsLeft;\r
+ streamChannels = channelsLeft;\r
+ }\r
+\r
+ // Determine input buffer offsets and skips\r
+ if ( inInterleaved ) {\r
+ inJump = inChannels;\r
+ in += inChannels - channelsLeft;\r
+ }\r
+ else {\r
+ inJump = 1;\r
+ in += (inChannels - channelsLeft) * inOffset;\r
+ }\r
+\r
+ for ( unsigned int i=0; i<stream_.bufferSize; i++ ) {\r
+ for ( unsigned int j=0; j<streamChannels; j++ ) {\r
+ *out++ = in[j*inOffset];\r
+ }\r
+ out += outJump;\r
+ in += inJump;\r
+ }\r
+ channelsLeft -= streamChannels;\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+ // Don't bother draining input\r
+ if ( handle->drainCounter ) {\r
+ handle->drainCounter++;\r
+ goto unlock;\r
+ }\r
+\r
+ AudioDeviceID inputDevice;\r
+ inputDevice = handle->id[1];\r
+ if ( stream_.mode == INPUT || ( stream_.mode == DUPLEX && deviceId == inputDevice ) ) {\r
+\r
+ if ( handle->nStreams[1] == 1 ) {\r
+ if ( stream_.doConvertBuffer[1] ) { // convert directly from CoreAudio stream buffer\r
+ convertBuffer( stream_.userBuffer[1],\r
+ (char *) inBufferList->mBuffers[handle->iStream[1]].mData,\r
+ stream_.convertInfo[1] );\r
+ }\r
+ else { // copy to user buffer\r
+ memcpy( stream_.userBuffer[1],\r
+ inBufferList->mBuffers[handle->iStream[1]].mData,\r
+ inBufferList->mBuffers[handle->iStream[1]].mDataByteSize );\r
+ }\r
+ }\r
+ else { // read from multiple streams\r
+ Float32 *outBuffer = (Float32 *) stream_.userBuffer[1];\r
+ if ( stream_.doConvertBuffer[1] ) outBuffer = (Float32 *) stream_.deviceBuffer;\r
+\r
+ if ( stream_.deviceInterleaved[1] == false ) { // mono mode\r
+ UInt32 bufferBytes = inBufferList->mBuffers[handle->iStream[1]].mDataByteSize;\r
+ for ( unsigned int i=0; i<stream_.nUserChannels[1]; i++ ) {\r
+ memcpy( (void *)&outBuffer[i*stream_.bufferSize],\r
+ inBufferList->mBuffers[handle->iStream[1]+i].mData, bufferBytes );\r
+ }\r
+ }\r
+ else { // read from multiple multi-channel streams\r
+ UInt32 streamChannels, channelsLeft, inJump, outJump, outOffset;\r
+ Float32 *out, *in;\r
+\r
+ bool outInterleaved = ( stream_.userInterleaved ) ? true : false;\r
+ UInt32 outChannels = stream_.nUserChannels[1];\r
+ if ( stream_.doConvertBuffer[1] ) {\r
+ outInterleaved = true; // device buffer will always be interleaved for nStreams > 1 and not mono mode\r
+ outChannels = stream_.nDeviceChannels[1];\r
+ }\r
+\r
+ if ( outInterleaved ) outOffset = 1;\r
+ else outOffset = stream_.bufferSize;\r
+\r
+ channelsLeft = outChannels;\r
+ for ( unsigned int i=0; i<handle->nStreams[1]; i++ ) {\r
+ out = outBuffer;\r
+ in = (Float32 *) inBufferList->mBuffers[handle->iStream[1]+i].mData;\r
+ streamChannels = inBufferList->mBuffers[handle->iStream[1]+i].mNumberChannels;\r
+\r
+ inJump = 0;\r
+ // Account for possible channel offset in first stream\r
+ if ( i == 0 && stream_.channelOffset[1] > 0 ) {\r
+ streamChannels -= stream_.channelOffset[1];\r
+ inJump = stream_.channelOffset[1];\r
+ in += inJump;\r
+ }\r
+\r
+ // Account for possible unread channels at end of the last stream\r
+ if ( streamChannels > channelsLeft ) {\r
+ inJump = streamChannels - channelsLeft;\r
+ streamChannels = channelsLeft;\r
+ }\r
+\r
+ // Determine output buffer offsets and skips\r
+ if ( outInterleaved ) {\r
+ outJump = outChannels;\r
+ out += outChannels - channelsLeft;\r
+ }\r
+ else {\r
+ outJump = 1;\r
+ out += (outChannels - channelsLeft) * outOffset;\r
+ }\r
+\r
+ for ( unsigned int i=0; i<stream_.bufferSize; i++ ) {\r
+ for ( unsigned int j=0; j<streamChannels; j++ ) {\r
+ out[j*outOffset] = *in++;\r
+ }\r
+ out += outJump;\r
+ in += inJump;\r
+ }\r
+ channelsLeft -= streamChannels;\r
+ }\r
+ }\r
+\r
+ if ( stream_.doConvertBuffer[1] ) { // convert from our internal "device" buffer\r
+ convertBuffer( stream_.userBuffer[1],\r
+ stream_.deviceBuffer,\r
+ stream_.convertInfo[1] );\r
+ }\r
+ }\r
+ }\r
+\r
+ unlock:\r
+ //MUTEX_UNLOCK( &stream_.mutex );\r
+\r
+ RtApi::tickStreamTime();\r
+ return SUCCESS;\r
+}\r
+\r
+const char* RtApiCore :: getErrorCode( OSStatus code )\r
+{\r
+ switch( code ) {\r
+\r
+ case kAudioHardwareNotRunningError:\r
+ return "kAudioHardwareNotRunningError";\r
+\r
+ case kAudioHardwareUnspecifiedError:\r
+ return "kAudioHardwareUnspecifiedError";\r
+\r
+ case kAudioHardwareUnknownPropertyError:\r
+ return "kAudioHardwareUnknownPropertyError";\r
+\r
+ case kAudioHardwareBadPropertySizeError:\r
+ return "kAudioHardwareBadPropertySizeError";\r
+\r
+ case kAudioHardwareIllegalOperationError:\r
+ return "kAudioHardwareIllegalOperationError";\r
+\r
+ case kAudioHardwareBadObjectError:\r
+ return "kAudioHardwareBadObjectError";\r
+\r
+ case kAudioHardwareBadDeviceError:\r
+ return "kAudioHardwareBadDeviceError";\r
+\r
+ case kAudioHardwareBadStreamError:\r
+ return "kAudioHardwareBadStreamError";\r
+\r
+ case kAudioHardwareUnsupportedOperationError:\r
+ return "kAudioHardwareUnsupportedOperationError";\r
+\r
+ case kAudioDeviceUnsupportedFormatError:\r
+ return "kAudioDeviceUnsupportedFormatError";\r
+\r
+ case kAudioDevicePermissionsError:\r
+ return "kAudioDevicePermissionsError";\r
+\r
+ default:\r
+ return "CoreAudio unknown error";\r
+ }\r
+}\r
+\r
+ //******************** End of __MACOSX_CORE__ *********************//\r
+#endif\r
+\r
+#if defined(__UNIX_JACK__)\r
+\r
+// JACK is a low-latency audio server, originally written for the\r
+// GNU/Linux operating system and now also ported to OS-X. It can\r
+// connect a number of different applications to an audio device, as\r
+// well as allowing them to share audio between themselves.\r
+//\r
+// When using JACK with RtAudio, "devices" refer to JACK clients that\r
+// have ports connected to the server. The JACK server is typically\r
+// started in a terminal as follows:\r
+//\r
+// .jackd -d alsa -d hw:0\r
+//\r
+// or through an interface program such as qjackctl. Many of the\r
+// parameters normally set for a stream are fixed by the JACK server\r
+// and can be specified when the JACK server is started. In\r
+// particular,\r
+//\r
+// .jackd -d alsa -d hw:0 -r 44100 -p 512 -n 4\r
+//\r
+// specifies a sample rate of 44100 Hz, a buffer size of 512 sample\r
+// frames, and number of buffers = 4. Once the server is running, it\r
+// is not possible to override these values. If the values are not\r
+// specified in the command-line, the JACK server uses default values.\r
+//\r
+// The JACK server does not have to be running when an instance of\r
+// RtApiJack is created, though the function getDeviceCount() will\r
+// report 0 devices found until JACK has been started. When no\r
+// devices are available (i.e., the JACK server is not running), a\r
+// stream cannot be opened.\r
+\r
+#include <jack/jack.h>\r
+#include <unistd.h>\r
+#include <cstdio>\r
+\r
+// A structure to hold various information related to the Jack API\r
+// implementation.\r
+struct JackHandle {\r
+ jack_client_t *client;\r
+ jack_port_t **ports[2];\r
+ std::string deviceName[2];\r
+ bool xrun[2];\r
+ pthread_cond_t condition;\r
+ int drainCounter; // Tracks callback counts when draining\r
+ bool internalDrain; // Indicates if stop is initiated from callback or not.\r
+\r
+ JackHandle()\r
+ :client(0), drainCounter(0), internalDrain(false) { ports[0] = 0; ports[1] = 0; xrun[0] = false; xrun[1] = false; }\r
+};\r
+\r
+/* --- Monocasual hack ------------------------------------------------------ */\r
+#ifdef __linux__\r
+void *RtApi :: __HACK__getJackClient() {\r
+ JackHandle *handle = (JackHandle *) stream_.apiHandle;\r
+ return (void*) handle->client;\r
+}\r
+#endif\r
+/* -------------------------------------------------------------------------- */\r
+\r
+static void jackSilentError( const char * ) {}\r
+\r
+RtApiJack :: RtApiJack()\r
+{\r
+ // Nothing to do here.\r
+#if !defined(__RTAUDIO_DEBUG__)\r
+ // Turn off Jack's internal error reporting.\r
+ jack_set_error_function( &jackSilentError );\r
+#endif\r
+}\r
+\r
+RtApiJack :: ~RtApiJack()\r
+{\r
+ if ( stream_.state != STREAM_CLOSED ) closeStream();\r
+}\r
+\r
+unsigned int RtApiJack :: getDeviceCount( void )\r
+{\r
+ // See if we can become a jack client.\r
+ jack_options_t options = (jack_options_t) ( JackNoStartServer ); //JackNullOption;\r
+ jack_status_t *status = NULL;\r
+ jack_client_t *client = jack_client_open( "RtApiJackCount", options, status );\r
+ if ( client == 0 ) return 0;\r
+\r
+ const char **ports;\r
+ std::string port, previousPort;\r
+ unsigned int nChannels = 0, nDevices = 0;\r
+ ports = jack_get_ports( client, NULL, NULL, 0 );\r
+ if ( ports ) {\r
+ // Parse the port names up to the first colon (:).\r
+ size_t iColon = 0;\r
+ do {\r
+ port = (char *) ports[ nChannels ];\r
+ iColon = port.find(":");\r
+ if ( iColon != std::string::npos ) {\r
+ port = port.substr( 0, iColon + 1 );\r
+ if ( port != previousPort ) {\r
+ nDevices++;\r
+ previousPort = port;\r
+ }\r
+ }\r
+ } while ( ports[++nChannels] );\r
+ free( ports );\r
+ }\r
+\r
+ jack_client_close( client );\r
+ return nDevices;\r
+}\r
+\r
+RtAudio::DeviceInfo RtApiJack :: getDeviceInfo( unsigned int device )\r
+{\r
+ RtAudio::DeviceInfo info;\r
+ info.probed = false;\r
+\r
+ jack_options_t options = (jack_options_t) ( JackNoStartServer ); //JackNullOption\r
+ jack_status_t *status = NULL;\r
+ jack_client_t *client = jack_client_open( "RtApiJackInfo", options, status );\r
+ if ( client == 0 ) {\r
+ errorText_ = "RtApiJack::getDeviceInfo: Jack server not found or connection error!";\r
+ error( RtAudioError::WARNING );\r
+ return info;\r
+ }\r
+\r
+ const char **ports;\r
+ std::string port, previousPort;\r
+ unsigned int nPorts = 0, nDevices = 0;\r
+ ports = jack_get_ports( client, NULL, NULL, 0 );\r
+ if ( ports ) {\r
+ // Parse the port names up to the first colon (:).\r
+ size_t iColon = 0;\r
+ do {\r
+ port = (char *) ports[ nPorts ];\r
+ iColon = port.find(":");\r
+ if ( iColon != std::string::npos ) {\r
+ port = port.substr( 0, iColon );\r
+ if ( port != previousPort ) {\r
+ if ( nDevices == device ) info.name = port;\r
+ nDevices++;\r
+ previousPort = port;\r
+ }\r
+ }\r
+ } while ( ports[++nPorts] );\r
+ free( ports );\r
+ }\r
+\r
+ if ( device >= nDevices ) {\r
+ jack_client_close( client );\r
+ errorText_ = "RtApiJack::getDeviceInfo: device ID is invalid!";\r
+ error( RtAudioError::INVALID_USE );\r
+ return info;\r
+ }\r
+\r
+ // Get the current jack server sample rate.\r
+ info.sampleRates.clear();\r
+\r
+ info.preferredSampleRate = jack_get_sample_rate( client );\r
+ info.sampleRates.push_back( info.preferredSampleRate );\r
+\r
+ // Count the available ports containing the client name as device\r
+ // channels. Jack "input ports" equal RtAudio output channels.\r
+ unsigned int nChannels = 0;\r
+ ports = jack_get_ports( client, info.name.c_str(), NULL, JackPortIsInput );\r
+ if ( ports ) {\r
+ while ( ports[ nChannels ] ) nChannels++;\r
+ free( ports );\r
+ info.outputChannels = nChannels;\r
+ }\r
+\r
+ // Jack "output ports" equal RtAudio input channels.\r
+ nChannels = 0;\r
+ ports = jack_get_ports( client, info.name.c_str(), NULL, JackPortIsOutput );\r
+ if ( ports ) {\r
+ while ( ports[ nChannels ] ) nChannels++;\r
+ free( ports );\r
+ info.inputChannels = nChannels;\r
+ }\r
+\r
+ if ( info.outputChannels == 0 && info.inputChannels == 0 ) {\r
+ jack_client_close(client);\r
+ errorText_ = "RtApiJack::getDeviceInfo: error determining Jack input/output channels!";\r
+ error( RtAudioError::WARNING );\r
+ return info;\r
+ }\r
+\r
+ // If device opens for both playback and capture, we determine the channels.\r
+ if ( info.outputChannels > 0 && info.inputChannels > 0 )\r
+ info.duplexChannels = (info.outputChannels > info.inputChannels) ? info.inputChannels : info.outputChannels;\r
+\r
+ // Jack always uses 32-bit floats.\r
+ info.nativeFormats = RTAUDIO_FLOAT32;\r
+\r
+ // Jack doesn't provide default devices so we'll use the first available one.\r
+ if ( device == 0 && info.outputChannels > 0 )\r
+ info.isDefaultOutput = true;\r
+ if ( device == 0 && info.inputChannels > 0 )\r
+ info.isDefaultInput = true;\r
+\r
+ jack_client_close(client);\r
+ info.probed = true;\r
+ return info;\r
+}\r
+\r
+static int jackCallbackHandler( jack_nframes_t nframes, void *infoPointer )\r
+{\r
+ CallbackInfo *info = (CallbackInfo *) infoPointer;\r
+\r
+ RtApiJack *object = (RtApiJack *) info->object;\r
+ if ( object->callbackEvent( (unsigned long) nframes ) == false ) return 1;\r
+\r
+ return 0;\r
+}\r
+\r
+// This function will be called by a spawned thread when the Jack\r
+// server signals that it is shutting down. It is necessary to handle\r
+// it this way because the jackShutdown() function must return before\r
+// the jack_deactivate() function (in closeStream()) will return.\r
+static void *jackCloseStream( void *ptr )\r
+{\r
+ CallbackInfo *info = (CallbackInfo *) ptr;\r
+ RtApiJack *object = (RtApiJack *) info->object;\r
+\r
+ object->closeStream();\r
+\r
+ pthread_exit( NULL );\r
+}\r
+static void jackShutdown( void *infoPointer )\r
+{\r
+ CallbackInfo *info = (CallbackInfo *) infoPointer;\r
+ RtApiJack *object = (RtApiJack *) info->object;\r
+\r
+ // Check current stream state. If stopped, then we'll assume this\r
+ // was called as a result of a call to RtApiJack::stopStream (the\r
+ // deactivation of a client handle causes this function to be called).\r
+ // If not, we'll assume the Jack server is shutting down or some\r
+ // other problem occurred and we should close the stream.\r
+ if ( object->isStreamRunning() == false ) return;\r
+\r
+ ThreadHandle threadId;\r
+ pthread_create( &threadId, NULL, jackCloseStream, info );\r
+ std::cerr << "\nRtApiJack: the Jack server is shutting down this client ... stream stopped and closed!!\n" << std::endl;\r
+}\r
+\r
+static int jackXrun( void *infoPointer )\r
+{\r
+ JackHandle *handle = (JackHandle *) infoPointer;\r
+\r
+ if ( handle->ports[0] ) handle->xrun[0] = true;\r
+ if ( handle->ports[1] ) handle->xrun[1] = true;\r
+\r
+ return 0;\r
+}\r
+\r
+bool RtApiJack :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels,\r
+ unsigned int firstChannel, unsigned int sampleRate,\r
+ RtAudioFormat format, unsigned int *bufferSize,\r
+ RtAudio::StreamOptions *options )\r
+{\r
+ JackHandle *handle = (JackHandle *) stream_.apiHandle;\r
+\r
+ // Look for jack server and try to become a client (only do once per stream).\r
+ jack_client_t *client = 0;\r
+ if ( mode == OUTPUT || ( mode == INPUT && stream_.mode != OUTPUT ) ) {\r
+ jack_options_t jackoptions = (jack_options_t) ( JackNoStartServer ); //JackNullOption;\r
+ jack_status_t *status = NULL;\r
+ if ( options && !options->streamName.empty() )\r
+ client = jack_client_open( options->streamName.c_str(), jackoptions, status );\r
+ else\r
+ client = jack_client_open( "RtApiJack", jackoptions, status );\r
+ if ( client == 0 ) {\r
+ errorText_ = "RtApiJack::probeDeviceOpen: Jack server not found or connection error!";\r
+ error( RtAudioError::WARNING );\r
+ return FAILURE;\r
+ }\r
+ }\r
+ else {\r
+ // The handle must have been created on an earlier pass.\r
+ client = handle->client;\r
+ }\r
+\r
+ const char **ports;\r
+ std::string port, previousPort, deviceName;\r
+ unsigned int nPorts = 0, nDevices = 0;\r
+ ports = jack_get_ports( client, NULL, NULL, 0 );\r
+ if ( ports ) {\r
+ // Parse the port names up to the first colon (:).\r
+ size_t iColon = 0;\r
+ do {\r
+ port = (char *) ports[ nPorts ];\r
+ iColon = port.find(":");\r
+ if ( iColon != std::string::npos ) {\r
+ port = port.substr( 0, iColon );\r
+ if ( port != previousPort ) {\r
+ if ( nDevices == device ) deviceName = port;\r
+ nDevices++;\r
+ previousPort = port;\r
+ }\r
+ }\r
+ } while ( ports[++nPorts] );\r
+ free( ports );\r
+ }\r
+\r
+ if ( device >= nDevices ) {\r
+ errorText_ = "RtApiJack::probeDeviceOpen: device ID is invalid!";\r
+ return FAILURE;\r
+ }\r
+\r
+ // Count the available ports containing the client name as device\r
+ // channels. Jack "input ports" equal RtAudio output channels.\r
+ unsigned int nChannels = 0;\r
+ unsigned long flag = JackPortIsInput;\r
+ if ( mode == INPUT ) flag = JackPortIsOutput;\r
+ ports = jack_get_ports( client, deviceName.c_str(), NULL, flag );\r
+ if ( ports ) {\r
+ while ( ports[ nChannels ] ) nChannels++;\r
+ free( ports );\r
+ }\r
+\r
+ // Compare the jack ports for specified client to the requested number of channels.\r
+ if ( nChannels < (channels + firstChannel) ) {\r
+ errorStream_ << "RtApiJack::probeDeviceOpen: requested number of channels (" << channels << ") + offset (" << firstChannel << ") not found for specified device (" << device << ":" << deviceName << ").";\r
+ errorText_ = errorStream_.str();\r
+ return FAILURE;\r
+ }\r
+\r
+ // Check the jack server sample rate.\r
+ unsigned int jackRate = jack_get_sample_rate( client );\r
+ if ( sampleRate != jackRate ) {\r
+ jack_client_close( client );\r
+ errorStream_ << "RtApiJack::probeDeviceOpen: the requested sample rate (" << sampleRate << ") is different than the JACK server rate (" << jackRate << ").";\r
+ errorText_ = errorStream_.str();\r
+ return FAILURE;\r
+ }\r
+ stream_.sampleRate = jackRate;\r
+\r
+ // Get the latency of the JACK port.\r
+ ports = jack_get_ports( client, deviceName.c_str(), NULL, flag );\r
+ if ( ports[ firstChannel ] ) {\r
+ // Added by Ge Wang\r
+ jack_latency_callback_mode_t cbmode = (mode == INPUT ? JackCaptureLatency : JackPlaybackLatency);\r
+ // the range (usually the min and max are equal)\r
+ jack_latency_range_t latrange; latrange.min = latrange.max = 0;\r
+ // get the latency range\r
+ jack_port_get_latency_range( jack_port_by_name( client, ports[firstChannel] ), cbmode, &latrange );\r
+ // be optimistic, use the min!\r
+ stream_.latency[mode] = latrange.min;\r
+ //stream_.latency[mode] = jack_port_get_latency( jack_port_by_name( client, ports[ firstChannel ] ) );\r
+ }\r
+ free( ports );\r
+\r
+ // The jack server always uses 32-bit floating-point data.\r
+ stream_.deviceFormat[mode] = RTAUDIO_FLOAT32;\r
+ stream_.userFormat = format;\r
+\r
+ if ( options && options->flags & RTAUDIO_NONINTERLEAVED ) stream_.userInterleaved = false;\r
+ else stream_.userInterleaved = true;\r
+\r
+ // Jack always uses non-interleaved buffers.\r
+ stream_.deviceInterleaved[mode] = false;\r
+\r
+ // Jack always provides host byte-ordered data.\r
+ stream_.doByteSwap[mode] = false;\r
+\r
+ // Get the buffer size. The buffer size and number of buffers\r
+ // (periods) is set when the jack server is started.\r
+ stream_.bufferSize = (int) jack_get_buffer_size( client );\r
+ *bufferSize = stream_.bufferSize;\r
+\r
+ stream_.nDeviceChannels[mode] = channels;\r
+ stream_.nUserChannels[mode] = channels;\r
+\r
+ // Set flags for buffer conversion.\r
+ stream_.doConvertBuffer[mode] = false;\r
+ if ( stream_.userFormat != stream_.deviceFormat[mode] )\r
+ stream_.doConvertBuffer[mode] = true;\r
+ if ( stream_.userInterleaved != stream_.deviceInterleaved[mode] &&\r
+ stream_.nUserChannels[mode] > 1 )\r
+ stream_.doConvertBuffer[mode] = true;\r
+\r
+ // Allocate our JackHandle structure for the stream.\r
+ if ( handle == 0 ) {\r
+ try {\r
+ handle = new JackHandle;\r
+ }\r
+ catch ( std::bad_alloc& ) {\r
+ errorText_ = "RtApiJack::probeDeviceOpen: error allocating JackHandle memory.";\r
+ goto error;\r
+ }\r
+\r
+ if ( pthread_cond_init(&handle->condition, NULL) ) {\r
+ errorText_ = "RtApiJack::probeDeviceOpen: error initializing pthread condition variable.";\r
+ goto error;\r
+ }\r
+ stream_.apiHandle = (void *) handle;\r
+ handle->client = client;\r
+ }\r
+ handle->deviceName[mode] = deviceName;\r
+\r
+ // Allocate necessary internal buffers.\r
+ unsigned long bufferBytes;\r
+ bufferBytes = stream_.nUserChannels[mode] * *bufferSize * formatBytes( stream_.userFormat );\r
+ stream_.userBuffer[mode] = (char *) calloc( bufferBytes, 1 );\r
+ if ( stream_.userBuffer[mode] == NULL ) {\r
+ errorText_ = "RtApiJack::probeDeviceOpen: error allocating user buffer memory.";\r
+ goto error;\r
+ }\r
+\r
+ if ( stream_.doConvertBuffer[mode] ) {\r
+\r
+ bool makeBuffer = true;\r
+ if ( mode == OUTPUT )\r
+ bufferBytes = stream_.nDeviceChannels[0] * formatBytes( stream_.deviceFormat[0] );\r
+ else { // mode == INPUT\r
+ bufferBytes = stream_.nDeviceChannels[1] * formatBytes( stream_.deviceFormat[1] );\r
+ if ( stream_.mode == OUTPUT && stream_.deviceBuffer ) {\r
+ unsigned long bytesOut = stream_.nDeviceChannels[0] * formatBytes(stream_.deviceFormat[0]);\r
+ if ( bufferBytes < bytesOut ) makeBuffer = false;\r
+ }\r
+ }\r
+\r
+ if ( makeBuffer ) {\r
+ bufferBytes *= *bufferSize;\r
+ if ( stream_.deviceBuffer ) free( stream_.deviceBuffer );\r
+ stream_.deviceBuffer = (char *) calloc( bufferBytes, 1 );\r
+ if ( stream_.deviceBuffer == NULL ) {\r
+ errorText_ = "RtApiJack::probeDeviceOpen: error allocating device buffer memory.";\r
+ goto error;\r
+ }\r
+ }\r
+ }\r
+\r
+ // Allocate memory for the Jack ports (channels) identifiers.\r
+ handle->ports[mode] = (jack_port_t **) malloc ( sizeof (jack_port_t *) * channels );\r
+ if ( handle->ports[mode] == NULL ) {\r
+ errorText_ = "RtApiJack::probeDeviceOpen: error allocating port memory.";\r
+ goto error;\r
+ }\r
+\r
+ stream_.device[mode] = device;\r
+ stream_.channelOffset[mode] = firstChannel;\r
+ stream_.state = STREAM_STOPPED;\r
+ stream_.callbackInfo.object = (void *) this;\r
+\r
+ if ( stream_.mode == OUTPUT && mode == INPUT )\r
+ // We had already set up the stream for output.\r
+ stream_.mode = DUPLEX;\r
+ else {\r
+ stream_.mode = mode;\r
+ jack_set_process_callback( handle->client, jackCallbackHandler, (void *) &stream_.callbackInfo );\r
+ jack_set_xrun_callback( handle->client, jackXrun, (void *) &handle );\r
+ jack_on_shutdown( handle->client, jackShutdown, (void *) &stream_.callbackInfo );\r
+ }\r
+\r
+ // Register our ports.\r
+ char label[64];\r
+ if ( mode == OUTPUT ) {\r
+ for ( unsigned int i=0; i<stream_.nUserChannels[0]; i++ ) {\r
+ snprintf( label, 64, "outport %d", i );\r
+ handle->ports[0][i] = jack_port_register( handle->client, (const char *)label,\r
+ JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0 );\r
+ }\r
+ }\r
+ else {\r
+ for ( unsigned int i=0; i<stream_.nUserChannels[1]; i++ ) {\r
+ snprintf( label, 64, "inport %d", i );\r
+ handle->ports[1][i] = jack_port_register( handle->client, (const char *)label,\r
+ JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0 );\r
+ }\r
+ }\r
+\r
+ // Setup the buffer conversion information structure. We don't use\r
+ // buffers to do channel offsets, so we override that parameter\r
+ // here.\r
+ if ( stream_.doConvertBuffer[mode] ) setConvertInfo( mode, 0 );\r
+\r
+ return SUCCESS;\r
+\r
+ error:\r
+ if ( handle ) {\r
+ pthread_cond_destroy( &handle->condition );\r
+ jack_client_close( handle->client );\r
+\r
+ if ( handle->ports[0] ) free( handle->ports[0] );\r
+ if ( handle->ports[1] ) free( handle->ports[1] );\r
+\r
+ delete handle;\r
+ stream_.apiHandle = 0;\r
+ }\r
+\r
+ for ( int i=0; i<2; i++ ) {\r
+ if ( stream_.userBuffer[i] ) {\r
+ free( stream_.userBuffer[i] );\r
+ stream_.userBuffer[i] = 0;\r
+ }\r
+ }\r
+\r
+ if ( stream_.deviceBuffer ) {\r
+ free( stream_.deviceBuffer );\r
+ stream_.deviceBuffer = 0;\r
+ }\r
+\r
+ return FAILURE;\r
+}\r
+\r
+void RtApiJack :: closeStream( void )\r
+{\r
+ if ( stream_.state == STREAM_CLOSED ) {\r
+ errorText_ = "RtApiJack::closeStream(): no open stream to close!";\r
+ error( RtAudioError::WARNING );\r
+ return;\r
+ }\r
+\r
+ JackHandle *handle = (JackHandle *) stream_.apiHandle;\r
+ if ( handle ) {\r
+\r
+ if ( stream_.state == STREAM_RUNNING )\r
+ jack_deactivate( handle->client );\r
+\r
+ jack_client_close( handle->client );\r
+ }\r
+\r
+ if ( handle ) {\r
+ if ( handle->ports[0] ) free( handle->ports[0] );\r
+ if ( handle->ports[1] ) free( handle->ports[1] );\r
+ pthread_cond_destroy( &handle->condition );\r
+ delete handle;\r
+ stream_.apiHandle = 0;\r
+ }\r
+\r
+ for ( int i=0; i<2; i++ ) {\r
+ if ( stream_.userBuffer[i] ) {\r
+ free( stream_.userBuffer[i] );\r
+ stream_.userBuffer[i] = 0;\r
+ }\r
+ }\r
+\r
+ if ( stream_.deviceBuffer ) {\r
+ free( stream_.deviceBuffer );\r
+ stream_.deviceBuffer = 0;\r
+ }\r
+\r
+ stream_.mode = UNINITIALIZED;\r
+ stream_.state = STREAM_CLOSED;\r
+}\r
+\r
+void RtApiJack :: startStream( void )\r
+{\r
+ verifyStream();\r
+ if ( stream_.state == STREAM_RUNNING ) {\r
+ errorText_ = "RtApiJack::startStream(): the stream is already running!";\r
+ error( RtAudioError::WARNING );\r
+ return;\r
+ }\r
+\r
+ JackHandle *handle = (JackHandle *) stream_.apiHandle;\r
+ int result = jack_activate( handle->client );\r
+ if ( result ) {\r
+ errorText_ = "RtApiJack::startStream(): unable to activate JACK client!";\r
+ goto unlock;\r
+ }\r
+\r
+ const char **ports;\r
+\r
+ // Get the list of available ports.\r
+ if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) {\r
+ result = 1;\r
+ ports = jack_get_ports( handle->client, handle->deviceName[0].c_str(), NULL, JackPortIsInput);\r
+ if ( ports == NULL) {\r
+ errorText_ = "RtApiJack::startStream(): error determining available JACK input ports!";\r
+ goto unlock;\r
+ }\r
+\r
+ // Now make the port connections. Since RtAudio wasn't designed to\r
+ // allow the user to select particular channels of a device, we'll\r
+ // just open the first "nChannels" ports with offset.\r
+ for ( unsigned int i=0; i<stream_.nUserChannels[0]; i++ ) {\r
+ result = 1;\r
+ if ( ports[ stream_.channelOffset[0] + i ] )\r
+ result = jack_connect( handle->client, jack_port_name( handle->ports[0][i] ), ports[ stream_.channelOffset[0] + i ] );\r
+ if ( result ) {\r
+ free( ports );\r
+ errorText_ = "RtApiJack::startStream(): error connecting output ports!";\r
+ goto unlock;\r
+ }\r
+ }\r
+ free(ports);\r
+ }\r
+\r
+ if ( stream_.mode == INPUT || stream_.mode == DUPLEX ) {\r
+ result = 1;\r
+ ports = jack_get_ports( handle->client, handle->deviceName[1].c_str(), NULL, JackPortIsOutput );\r
+ if ( ports == NULL) {\r
+ errorText_ = "RtApiJack::startStream(): error determining available JACK output ports!";\r
+ goto unlock;\r
+ }\r
+\r
+ // Now make the port connections. See note above.\r
+ for ( unsigned int i=0; i<stream_.nUserChannels[1]; i++ ) {\r
+ result = 1;\r
+ if ( ports[ stream_.channelOffset[1] + i ] )\r
+ result = jack_connect( handle->client, ports[ stream_.channelOffset[1] + i ], jack_port_name( handle->ports[1][i] ) );\r
+ if ( result ) {\r
+ free( ports );\r
+ errorText_ = "RtApiJack::startStream(): error connecting input ports!";\r
+ goto unlock;\r
+ }\r
+ }\r
+ free(ports);\r
+ }\r
+\r
+ handle->drainCounter = 0;\r
+ handle->internalDrain = false;\r
+ stream_.state = STREAM_RUNNING;\r
+\r
+ unlock:\r
+ if ( result == 0 ) return;\r
+ error( RtAudioError::SYSTEM_ERROR );\r
+}\r
+\r
+void RtApiJack :: stopStream( void )\r
+{\r
+ verifyStream();\r
+ if ( stream_.state == STREAM_STOPPED ) {\r
+ errorText_ = "RtApiJack::stopStream(): the stream is already stopped!";\r
+ error( RtAudioError::WARNING );\r
+ return;\r
+ }\r
+\r
+ JackHandle *handle = (JackHandle *) stream_.apiHandle;\r
+ if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) {\r
+\r
+ if ( handle->drainCounter == 0 ) {\r
+ handle->drainCounter = 2;\r
+ pthread_cond_wait( &handle->condition, &stream_.mutex ); // block until signaled\r
+ }\r
+ }\r
+\r
+ jack_deactivate( handle->client );\r
+ stream_.state = STREAM_STOPPED;\r
+}\r
+\r
+void RtApiJack :: abortStream( void )\r
+{\r
+ verifyStream();\r
+ if ( stream_.state == STREAM_STOPPED ) {\r
+ errorText_ = "RtApiJack::abortStream(): the stream is already stopped!";\r
+ error( RtAudioError::WARNING );\r
+ return;\r
+ }\r
+\r
+ JackHandle *handle = (JackHandle *) stream_.apiHandle;\r
+ handle->drainCounter = 2;\r
+\r
+ stopStream();\r
+}\r
+\r
+// This function will be called by a spawned thread when the user\r
+// callback function signals that the stream should be stopped or\r
+// aborted. It is necessary to handle it this way because the\r
+// callbackEvent() function must return before the jack_deactivate()\r
+// function will return.\r
+static void *jackStopStream( void *ptr )\r
+{\r
+ CallbackInfo *info = (CallbackInfo *) ptr;\r
+ RtApiJack *object = (RtApiJack *) info->object;\r
+\r
+ object->stopStream();\r
+ pthread_exit( NULL );\r
+}\r
+\r
+bool RtApiJack :: callbackEvent( unsigned long nframes )\r
+{\r
+ if ( stream_.state == STREAM_STOPPED || stream_.state == STREAM_STOPPING ) return SUCCESS;\r
+ if ( stream_.state == STREAM_CLOSED ) {\r
+ errorText_ = "RtApiCore::callbackEvent(): the stream is closed ... this shouldn't happen!";\r
+ error( RtAudioError::WARNING );\r
+ return FAILURE;\r
+ }\r
+ if ( stream_.bufferSize != nframes ) {\r
+ errorText_ = "RtApiCore::callbackEvent(): the JACK buffer size has changed ... cannot process!";\r
+ error( RtAudioError::WARNING );\r
+ return FAILURE;\r
+ }\r
+\r
+ CallbackInfo *info = (CallbackInfo *) &stream_.callbackInfo;\r
+ JackHandle *handle = (JackHandle *) stream_.apiHandle;\r
+\r
+ // Check if we were draining the stream and signal is finished.\r
+ if ( handle->drainCounter > 3 ) {\r
+ ThreadHandle threadId;\r
+\r
+ stream_.state = STREAM_STOPPING;\r
+ if ( handle->internalDrain == true )\r
+ pthread_create( &threadId, NULL, jackStopStream, info );\r
+ else\r
+ pthread_cond_signal( &handle->condition );\r
+ return SUCCESS;\r
+ }\r
+\r
+ // Invoke user callback first, to get fresh output data.\r
+ if ( handle->drainCounter == 0 ) {\r
+ RtAudioCallback callback = (RtAudioCallback) info->callback;\r
+ double streamTime = getStreamTime();\r
+ RtAudioStreamStatus status = 0;\r
+ if ( stream_.mode != INPUT && handle->xrun[0] == true ) {\r
+ status |= RTAUDIO_OUTPUT_UNDERFLOW;\r
+ handle->xrun[0] = false;\r
+ }\r
+ if ( stream_.mode != OUTPUT && handle->xrun[1] == true ) {\r
+ status |= RTAUDIO_INPUT_OVERFLOW;\r
+ handle->xrun[1] = false;\r
+ }\r
+ int cbReturnValue = callback( stream_.userBuffer[0], stream_.userBuffer[1],\r
+ stream_.bufferSize, streamTime, status, info->userData );\r
+ if ( cbReturnValue == 2 ) {\r
+ stream_.state = STREAM_STOPPING;\r
+ handle->drainCounter = 2;\r
+ ThreadHandle id;\r
+ pthread_create( &id, NULL, jackStopStream, info );\r
+ return SUCCESS;\r
+ }\r
+ else if ( cbReturnValue == 1 ) {\r
+ handle->drainCounter = 1;\r
+ handle->internalDrain = true;\r
+ }\r
+ }\r
+\r
+ jack_default_audio_sample_t *jackbuffer;\r
+ unsigned long bufferBytes = nframes * sizeof( jack_default_audio_sample_t );\r
+ if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) {\r
+\r
+ if ( handle->drainCounter > 1 ) { // write zeros to the output stream\r
+\r
+ for ( unsigned int i=0; i<stream_.nDeviceChannels[0]; i++ ) {\r
+ jackbuffer = (jack_default_audio_sample_t *) jack_port_get_buffer( handle->ports[0][i], (jack_nframes_t) nframes );\r
+ memset( jackbuffer, 0, bufferBytes );\r
+ }\r
+\r
+ }\r
+ else if ( stream_.doConvertBuffer[0] ) {\r
+\r
+ convertBuffer( stream_.deviceBuffer, stream_.userBuffer[0], stream_.convertInfo[0] );\r
+\r
+ for ( unsigned int i=0; i<stream_.nDeviceChannels[0]; i++ ) {\r
+ jackbuffer = (jack_default_audio_sample_t *) jack_port_get_buffer( handle->ports[0][i], (jack_nframes_t) nframes );\r
+ memcpy( jackbuffer, &stream_.deviceBuffer[i*bufferBytes], bufferBytes );\r
+ }\r
+ }\r
+ else { // no buffer conversion\r
+ for ( unsigned int i=0; i<stream_.nUserChannels[0]; i++ ) {\r
+ jackbuffer = (jack_default_audio_sample_t *) jack_port_get_buffer( handle->ports[0][i], (jack_nframes_t) nframes );\r
+ memcpy( jackbuffer, &stream_.userBuffer[0][i*bufferBytes], bufferBytes );\r
+ }\r
+ }\r
+ }\r
+\r
+ // Don't bother draining input\r
+ if ( handle->drainCounter ) {\r
+ handle->drainCounter++;\r
+ goto unlock;\r
+ }\r
+\r
+ if ( stream_.mode == INPUT || stream_.mode == DUPLEX ) {\r
+\r
+ if ( stream_.doConvertBuffer[1] ) {\r
+ for ( unsigned int i=0; i<stream_.nDeviceChannels[1]; i++ ) {\r
+ jackbuffer = (jack_default_audio_sample_t *) jack_port_get_buffer( handle->ports[1][i], (jack_nframes_t) nframes );\r
+ memcpy( &stream_.deviceBuffer[i*bufferBytes], jackbuffer, bufferBytes );\r
+ }\r
+ convertBuffer( stream_.userBuffer[1], stream_.deviceBuffer, stream_.convertInfo[1] );\r
+ }\r
+ else { // no buffer conversion\r
+ for ( unsigned int i=0; i<stream_.nUserChannels[1]; i++ ) {\r
+ jackbuffer = (jack_default_audio_sample_t *) jack_port_get_buffer( handle->ports[1][i], (jack_nframes_t) nframes );\r
+ memcpy( &stream_.userBuffer[1][i*bufferBytes], jackbuffer, bufferBytes );\r
+ }\r
+ }\r
+ }\r
+\r
+ unlock:\r
+ RtApi::tickStreamTime();\r
+ return SUCCESS;\r
+}\r
+ //******************** End of __UNIX_JACK__ *********************//\r
+#endif\r
+\r
+#if defined(__WINDOWS_ASIO__) // ASIO API on Windows\r
+\r
+// The ASIO API is designed around a callback scheme, so this\r
+// implementation is similar to that used for OS-X CoreAudio and Linux\r
+// Jack. The primary constraint with ASIO is that it only allows\r
+// access to a single driver at a time. Thus, it is not possible to\r
+// have more than one simultaneous RtAudio stream.\r
+//\r
+// This implementation also requires a number of external ASIO files\r
+// and a few global variables. The ASIO callback scheme does not\r
+// allow for the passing of user data, so we must create a global\r
+// pointer to our callbackInfo structure.\r
+//\r
+// On unix systems, we make use of a pthread condition variable.\r
+// Since there is no equivalent in Windows, I hacked something based\r
+// on information found in\r
+// http://www.cs.wustl.edu/~schmidt/win32-cv-1.html.\r
+\r
+#include "asiosys.h"\r
+#include "asio.h"\r
+#include "iasiothiscallresolver.h"\r
+#include "asiodrivers.h"\r
+#include <cmath>\r
+\r
+static AsioDrivers drivers;\r
+static ASIOCallbacks asioCallbacks;\r
+static ASIODriverInfo driverInfo;\r
+static CallbackInfo *asioCallbackInfo;\r
+static bool asioXRun;\r
+\r
+struct AsioHandle {\r
+ int drainCounter; // Tracks callback counts when draining\r
+ bool internalDrain; // Indicates if stop is initiated from callback or not.\r
+ ASIOBufferInfo *bufferInfos;\r
+ HANDLE condition;\r
+\r
+ AsioHandle()\r
+ :drainCounter(0), internalDrain(false), bufferInfos(0) {}\r
+};\r
+\r
+// Function declarations (definitions at end of section)\r
+static const char* getAsioErrorString( ASIOError result );\r
+static void sampleRateChanged( ASIOSampleRate sRate );\r
+static long asioMessages( long selector, long value, void* message, double* opt );\r
+\r
+RtApiAsio :: RtApiAsio()\r
+{\r
+ // ASIO cannot run on a multi-threaded appartment. You can call\r
+ // CoInitialize beforehand, but it must be for appartment threading\r
+ // (in which case, CoInitilialize will return S_FALSE here).\r
+ coInitialized_ = false;\r
+ HRESULT hr = CoInitialize( NULL );\r
+ if ( FAILED(hr) ) {\r
+ errorText_ = "RtApiAsio::ASIO requires a single-threaded appartment. Call CoInitializeEx(0,COINIT_APARTMENTTHREADED)";\r
+ error( RtAudioError::WARNING );\r
+ }\r
+ coInitialized_ = true;\r
+\r
+ drivers.removeCurrentDriver();\r
+ driverInfo.asioVersion = 2;\r
+\r
+ // See note in DirectSound implementation about GetDesktopWindow().\r
+ driverInfo.sysRef = GetForegroundWindow();\r
+}\r
+\r
+RtApiAsio :: ~RtApiAsio()\r
+{\r
+ if ( stream_.state != STREAM_CLOSED ) closeStream();\r
+ if ( coInitialized_ ) CoUninitialize();\r
+}\r
+\r
+unsigned int RtApiAsio :: getDeviceCount( void )\r
+{\r
+ return (unsigned int) drivers.asioGetNumDev();\r
+}\r
+\r
+RtAudio::DeviceInfo RtApiAsio :: getDeviceInfo( unsigned int device )\r
+{\r
+ RtAudio::DeviceInfo info;\r
+ info.probed = false;\r
+\r
+ // Get device ID\r
+ unsigned int nDevices = getDeviceCount();\r
+ if ( nDevices == 0 ) {\r
+ errorText_ = "RtApiAsio::getDeviceInfo: no devices found!";\r
+ error( RtAudioError::INVALID_USE );\r
+ return info;\r
+ }\r
+\r
+ if ( device >= nDevices ) {\r
+ errorText_ = "RtApiAsio::getDeviceInfo: device ID is invalid!";\r
+ error( RtAudioError::INVALID_USE );\r
+ return info;\r
+ }\r
+\r
+ // If a stream is already open, we cannot probe other devices. Thus, use the saved results.\r
+ if ( stream_.state != STREAM_CLOSED ) {\r
+ if ( device >= devices_.size() ) {\r
+ errorText_ = "RtApiAsio::getDeviceInfo: device ID was not present before stream was opened.";\r
+ error( RtAudioError::WARNING );\r
+ return info;\r
+ }\r
+ return devices_[ device ];\r
+ }\r
+\r
+ char driverName[32];\r
+ ASIOError result = drivers.asioGetDriverName( (int) device, driverName, 32 );\r
+ if ( result != ASE_OK ) {\r
+ errorStream_ << "RtApiAsio::getDeviceInfo: unable to get driver name (" << getAsioErrorString( result ) << ").";\r
+ errorText_ = errorStream_.str();\r
+ error( RtAudioError::WARNING );\r
+ return info;\r
+ }\r
+\r
+ info.name = driverName;\r
+\r
+ if ( !drivers.loadDriver( driverName ) ) {\r
+ errorStream_ << "RtApiAsio::getDeviceInfo: unable to load driver (" << driverName << ").";\r
+ errorText_ = errorStream_.str();\r
+ error( RtAudioError::WARNING );\r
+ return info;\r
+ }\r
+\r
+ result = ASIOInit( &driverInfo );\r
+ if ( result != ASE_OK ) {\r
+ errorStream_ << "RtApiAsio::getDeviceInfo: error (" << getAsioErrorString( result ) << ") initializing driver (" << driverName << ").";\r
+ errorText_ = errorStream_.str();\r
+ error( RtAudioError::WARNING );\r
+ return info;\r
+ }\r
+\r
+ // Determine the device channel information.\r
+ long inputChannels, outputChannels;\r
+ result = ASIOGetChannels( &inputChannels, &outputChannels );\r
+ if ( result != ASE_OK ) {\r
+ drivers.removeCurrentDriver();\r
+ errorStream_ << "RtApiAsio::getDeviceInfo: error (" << getAsioErrorString( result ) << ") getting channel count (" << driverName << ").";\r
+ errorText_ = errorStream_.str();\r
+ error( RtAudioError::WARNING );\r
+ return info;\r
+ }\r
+\r
+ info.outputChannels = outputChannels;\r
+ info.inputChannels = inputChannels;\r
+ if ( info.outputChannels > 0 && info.inputChannels > 0 )\r
+ info.duplexChannels = (info.outputChannels > info.inputChannels) ? info.inputChannels : info.outputChannels;\r
+\r
+ // Determine the supported sample rates.\r
+ info.sampleRates.clear();\r
+ for ( unsigned int i=0; i<MAX_SAMPLE_RATES; i++ ) {\r
+ result = ASIOCanSampleRate( (ASIOSampleRate) SAMPLE_RATES[i] );\r
+ if ( result == ASE_OK ) {\r
+ info.sampleRates.push_back( SAMPLE_RATES[i] );\r
+\r
+ if ( !info.preferredSampleRate || ( SAMPLE_RATES[i] <= 48000 && SAMPLE_RATES[i] > info.preferredSampleRate ) )\r
+ info.preferredSampleRate = SAMPLE_RATES[i];\r
+ }\r
+ }\r
+\r
+ // Determine supported data types ... just check first channel and assume rest are the same.\r
+ ASIOChannelInfo channelInfo;\r
+ channelInfo.channel = 0;\r
+ channelInfo.isInput = true;\r
+ if ( info.inputChannels <= 0 ) channelInfo.isInput = false;\r
+ result = ASIOGetChannelInfo( &channelInfo );\r
+ if ( result != ASE_OK ) {\r
+ drivers.removeCurrentDriver();\r
+ errorStream_ << "RtApiAsio::getDeviceInfo: error (" << getAsioErrorString( result ) << ") getting driver channel info (" << driverName << ").";\r
+ errorText_ = errorStream_.str();\r
+ error( RtAudioError::WARNING );\r
+ return info;\r
+ }\r
+\r
+ info.nativeFormats = 0;\r
+ if ( channelInfo.type == ASIOSTInt16MSB || channelInfo.type == ASIOSTInt16LSB )\r
+ info.nativeFormats |= RTAUDIO_SINT16;\r
+ else if ( channelInfo.type == ASIOSTInt32MSB || channelInfo.type == ASIOSTInt32LSB )\r
+ info.nativeFormats |= RTAUDIO_SINT32;\r
+ else if ( channelInfo.type == ASIOSTFloat32MSB || channelInfo.type == ASIOSTFloat32LSB )\r
+ info.nativeFormats |= RTAUDIO_FLOAT32;\r
+ else if ( channelInfo.type == ASIOSTFloat64MSB || channelInfo.type == ASIOSTFloat64LSB )\r
+ info.nativeFormats |= RTAUDIO_FLOAT64;\r
+ else if ( channelInfo.type == ASIOSTInt24MSB || channelInfo.type == ASIOSTInt24LSB )\r
+ info.nativeFormats |= RTAUDIO_SINT24;\r
+\r
+ if ( info.outputChannels > 0 )\r
+ if ( getDefaultOutputDevice() == device ) info.isDefaultOutput = true;\r
+ if ( info.inputChannels > 0 )\r
+ if ( getDefaultInputDevice() == device ) info.isDefaultInput = true;\r
+\r
+ info.probed = true;\r
+ drivers.removeCurrentDriver();\r
+ return info;\r
+}\r
+\r
+static void bufferSwitch( long index, ASIOBool /*processNow*/ )\r
+{\r
+ RtApiAsio *object = (RtApiAsio *) asioCallbackInfo->object;\r
+ object->callbackEvent( index );\r
+}\r
+\r
+void RtApiAsio :: saveDeviceInfo( void )\r
+{\r
+ devices_.clear();\r
+\r
+ unsigned int nDevices = getDeviceCount();\r
+ devices_.resize( nDevices );\r
+ for ( unsigned int i=0; i<nDevices; i++ )\r
+ devices_[i] = getDeviceInfo( i );\r
+}\r
+\r
+bool RtApiAsio :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels,\r
+ unsigned int firstChannel, unsigned int sampleRate,\r
+ RtAudioFormat format, unsigned int *bufferSize,\r
+ RtAudio::StreamOptions *options )\r
+{////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\r
+\r
+ bool isDuplexInput = mode == INPUT && stream_.mode == OUTPUT;\r
+\r
+ // For ASIO, a duplex stream MUST use the same driver.\r
+ if ( isDuplexInput && stream_.device[0] != device ) {\r
+ errorText_ = "RtApiAsio::probeDeviceOpen: an ASIO duplex stream must use the same device for input and output!";\r
+ return FAILURE;\r
+ }\r
+\r
+ char driverName[32];\r
+ ASIOError result = drivers.asioGetDriverName( (int) device, driverName, 32 );\r
+ if ( result != ASE_OK ) {\r
+ errorStream_ << "RtApiAsio::probeDeviceOpen: unable to get driver name (" << getAsioErrorString( result ) << ").";\r
+ errorText_ = errorStream_.str();\r
+ return FAILURE;\r
+ }\r
+\r
+ // Only load the driver once for duplex stream.\r
+ if ( !isDuplexInput ) {\r
+ // The getDeviceInfo() function will not work when a stream is open\r
+ // because ASIO does not allow multiple devices to run at the same\r
+ // time. Thus, we'll probe the system before opening a stream and\r
+ // save the results for use by getDeviceInfo().\r
+ this->saveDeviceInfo();\r
+\r
+ if ( !drivers.loadDriver( driverName ) ) {\r
+ errorStream_ << "RtApiAsio::probeDeviceOpen: unable to load driver (" << driverName << ").";\r
+ errorText_ = errorStream_.str();\r
+ return FAILURE;\r
+ }\r
+\r
+ result = ASIOInit( &driverInfo );\r
+ if ( result != ASE_OK ) {\r
+ errorStream_ << "RtApiAsio::probeDeviceOpen: error (" << getAsioErrorString( result ) << ") initializing driver (" << driverName << ").";\r
+ errorText_ = errorStream_.str();\r
+ return FAILURE;\r
+ }\r
+ }\r
+\r
+ // keep them before any "goto error", they are used for error cleanup + goto device boundary checks\r
+ bool buffersAllocated = false;\r
+ AsioHandle *handle = (AsioHandle *) stream_.apiHandle;\r
+ unsigned int nChannels;\r
+\r
+\r
+ // Check the device channel count.\r
+ long inputChannels, outputChannels;\r
+ result = ASIOGetChannels( &inputChannels, &outputChannels );\r
+ if ( result != ASE_OK ) {\r
+ errorStream_ << "RtApiAsio::probeDeviceOpen: error (" << getAsioErrorString( result ) << ") getting channel count (" << driverName << ").";\r
+ errorText_ = errorStream_.str();\r
+ goto error;\r
+ }\r
+\r
+ if ( ( mode == OUTPUT && (channels+firstChannel) > (unsigned int) outputChannels) ||\r
+ ( mode == INPUT && (channels+firstChannel) > (unsigned int) inputChannels) ) {\r
+ errorStream_ << "RtApiAsio::probeDeviceOpen: driver (" << driverName << ") does not support requested channel count (" << channels << ") + offset (" << firstChannel << ").";\r
+ errorText_ = errorStream_.str();\r
+ goto error;\r
+ }\r
+ stream_.nDeviceChannels[mode] = channels;\r
+ stream_.nUserChannels[mode] = channels;\r
+ stream_.channelOffset[mode] = firstChannel;\r
+\r
+ // Verify the sample rate is supported.\r
+ result = ASIOCanSampleRate( (ASIOSampleRate) sampleRate );\r
+ if ( result != ASE_OK ) {\r
+ errorStream_ << "RtApiAsio::probeDeviceOpen: driver (" << driverName << ") does not support requested sample rate (" << sampleRate << ").";\r
+ errorText_ = errorStream_.str();\r
+ goto error;\r
+ }\r
+\r
+ // Get the current sample rate\r
+ ASIOSampleRate currentRate;\r
+ result = ASIOGetSampleRate( ¤tRate );\r
+ if ( result != ASE_OK ) {\r
+ errorStream_ << "RtApiAsio::probeDeviceOpen: driver (" << driverName << ") error getting sample rate.";\r
+ errorText_ = errorStream_.str();\r
+ goto error;\r
+ }\r
+\r
+ // Set the sample rate only if necessary\r
+ if ( currentRate != sampleRate ) {\r
+ result = ASIOSetSampleRate( (ASIOSampleRate) sampleRate );\r
+ if ( result != ASE_OK ) {\r
+ errorStream_ << "RtApiAsio::probeDeviceOpen: driver (" << driverName << ") error setting sample rate (" << sampleRate << ").";\r
+ errorText_ = errorStream_.str();\r
+ goto error;\r
+ }\r
+ }\r
+\r
+ // Determine the driver data type.\r
+ ASIOChannelInfo channelInfo;\r
+ channelInfo.channel = 0;\r
+ if ( mode == OUTPUT ) channelInfo.isInput = false;\r
+ else channelInfo.isInput = true;\r
+ result = ASIOGetChannelInfo( &channelInfo );\r
+ if ( result != ASE_OK ) {\r
+ errorStream_ << "RtApiAsio::probeDeviceOpen: driver (" << driverName << ") error (" << getAsioErrorString( result ) << ") getting data format.";\r
+ errorText_ = errorStream_.str();\r
+ goto error;\r
+ }\r
+\r
+ // Assuming WINDOWS host is always little-endian.\r
+ stream_.doByteSwap[mode] = false;\r
+ stream_.userFormat = format;\r
+ stream_.deviceFormat[mode] = 0;\r
+ if ( channelInfo.type == ASIOSTInt16MSB || channelInfo.type == ASIOSTInt16LSB ) {\r
+ stream_.deviceFormat[mode] = RTAUDIO_SINT16;\r
+ if ( channelInfo.type == ASIOSTInt16MSB ) stream_.doByteSwap[mode] = true;\r
+ }\r
+ else if ( channelInfo.type == ASIOSTInt32MSB || channelInfo.type == ASIOSTInt32LSB ) {\r
+ stream_.deviceFormat[mode] = RTAUDIO_SINT32;\r
+ if ( channelInfo.type == ASIOSTInt32MSB ) stream_.doByteSwap[mode] = true;\r
+ }\r
+ else if ( channelInfo.type == ASIOSTFloat32MSB || channelInfo.type == ASIOSTFloat32LSB ) {\r
+ stream_.deviceFormat[mode] = RTAUDIO_FLOAT32;\r
+ if ( channelInfo.type == ASIOSTFloat32MSB ) stream_.doByteSwap[mode] = true;\r
+ }\r
+ else if ( channelInfo.type == ASIOSTFloat64MSB || channelInfo.type == ASIOSTFloat64LSB ) {\r
+ stream_.deviceFormat[mode] = RTAUDIO_FLOAT64;\r
+ if ( channelInfo.type == ASIOSTFloat64MSB ) stream_.doByteSwap[mode] = true;\r
+ }\r
+ else if ( channelInfo.type == ASIOSTInt24MSB || channelInfo.type == ASIOSTInt24LSB ) {\r
+ stream_.deviceFormat[mode] = RTAUDIO_SINT24;\r
+ if ( channelInfo.type == ASIOSTInt24MSB ) stream_.doByteSwap[mode] = true;\r
+ }\r
+\r
+ if ( stream_.deviceFormat[mode] == 0 ) {\r
+ errorStream_ << "RtApiAsio::probeDeviceOpen: driver (" << driverName << ") data format not supported by RtAudio.";\r
+ errorText_ = errorStream_.str();\r
+ goto error;\r
+ }\r
+\r
+ // Set the buffer size. For a duplex stream, this will end up\r
+ // setting the buffer size based on the input constraints, which\r
+ // should be ok.\r
+ long minSize, maxSize, preferSize, granularity;\r
+ result = ASIOGetBufferSize( &minSize, &maxSize, &preferSize, &granularity );\r
+ if ( result != ASE_OK ) {\r
+ errorStream_ << "RtApiAsio::probeDeviceOpen: driver (" << driverName << ") error (" << getAsioErrorString( result ) << ") getting buffer size.";\r
+ errorText_ = errorStream_.str();\r
+ goto error;\r
+ }\r
+\r
+ if ( isDuplexInput ) {\r
+ // When this is the duplex input (output was opened before), then we have to use the same\r
+ // buffersize as the output, because it might use the preferred buffer size, which most\r
+ // likely wasn't passed as input to this. The buffer sizes have to be identically anyway,\r
+ // So instead of throwing an error, make them equal. The caller uses the reference\r
+ // to the "bufferSize" param as usual to set up processing buffers.\r
+\r
+ *bufferSize = stream_.bufferSize;\r
+\r
+ } else {\r
+ if ( *bufferSize == 0 ) *bufferSize = preferSize;\r
+ else if ( *bufferSize < (unsigned int) minSize ) *bufferSize = (unsigned int) minSize;\r
+ else if ( *bufferSize > (unsigned int) maxSize ) *bufferSize = (unsigned int) maxSize;\r
+ else if ( granularity == -1 ) {\r
+ // Make sure bufferSize is a power of two.\r
+ int log2_of_min_size = 0;\r
+ int log2_of_max_size = 0;\r
+\r
+ for ( unsigned int i = 0; i < sizeof(long) * 8; i++ ) {\r
+ if ( minSize & ((long)1 << i) ) log2_of_min_size = i;\r
+ if ( maxSize & ((long)1 << i) ) log2_of_max_size = i;\r
+ }\r
+\r
+ long min_delta = std::abs( (long)*bufferSize - ((long)1 << log2_of_min_size) );\r
+ int min_delta_num = log2_of_min_size;\r
+\r
+ for (int i = log2_of_min_size + 1; i <= log2_of_max_size; i++) {\r
+ long current_delta = std::abs( (long)*bufferSize - ((long)1 << i) );\r
+ if (current_delta < min_delta) {\r
+ min_delta = current_delta;\r
+ min_delta_num = i;\r
+ }\r
+ }\r
+\r
+ *bufferSize = ( (unsigned int)1 << min_delta_num );\r
+ if ( *bufferSize < (unsigned int) minSize ) *bufferSize = (unsigned int) minSize;\r
+ else if ( *bufferSize > (unsigned int) maxSize ) *bufferSize = (unsigned int) maxSize;\r
+ }\r
+ else if ( granularity != 0 ) {\r
+ // Set to an even multiple of granularity, rounding up.\r
+ *bufferSize = (*bufferSize + granularity-1) / granularity * granularity;\r
+ }\r
+ }\r
+\r
+ /*\r
+ // we don't use it anymore, see above!\r
+ // Just left it here for the case...\r
+ if ( isDuplexInput && stream_.bufferSize != *bufferSize ) {\r
+ errorText_ = "RtApiAsio::probeDeviceOpen: input/output buffersize discrepancy!";\r
+ goto error;\r
+ }\r
+ */\r
+\r
+ stream_.bufferSize = *bufferSize;\r
+ stream_.nBuffers = 2;\r
+\r
+ if ( options && options->flags & RTAUDIO_NONINTERLEAVED ) stream_.userInterleaved = false;\r
+ else stream_.userInterleaved = true;\r
+\r
+ // ASIO always uses non-interleaved buffers.\r
+ stream_.deviceInterleaved[mode] = false;\r
+\r
+ // Allocate, if necessary, our AsioHandle structure for the stream.\r
+ if ( handle == 0 ) {\r
+ try {\r
+ handle = new AsioHandle;\r
+ }\r
+ catch ( std::bad_alloc& ) {\r
+ errorText_ = "RtApiAsio::probeDeviceOpen: error allocating AsioHandle memory.";\r
+ goto error;\r
+ }\r
+ handle->bufferInfos = 0;\r
+\r
+ // Create a manual-reset event.\r
+ handle->condition = CreateEvent( NULL, // no security\r
+ TRUE, // manual-reset\r
+ FALSE, // non-signaled initially\r
+ NULL ); // unnamed\r
+ stream_.apiHandle = (void *) handle;\r
+ }\r
+\r
+ // Create the ASIO internal buffers. Since RtAudio sets up input\r
+ // and output separately, we'll have to dispose of previously\r
+ // created output buffers for a duplex stream.\r
+ if ( mode == INPUT && stream_.mode == OUTPUT ) {\r
+ ASIODisposeBuffers();\r
+ if ( handle->bufferInfos ) free( handle->bufferInfos );\r
+ }\r
+\r
+ // Allocate, initialize, and save the bufferInfos in our stream callbackInfo structure.\r
+ unsigned int i;\r
+ nChannels = stream_.nDeviceChannels[0] + stream_.nDeviceChannels[1];\r
+ handle->bufferInfos = (ASIOBufferInfo *) malloc( nChannels * sizeof(ASIOBufferInfo) );\r
+ if ( handle->bufferInfos == NULL ) {\r
+ errorStream_ << "RtApiAsio::probeDeviceOpen: error allocating bufferInfo memory for driver (" << driverName << ").";\r
+ errorText_ = errorStream_.str();\r
+ goto error;\r
+ }\r
+\r
+ ASIOBufferInfo *infos;\r
+ infos = handle->bufferInfos;\r
+ for ( i=0; i<stream_.nDeviceChannels[0]; i++, infos++ ) {\r
+ infos->isInput = ASIOFalse;\r
+ infos->channelNum = i + stream_.channelOffset[0];\r
+ infos->buffers[0] = infos->buffers[1] = 0;\r
+ }\r
+ for ( i=0; i<stream_.nDeviceChannels[1]; i++, infos++ ) {\r
+ infos->isInput = ASIOTrue;\r
+ infos->channelNum = i + stream_.channelOffset[1];\r
+ infos->buffers[0] = infos->buffers[1] = 0;\r
+ }\r
+\r
+ // prepare for callbacks\r
+ stream_.sampleRate = sampleRate;\r
+ stream_.device[mode] = device;\r
+ stream_.mode = isDuplexInput ? DUPLEX : mode;\r
+\r
+ // store this class instance before registering callbacks, that are going to use it\r
+ asioCallbackInfo = &stream_.callbackInfo;\r
+ stream_.callbackInfo.object = (void *) this;\r
+\r
+ // Set up the ASIO callback structure and create the ASIO data buffers.\r
+ asioCallbacks.bufferSwitch = &bufferSwitch;\r
+ asioCallbacks.sampleRateDidChange = &sampleRateChanged;\r
+ asioCallbacks.asioMessage = &asioMessages;\r
+ asioCallbacks.bufferSwitchTimeInfo = NULL;\r
+ result = ASIOCreateBuffers( handle->bufferInfos, nChannels, stream_.bufferSize, &asioCallbacks );\r
+ if ( result != ASE_OK ) {\r
+ // Standard method failed. This can happen with strict/misbehaving drivers that return valid buffer size ranges\r
+ // but only accept the preferred buffer size as parameter for ASIOCreateBuffers. eg. Creatives ASIO driver\r
+ // in that case, let's be naïve and try that instead\r
+ *bufferSize = preferSize;\r
+ stream_.bufferSize = *bufferSize;\r
+ result = ASIOCreateBuffers( handle->bufferInfos, nChannels, stream_.bufferSize, &asioCallbacks );\r
+ }\r
+\r
+ if ( result != ASE_OK ) {\r
+ errorStream_ << "RtApiAsio::probeDeviceOpen: driver (" << driverName << ") error (" << getAsioErrorString( result ) << ") creating buffers.";\r
+ errorText_ = errorStream_.str();\r
+ goto error;\r
+ }\r
+ buffersAllocated = true;\r
+ stream_.state = STREAM_STOPPED;\r
+\r
+ // Set flags for buffer conversion.\r
+ stream_.doConvertBuffer[mode] = false;\r
+ if ( stream_.userFormat != stream_.deviceFormat[mode] )\r
+ stream_.doConvertBuffer[mode] = true;\r
+ if ( stream_.userInterleaved != stream_.deviceInterleaved[mode] &&\r
+ stream_.nUserChannels[mode] > 1 )\r
+ stream_.doConvertBuffer[mode] = true;\r
+\r
+ // Allocate necessary internal buffers\r
+ unsigned long bufferBytes;\r
+ bufferBytes = stream_.nUserChannels[mode] * *bufferSize * formatBytes( stream_.userFormat );\r
+ stream_.userBuffer[mode] = (char *) calloc( bufferBytes, 1 );\r
+ if ( stream_.userBuffer[mode] == NULL ) {\r
+ errorText_ = "RtApiAsio::probeDeviceOpen: error allocating user buffer memory.";\r
+ goto error;\r
+ }\r
+\r
+ if ( stream_.doConvertBuffer[mode] ) {\r
+\r
+ bool makeBuffer = true;\r
+ bufferBytes = stream_.nDeviceChannels[mode] * formatBytes( stream_.deviceFormat[mode] );\r
+ if ( isDuplexInput && stream_.deviceBuffer ) {\r
+ unsigned long bytesOut = stream_.nDeviceChannels[0] * formatBytes( stream_.deviceFormat[0] );\r
+ if ( bufferBytes <= bytesOut ) makeBuffer = false;\r
+ }\r
+\r
+ if ( makeBuffer ) {\r
+ bufferBytes *= *bufferSize;\r
+ if ( stream_.deviceBuffer ) free( stream_.deviceBuffer );\r
+ stream_.deviceBuffer = (char *) calloc( bufferBytes, 1 );\r
+ if ( stream_.deviceBuffer == NULL ) {\r
+ errorText_ = "RtApiAsio::probeDeviceOpen: error allocating device buffer memory.";\r
+ goto error;\r
+ }\r
+ }\r
+ }\r
+\r
+ // Determine device latencies\r
+ long inputLatency, outputLatency;\r
+ result = ASIOGetLatencies( &inputLatency, &outputLatency );\r
+ if ( result != ASE_OK ) {\r
+ errorStream_ << "RtApiAsio::probeDeviceOpen: driver (" << driverName << ") error (" << getAsioErrorString( result ) << ") getting latency.";\r
+ errorText_ = errorStream_.str();\r
+ error( RtAudioError::WARNING); // warn but don't fail\r
+ }\r
+ else {\r
+ stream_.latency[0] = outputLatency;\r
+ stream_.latency[1] = inputLatency;\r
+ }\r
+\r
+ // Setup the buffer conversion information structure. We don't use\r
+ // buffers to do channel offsets, so we override that parameter\r
+ // here.\r
+ if ( stream_.doConvertBuffer[mode] ) setConvertInfo( mode, 0 );\r
+\r
+ return SUCCESS;\r
+\r
+ error:\r
+ if ( !isDuplexInput ) {\r
+ // the cleanup for error in the duplex input, is done by RtApi::openStream\r
+ // So we clean up for single channel only\r
+\r
+ if ( buffersAllocated )\r
+ ASIODisposeBuffers();\r
+\r
+ drivers.removeCurrentDriver();\r
+\r
+ if ( handle ) {\r
+ CloseHandle( handle->condition );\r
+ if ( handle->bufferInfos )\r
+ free( handle->bufferInfos );\r
+\r
+ delete handle;\r
+ stream_.apiHandle = 0;\r
+ }\r
+\r
+\r
+ if ( stream_.userBuffer[mode] ) {\r
+ free( stream_.userBuffer[mode] );\r
+ stream_.userBuffer[mode] = 0;\r
+ }\r
+\r
+ if ( stream_.deviceBuffer ) {\r
+ free( stream_.deviceBuffer );\r
+ stream_.deviceBuffer = 0;\r
+ }\r
+ }\r
+\r
+ return FAILURE;\r
+}////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\r
+\r
+void RtApiAsio :: closeStream()\r
+{\r
+ if ( stream_.state == STREAM_CLOSED ) {\r
+ errorText_ = "RtApiAsio::closeStream(): no open stream to close!";\r
+ error( RtAudioError::WARNING );\r
+ return;\r
+ }\r
+\r
+ if ( stream_.state == STREAM_RUNNING ) {\r
+ stream_.state = STREAM_STOPPED;\r
+ ASIOStop();\r
+ }\r
+ ASIODisposeBuffers();\r
+ drivers.removeCurrentDriver();\r
+\r
+ AsioHandle *handle = (AsioHandle *) stream_.apiHandle;\r
+ if ( handle ) {\r
+ CloseHandle( handle->condition );\r
+ if ( handle->bufferInfos )\r
+ free( handle->bufferInfos );\r
+ delete handle;\r
+ stream_.apiHandle = 0;\r
+ }\r
+\r
+ for ( int i=0; i<2; i++ ) {\r
+ if ( stream_.userBuffer[i] ) {\r
+ free( stream_.userBuffer[i] );\r
+ stream_.userBuffer[i] = 0;\r
+ }\r
+ }\r
+\r
+ if ( stream_.deviceBuffer ) {\r
+ free( stream_.deviceBuffer );\r
+ stream_.deviceBuffer = 0;\r
+ }\r
+\r
+ stream_.mode = UNINITIALIZED;\r
+ stream_.state = STREAM_CLOSED;\r
+}\r
+\r
+bool stopThreadCalled = false;\r
+\r
+void RtApiAsio :: startStream()\r
+{\r
+ verifyStream();\r
+ if ( stream_.state == STREAM_RUNNING ) {\r
+ errorText_ = "RtApiAsio::startStream(): the stream is already running!";\r
+ error( RtAudioError::WARNING );\r
+ return;\r
+ }\r
+\r
+ AsioHandle *handle = (AsioHandle *) stream_.apiHandle;\r
+ ASIOError result = ASIOStart();\r
+ if ( result != ASE_OK ) {\r
+ errorStream_ << "RtApiAsio::startStream: error (" << getAsioErrorString( result ) << ") starting device.";\r
+ errorText_ = errorStream_.str();\r
+ goto unlock;\r
+ }\r
+\r
+ handle->drainCounter = 0;\r
+ handle->internalDrain = false;\r
+ ResetEvent( handle->condition );\r
+ stream_.state = STREAM_RUNNING;\r
+ asioXRun = false;\r
+\r
+ unlock:\r
+ stopThreadCalled = false;\r
+\r
+ if ( result == ASE_OK ) return;\r
+ error( RtAudioError::SYSTEM_ERROR );\r
+}\r
+\r
+void RtApiAsio :: stopStream()\r
+{\r
+ verifyStream();\r
+ if ( stream_.state == STREAM_STOPPED ) {\r
+ errorText_ = "RtApiAsio::stopStream(): the stream is already stopped!";\r
+ error( RtAudioError::WARNING );\r
+ return;\r
+ }\r
+\r
+ AsioHandle *handle = (AsioHandle *) stream_.apiHandle;\r
+ if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) {\r
+ if ( handle->drainCounter == 0 ) {\r
+ handle->drainCounter = 2;\r
+ WaitForSingleObject( handle->condition, INFINITE ); // block until signaled\r
+ }\r
+ }\r
+\r
+ stream_.state = STREAM_STOPPED;\r
+\r
+ ASIOError result = ASIOStop();\r
+ if ( result != ASE_OK ) {\r
+ errorStream_ << "RtApiAsio::stopStream: error (" << getAsioErrorString( result ) << ") stopping device.";\r
+ errorText_ = errorStream_.str();\r
+ }\r
+\r
+ if ( result == ASE_OK ) return;\r
+ error( RtAudioError::SYSTEM_ERROR );\r
+}\r
+\r
+void RtApiAsio :: abortStream()\r
+{\r
+ verifyStream();\r
+ if ( stream_.state == STREAM_STOPPED ) {\r
+ errorText_ = "RtApiAsio::abortStream(): the stream is already stopped!";\r
+ error( RtAudioError::WARNING );\r
+ return;\r
+ }\r
+\r
+ // The following lines were commented-out because some behavior was\r
+ // noted where the device buffers need to be zeroed to avoid\r
+ // continuing sound, even when the device buffers are completely\r
+ // disposed. So now, calling abort is the same as calling stop.\r
+ // AsioHandle *handle = (AsioHandle *) stream_.apiHandle;\r
+ // handle->drainCounter = 2;\r
+ stopStream();\r
+}\r
+\r
+// This function will be called by a spawned thread when the user\r
+// callback function signals that the stream should be stopped or\r
+// aborted. It is necessary to handle it this way because the\r
+// callbackEvent() function must return before the ASIOStop()\r
+// function will return.\r
+static unsigned __stdcall asioStopStream( void *ptr )\r
+{\r
+ CallbackInfo *info = (CallbackInfo *) ptr;\r
+ RtApiAsio *object = (RtApiAsio *) info->object;\r
+\r
+ object->stopStream();\r
+ _endthreadex( 0 );\r
+ return 0;\r
+}\r
+\r
+bool RtApiAsio :: callbackEvent( long bufferIndex )\r
+{\r
+ if ( stream_.state == STREAM_STOPPED || stream_.state == STREAM_STOPPING ) return SUCCESS;\r
+ if ( stream_.state == STREAM_CLOSED ) {\r
+ errorText_ = "RtApiAsio::callbackEvent(): the stream is closed ... this shouldn't happen!";\r
+ error( RtAudioError::WARNING );\r
+ return FAILURE;\r
+ }\r
+\r
+ CallbackInfo *info = (CallbackInfo *) &stream_.callbackInfo;\r
+ AsioHandle *handle = (AsioHandle *) stream_.apiHandle;\r
+\r
+ // Check if we were draining the stream and signal if finished.\r
+ if ( handle->drainCounter > 3 ) {\r
+\r
+ stream_.state = STREAM_STOPPING;\r
+ if ( handle->internalDrain == false )\r
+ SetEvent( handle->condition );\r
+ else { // spawn a thread to stop the stream\r
+ unsigned threadId;\r
+ stream_.callbackInfo.thread = _beginthreadex( NULL, 0, &asioStopStream,\r
+ &stream_.callbackInfo, 0, &threadId );\r
+ }\r
+ return SUCCESS;\r
+ }\r
+\r
+ // Invoke user callback to get fresh output data UNLESS we are\r
+ // draining stream.\r
+ if ( handle->drainCounter == 0 ) {\r
+ RtAudioCallback callback = (RtAudioCallback) info->callback;\r
+ double streamTime = getStreamTime();\r
+ RtAudioStreamStatus status = 0;\r
+ if ( stream_.mode != INPUT && asioXRun == true ) {\r
+ status |= RTAUDIO_OUTPUT_UNDERFLOW;\r
+ asioXRun = false;\r
+ }\r
+ if ( stream_.mode != OUTPUT && asioXRun == true ) {\r
+ status |= RTAUDIO_INPUT_OVERFLOW;\r
+ asioXRun = false;\r
+ }\r
+ int cbReturnValue = callback( stream_.userBuffer[0], stream_.userBuffer[1],\r
+ stream_.bufferSize, streamTime, status, info->userData );\r
+ if ( cbReturnValue == 2 ) {\r
+ stream_.state = STREAM_STOPPING;\r
+ handle->drainCounter = 2;\r
+ unsigned threadId;\r
+ stream_.callbackInfo.thread = _beginthreadex( NULL, 0, &asioStopStream,\r
+ &stream_.callbackInfo, 0, &threadId );\r
+ return SUCCESS;\r
+ }\r
+ else if ( cbReturnValue == 1 ) {\r
+ handle->drainCounter = 1;\r
+ handle->internalDrain = true;\r
+ }\r
+ }\r
+\r
+ unsigned int nChannels, bufferBytes, i, j;\r
+ nChannels = stream_.nDeviceChannels[0] + stream_.nDeviceChannels[1];\r
+ if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) {\r
+\r
+ bufferBytes = stream_.bufferSize * formatBytes( stream_.deviceFormat[0] );\r
+\r
+ if ( handle->drainCounter > 1 ) { // write zeros to the output stream\r
+\r
+ for ( i=0, j=0; i<nChannels; i++ ) {\r
+ if ( handle->bufferInfos[i].isInput != ASIOTrue )\r
+ memset( handle->bufferInfos[i].buffers[bufferIndex], 0, bufferBytes );\r
+ }\r
+\r
+ }\r
+ else if ( stream_.doConvertBuffer[0] ) {\r
+\r
+ convertBuffer( stream_.deviceBuffer, stream_.userBuffer[0], stream_.convertInfo[0] );\r
+ if ( stream_.doByteSwap[0] )\r
+ byteSwapBuffer( stream_.deviceBuffer,\r
+ stream_.bufferSize * stream_.nDeviceChannels[0],\r
+ stream_.deviceFormat[0] );\r
+\r
+ for ( i=0, j=0; i<nChannels; i++ ) {\r
+ if ( handle->bufferInfos[i].isInput != ASIOTrue )\r
+ memcpy( handle->bufferInfos[i].buffers[bufferIndex],\r
+ &stream_.deviceBuffer[j++*bufferBytes], bufferBytes );\r
+ }\r
+\r
+ }\r
+ else {\r
+\r
+ if ( stream_.doByteSwap[0] )\r
+ byteSwapBuffer( stream_.userBuffer[0],\r
+ stream_.bufferSize * stream_.nUserChannels[0],\r
+ stream_.userFormat );\r
+\r
+ for ( i=0, j=0; i<nChannels; i++ ) {\r
+ if ( handle->bufferInfos[i].isInput != ASIOTrue )\r
+ memcpy( handle->bufferInfos[i].buffers[bufferIndex],\r
+ &stream_.userBuffer[0][bufferBytes*j++], bufferBytes );\r
+ }\r
+\r
+ }\r
+ }\r
+\r
+ // Don't bother draining input\r
+ if ( handle->drainCounter ) {\r
+ handle->drainCounter++;\r
+ goto unlock;\r
+ }\r
+\r
+ if ( stream_.mode == INPUT || stream_.mode == DUPLEX ) {\r
+\r
+ bufferBytes = stream_.bufferSize * formatBytes(stream_.deviceFormat[1]);\r
+\r
+ if (stream_.doConvertBuffer[1]) {\r
+\r
+ // Always interleave ASIO input data.\r
+ for ( i=0, j=0; i<nChannels; i++ ) {\r
+ if ( handle->bufferInfos[i].isInput == ASIOTrue )\r
+ memcpy( &stream_.deviceBuffer[j++*bufferBytes],\r
+ handle->bufferInfos[i].buffers[bufferIndex],\r
+ bufferBytes );\r
+ }\r
+\r
+ if ( stream_.doByteSwap[1] )\r
+ byteSwapBuffer( stream_.deviceBuffer,\r
+ stream_.bufferSize * stream_.nDeviceChannels[1],\r
+ stream_.deviceFormat[1] );\r
+ convertBuffer( stream_.userBuffer[1], stream_.deviceBuffer, stream_.convertInfo[1] );\r
+\r
+ }\r
+ else {\r
+ for ( i=0, j=0; i<nChannels; i++ ) {\r
+ if ( handle->bufferInfos[i].isInput == ASIOTrue ) {\r
+ memcpy( &stream_.userBuffer[1][bufferBytes*j++],\r
+ handle->bufferInfos[i].buffers[bufferIndex],\r
+ bufferBytes );\r
+ }\r
+ }\r
+\r
+ if ( stream_.doByteSwap[1] )\r
+ byteSwapBuffer( stream_.userBuffer[1],\r
+ stream_.bufferSize * stream_.nUserChannels[1],\r
+ stream_.userFormat );\r
+ }\r
+ }\r
+\r
+ unlock:\r
+ // The following call was suggested by Malte Clasen. While the API\r
+ // documentation indicates it should not be required, some device\r
+ // drivers apparently do not function correctly without it.\r
+ ASIOOutputReady();\r
+\r
+ RtApi::tickStreamTime();\r
+ return SUCCESS;\r
+}\r
+\r
+static void sampleRateChanged( ASIOSampleRate sRate )\r
+{\r
+ // The ASIO documentation says that this usually only happens during\r
+ // external sync. Audio processing is not stopped by the driver,\r
+ // actual sample rate might not have even changed, maybe only the\r
+ // sample rate status of an AES/EBU or S/PDIF digital input at the\r
+ // audio device.\r
+\r
+ RtApi *object = (RtApi *) asioCallbackInfo->object;\r
+ try {\r
+ object->stopStream();\r
+ }\r
+ catch ( RtAudioError &exception ) {\r
+ std::cerr << "\nRtApiAsio: sampleRateChanged() error (" << exception.getMessage() << ")!\n" << std::endl;\r
+ return;\r
+ }\r
+\r
+ std::cerr << "\nRtApiAsio: driver reports sample rate changed to " << sRate << " ... stream stopped!!!\n" << std::endl;\r
+}\r
+\r
+static long asioMessages( long selector, long value, void* /*message*/, double* /*opt*/ )\r
+{\r
+ long ret = 0;\r
+\r
+ switch( selector ) {\r
+ case kAsioSelectorSupported:\r
+ if ( value == kAsioResetRequest\r
+ || value == kAsioEngineVersion\r
+ || value == kAsioResyncRequest\r
+ || value == kAsioLatenciesChanged\r
+ // The following three were added for ASIO 2.0, you don't\r
+ // necessarily have to support them.\r
+ || value == kAsioSupportsTimeInfo\r
+ || value == kAsioSupportsTimeCode\r
+ || value == kAsioSupportsInputMonitor)\r
+ ret = 1L;\r
+ break;\r
+ case kAsioResetRequest:\r
+ // Defer the task and perform the reset of the driver during the\r
+ // next "safe" situation. You cannot reset the driver right now,\r
+ // as this code is called from the driver. Reset the driver is\r
+ // done by completely destruct is. I.e. ASIOStop(),\r
+ // ASIODisposeBuffers(), Destruction Afterwards you initialize the\r
+ // driver again.\r
+ std::cerr << "\nRtApiAsio: driver reset requested!!!" << std::endl;\r
+ ret = 1L;\r
+ break;\r
+ case kAsioResyncRequest:\r
+ // This informs the application that the driver encountered some\r
+ // non-fatal data loss. It is used for synchronization purposes\r
+ // of different media. Added mainly to work around the Win16Mutex\r
+ // problems in Windows 95/98 with the Windows Multimedia system,\r
+ // which could lose data because the Mutex was held too long by\r
+ // another thread. However a driver can issue it in other\r
+ // situations, too.\r
+ // std::cerr << "\nRtApiAsio: driver resync requested!!!" << std::endl;\r
+ asioXRun = true;\r
+ ret = 1L;\r
+ break;\r
+ case kAsioLatenciesChanged:\r
+ // This will inform the host application that the drivers were\r
+ // latencies changed. Beware, it this does not mean that the\r
+ // buffer sizes have changed! You might need to update internal\r
+ // delay data.\r
+ std::cerr << "\nRtApiAsio: driver latency may have changed!!!" << std::endl;\r
+ ret = 1L;\r
+ break;\r
+ case kAsioEngineVersion:\r
+ // Return the supported ASIO version of the host application. If\r
+ // a host application does not implement this selector, ASIO 1.0\r
+ // is assumed by the driver.\r
+ ret = 2L;\r
+ break;\r
+ case kAsioSupportsTimeInfo:\r
+ // Informs the driver whether the\r
+ // asioCallbacks.bufferSwitchTimeInfo() callback is supported.\r
+ // For compatibility with ASIO 1.0 drivers the host application\r
+ // should always support the "old" bufferSwitch method, too.\r
+ ret = 0;\r
+ break;\r
+ case kAsioSupportsTimeCode:\r
+ // Informs the driver whether application is interested in time\r
+ // code info. If an application does not need to know about time\r
+ // code, the driver has less work to do.\r
+ ret = 0;\r
+ break;\r
+ }\r
+ return ret;\r
+}\r
+\r
+static const char* getAsioErrorString( ASIOError result )\r
+{\r
+ struct Messages\r
+ {\r
+ ASIOError value;\r
+ const char*message;\r
+ };\r
+\r
+ static const Messages m[] =\r
+ {\r
+ { ASE_NotPresent, "Hardware input or output is not present or available." },\r
+ { ASE_HWMalfunction, "Hardware is malfunctioning." },\r
+ { ASE_InvalidParameter, "Invalid input parameter." },\r
+ { ASE_InvalidMode, "Invalid mode." },\r
+ { ASE_SPNotAdvancing, "Sample position not advancing." },\r
+ { ASE_NoClock, "Sample clock or rate cannot be determined or is not present." },\r
+ { ASE_NoMemory, "Not enough memory to complete the request." }\r
+ };\r
+\r
+ for ( unsigned int i = 0; i < sizeof(m)/sizeof(m[0]); ++i )\r
+ if ( m[i].value == result ) return m[i].message;\r
+\r
+ return "Unknown error.";\r
+}\r
+\r
+//******************** End of __WINDOWS_ASIO__ *********************//\r
+#endif\r
+\r
+\r
+#if defined(__WINDOWS_WASAPI__) // Windows WASAPI API\r
+\r
+// Authored by Marcus Tomlinson <themarcustomlinson@gmail.com>, April 2014\r
+// - Introduces support for the Windows WASAPI API\r
+// - Aims to deliver bit streams to and from hardware at the lowest possible latency, via the absolute minimum buffer sizes required\r
+// - Provides flexible stream configuration to an otherwise strict and inflexible WASAPI interface\r
+// - Includes automatic internal conversion of sample rate and buffer size between hardware and the user\r
+\r
+#ifndef INITGUID\r
+ #define INITGUID\r
+#endif\r
+#include <audioclient.h>\r
+#include <avrt.h>\r
+#include <mmdeviceapi.h>\r
+#include <functiondiscoverykeys_devpkey.h>\r
+\r
+//=============================================================================\r
+\r
+#define SAFE_RELEASE( objectPtr )\\r
+if ( objectPtr )\\r
+{\\r
+ objectPtr->Release();\\r
+ objectPtr = NULL;\\r
+}\r
+\r
+typedef HANDLE ( __stdcall *TAvSetMmThreadCharacteristicsPtr )( LPCWSTR TaskName, LPDWORD TaskIndex );\r
+\r
+//-----------------------------------------------------------------------------\r
+\r
+// WASAPI dictates stream sample rate, format, channel count, and in some cases, buffer size.\r
+// Therefore we must perform all necessary conversions to user buffers in order to satisfy these\r
+// requirements. WasapiBuffer ring buffers are used between HwIn->UserIn and UserOut->HwOut to\r
+// provide intermediate storage for read / write synchronization.\r
+class WasapiBuffer\r
+{\r
+public:\r
+ WasapiBuffer()\r
+ : buffer_( NULL ),\r
+ bufferSize_( 0 ),\r
+ inIndex_( 0 ),\r
+ outIndex_( 0 ) {}\r
+\r
+ ~WasapiBuffer() {\r
+ free( buffer_ );\r
+ }\r
+\r
+ // sets the length of the internal ring buffer\r
+ void setBufferSize( unsigned int bufferSize, unsigned int formatBytes ) {\r
+ free( buffer_ );\r
+\r
+ buffer_ = ( char* ) calloc( bufferSize, formatBytes );\r
+\r
+ bufferSize_ = bufferSize;\r
+ inIndex_ = 0;\r
+ outIndex_ = 0;\r
+ }\r
+\r
+ // attempt to push a buffer into the ring buffer at the current "in" index\r
+ bool pushBuffer( char* buffer, unsigned int bufferSize, RtAudioFormat format )\r
+ {\r
+ if ( !buffer || // incoming buffer is NULL\r
+ bufferSize == 0 || // incoming buffer has no data\r
+ bufferSize > bufferSize_ ) // incoming buffer too large\r
+ {\r
+ return false;\r
+ }\r
+\r
+ unsigned int relOutIndex = outIndex_;\r
+ unsigned int inIndexEnd = inIndex_ + bufferSize;\r
+ if ( relOutIndex < inIndex_ && inIndexEnd >= bufferSize_ ) {\r
+ relOutIndex += bufferSize_;\r
+ }\r
+\r
+ // "in" index can end on the "out" index but cannot begin at it\r
+ if ( inIndex_ <= relOutIndex && inIndexEnd > relOutIndex ) {\r
+ return false; // not enough space between "in" index and "out" index\r
+ }\r
+\r
+ // copy buffer from external to internal\r
+ int fromZeroSize = inIndex_ + bufferSize - bufferSize_;\r
+ fromZeroSize = fromZeroSize < 0 ? 0 : fromZeroSize;\r
+ int fromInSize = bufferSize - fromZeroSize;\r
+\r
+ switch( format )\r
+ {\r
+ case RTAUDIO_SINT8:\r
+ memcpy( &( ( char* ) buffer_ )[inIndex_], buffer, fromInSize * sizeof( char ) );\r
+ memcpy( buffer_, &( ( char* ) buffer )[fromInSize], fromZeroSize * sizeof( char ) );\r
+ break;\r
+ case RTAUDIO_SINT16:\r
+ memcpy( &( ( short* ) buffer_ )[inIndex_], buffer, fromInSize * sizeof( short ) );\r
+ memcpy( buffer_, &( ( short* ) buffer )[fromInSize], fromZeroSize * sizeof( short ) );\r
+ break;\r
+ case RTAUDIO_SINT24:\r
+ memcpy( &( ( S24* ) buffer_ )[inIndex_], buffer, fromInSize * sizeof( S24 ) );\r
+ memcpy( buffer_, &( ( S24* ) buffer )[fromInSize], fromZeroSize * sizeof( S24 ) );\r
+ break;\r
+ case RTAUDIO_SINT32:\r
+ memcpy( &( ( int* ) buffer_ )[inIndex_], buffer, fromInSize * sizeof( int ) );\r
+ memcpy( buffer_, &( ( int* ) buffer )[fromInSize], fromZeroSize * sizeof( int ) );\r
+ break;\r
+ case RTAUDIO_FLOAT32:\r
+ memcpy( &( ( float* ) buffer_ )[inIndex_], buffer, fromInSize * sizeof( float ) );\r
+ memcpy( buffer_, &( ( float* ) buffer )[fromInSize], fromZeroSize * sizeof( float ) );\r
+ break;\r
+ case RTAUDIO_FLOAT64:\r
+ memcpy( &( ( double* ) buffer_ )[inIndex_], buffer, fromInSize * sizeof( double ) );\r
+ memcpy( buffer_, &( ( double* ) buffer )[fromInSize], fromZeroSize * sizeof( double ) );\r
+ break;\r
+ }\r
+\r
+ // update "in" index\r
+ inIndex_ += bufferSize;\r
+ inIndex_ %= bufferSize_;\r
+\r
+ return true;\r
+ }\r
+\r
+ // attempt to pull a buffer from the ring buffer from the current "out" index\r
+ bool pullBuffer( char* buffer, unsigned int bufferSize, RtAudioFormat format )\r
+ {\r
+ if ( !buffer || // incoming buffer is NULL\r
+ bufferSize == 0 || // incoming buffer has no data\r
+ bufferSize > bufferSize_ ) // incoming buffer too large\r
+ {\r
+ return false;\r
+ }\r
+\r
+ unsigned int relInIndex = inIndex_;\r
+ unsigned int outIndexEnd = outIndex_ + bufferSize;\r
+ if ( relInIndex < outIndex_ && outIndexEnd >= bufferSize_ ) {\r
+ relInIndex += bufferSize_;\r
+ }\r
+\r
+ // "out" index can begin at and end on the "in" index\r
+ if ( outIndex_ < relInIndex && outIndexEnd > relInIndex ) {\r
+ return false; // not enough space between "out" index and "in" index\r
+ }\r
+\r
+ // copy buffer from internal to external\r
+ int fromZeroSize = outIndex_ + bufferSize - bufferSize_;\r
+ fromZeroSize = fromZeroSize < 0 ? 0 : fromZeroSize;\r
+ int fromOutSize = bufferSize - fromZeroSize;\r
+\r
+ switch( format )\r
+ {\r
+ case RTAUDIO_SINT8:\r
+ memcpy( buffer, &( ( char* ) buffer_ )[outIndex_], fromOutSize * sizeof( char ) );\r
+ memcpy( &( ( char* ) buffer )[fromOutSize], buffer_, fromZeroSize * sizeof( char ) );\r
+ break;\r
+ case RTAUDIO_SINT16:\r
+ memcpy( buffer, &( ( short* ) buffer_ )[outIndex_], fromOutSize * sizeof( short ) );\r
+ memcpy( &( ( short* ) buffer )[fromOutSize], buffer_, fromZeroSize * sizeof( short ) );\r
+ break;\r
+ case RTAUDIO_SINT24:\r
+ memcpy( buffer, &( ( S24* ) buffer_ )[outIndex_], fromOutSize * sizeof( S24 ) );\r
+ memcpy( &( ( S24* ) buffer )[fromOutSize], buffer_, fromZeroSize * sizeof( S24 ) );\r
+ break;\r
+ case RTAUDIO_SINT32:\r
+ memcpy( buffer, &( ( int* ) buffer_ )[outIndex_], fromOutSize * sizeof( int ) );\r
+ memcpy( &( ( int* ) buffer )[fromOutSize], buffer_, fromZeroSize * sizeof( int ) );\r
+ break;\r
+ case RTAUDIO_FLOAT32:\r
+ memcpy( buffer, &( ( float* ) buffer_ )[outIndex_], fromOutSize * sizeof( float ) );\r
+ memcpy( &( ( float* ) buffer )[fromOutSize], buffer_, fromZeroSize * sizeof( float ) );\r
+ break;\r
+ case RTAUDIO_FLOAT64:\r
+ memcpy( buffer, &( ( double* ) buffer_ )[outIndex_], fromOutSize * sizeof( double ) );\r
+ memcpy( &( ( double* ) buffer )[fromOutSize], buffer_, fromZeroSize * sizeof( double ) );\r
+ break;\r
+ }\r
+\r
+ // update "out" index\r
+ outIndex_ += bufferSize;\r
+ outIndex_ %= bufferSize_;\r
+\r
+ return true;\r
+ }\r
+\r
+private:\r
+ char* buffer_;\r
+ unsigned int bufferSize_;\r
+ unsigned int inIndex_;\r
+ unsigned int outIndex_;\r
+};\r
+\r
+//-----------------------------------------------------------------------------\r
+\r
+// In order to satisfy WASAPI's buffer requirements, we need a means of converting sample rate\r
+// between HW and the user. The convertBufferWasapi function is used to perform this conversion\r
+// between HwIn->UserIn and UserOut->HwOut during the stream callback loop.\r
+// This sample rate converter favors speed over quality, and works best with conversions between\r
+// one rate and its multiple.\r
+void convertBufferWasapi( char* outBuffer,\r
+ const char* inBuffer,\r
+ const unsigned int& channelCount,\r
+ const unsigned int& inSampleRate,\r
+ const unsigned int& outSampleRate,\r
+ const unsigned int& inSampleCount,\r
+ unsigned int& outSampleCount,\r
+ const RtAudioFormat& format )\r
+{\r
+ // calculate the new outSampleCount and relative sampleStep\r
+ float sampleRatio = ( float ) outSampleRate / inSampleRate;\r
+ float sampleStep = 1.0f / sampleRatio;\r
+ float inSampleFraction = 0.0f;\r
+\r
+ outSampleCount = ( unsigned int ) roundf( inSampleCount * sampleRatio );\r
+\r
+ // frame-by-frame, copy each relative input sample into it's corresponding output sample\r
+ for ( unsigned int outSample = 0; outSample < outSampleCount; outSample++ )\r
+ {\r
+ unsigned int inSample = ( unsigned int ) inSampleFraction;\r
+\r
+ switch ( format )\r
+ {\r
+ case RTAUDIO_SINT8:\r
+ memcpy( &( ( char* ) outBuffer )[ outSample * channelCount ], &( ( char* ) inBuffer )[ inSample * channelCount ], channelCount * sizeof( char ) );\r
+ break;\r
+ case RTAUDIO_SINT16:\r
+ memcpy( &( ( short* ) outBuffer )[ outSample * channelCount ], &( ( short* ) inBuffer )[ inSample * channelCount ], channelCount * sizeof( short ) );\r
+ break;\r
+ case RTAUDIO_SINT24:\r
+ memcpy( &( ( S24* ) outBuffer )[ outSample * channelCount ], &( ( S24* ) inBuffer )[ inSample * channelCount ], channelCount * sizeof( S24 ) );\r
+ break;\r
+ case RTAUDIO_SINT32:\r
+ memcpy( &( ( int* ) outBuffer )[ outSample * channelCount ], &( ( int* ) inBuffer )[ inSample * channelCount ], channelCount * sizeof( int ) );\r
+ break;\r
+ case RTAUDIO_FLOAT32:\r
+ memcpy( &( ( float* ) outBuffer )[ outSample * channelCount ], &( ( float* ) inBuffer )[ inSample * channelCount ], channelCount * sizeof( float ) );\r
+ break;\r
+ case RTAUDIO_FLOAT64:\r
+ memcpy( &( ( double* ) outBuffer )[ outSample * channelCount ], &( ( double* ) inBuffer )[ inSample * channelCount ], channelCount * sizeof( double ) );\r
+ break;\r
+ }\r
+\r
+ // jump to next in sample\r
+ inSampleFraction += sampleStep;\r
+ }\r
+}\r
+\r
+//-----------------------------------------------------------------------------\r
+\r
+// A structure to hold various information related to the WASAPI implementation.\r
+struct WasapiHandle\r
+{\r
+ IAudioClient* captureAudioClient;\r
+ IAudioClient* renderAudioClient;\r
+ IAudioCaptureClient* captureClient;\r
+ IAudioRenderClient* renderClient;\r
+ HANDLE captureEvent;\r
+ HANDLE renderEvent;\r
+\r
+ WasapiHandle()\r
+ : captureAudioClient( NULL ),\r
+ renderAudioClient( NULL ),\r
+ captureClient( NULL ),\r
+ renderClient( NULL ),\r
+ captureEvent( NULL ),\r
+ renderEvent( NULL ) {}\r
+};\r
+\r
+//=============================================================================\r
+\r
+RtApiWasapi::RtApiWasapi()\r
+ : coInitialized_( false ), deviceEnumerator_( NULL )\r
+{\r
+ // WASAPI can run either apartment or multi-threaded\r
+ HRESULT hr = CoInitialize( NULL );\r
+ if ( !FAILED( hr ) )\r
+ coInitialized_ = true;\r
+\r
+ // Instantiate device enumerator\r
+ hr = CoCreateInstance( __uuidof( MMDeviceEnumerator ), NULL,\r
+ CLSCTX_ALL, __uuidof( IMMDeviceEnumerator ),\r
+ ( void** ) &deviceEnumerator_ );\r
+\r
+ if ( FAILED( hr ) ) {\r
+ errorText_ = "RtApiWasapi::RtApiWasapi: Unable to instantiate device enumerator";\r
+ error( RtAudioError::DRIVER_ERROR );\r
+ }\r
+}\r
+\r
+//-----------------------------------------------------------------------------\r
+\r
+RtApiWasapi::~RtApiWasapi()\r
+{\r
+ if ( stream_.state != STREAM_CLOSED )\r
+ closeStream();\r
+\r
+ SAFE_RELEASE( deviceEnumerator_ );\r
+\r
+ // If this object previously called CoInitialize()\r
+ if ( coInitialized_ )\r
+ CoUninitialize();\r
+}\r
+\r
+//=============================================================================\r
+\r
+unsigned int RtApiWasapi::getDeviceCount( void )\r
+{\r
+ unsigned int captureDeviceCount = 0;\r
+ unsigned int renderDeviceCount = 0;\r
+\r
+ IMMDeviceCollection* captureDevices = NULL;\r
+ IMMDeviceCollection* renderDevices = NULL;\r
+\r
+ // Count capture devices\r
+ errorText_.clear();\r
+ HRESULT hr = deviceEnumerator_->EnumAudioEndpoints( eCapture, DEVICE_STATE_ACTIVE, &captureDevices );\r
+ if ( FAILED( hr ) ) {\r
+ errorText_ = "RtApiWasapi::getDeviceCount: Unable to retrieve capture device collection.";\r
+ goto Exit;\r
+ }\r
+\r
+ hr = captureDevices->GetCount( &captureDeviceCount );\r
+ if ( FAILED( hr ) ) {\r
+ errorText_ = "RtApiWasapi::getDeviceCount: Unable to retrieve capture device count.";\r
+ goto Exit;\r
+ }\r
+\r
+ // Count render devices\r
+ hr = deviceEnumerator_->EnumAudioEndpoints( eRender, DEVICE_STATE_ACTIVE, &renderDevices );\r
+ if ( FAILED( hr ) ) {\r
+ errorText_ = "RtApiWasapi::getDeviceCount: Unable to retrieve render device collection.";\r
+ goto Exit;\r
+ }\r
+\r
+ hr = renderDevices->GetCount( &renderDeviceCount );\r
+ if ( FAILED( hr ) ) {\r
+ errorText_ = "RtApiWasapi::getDeviceCount: Unable to retrieve render device count.";\r
+ goto Exit;\r
+ }\r
+\r
+Exit:\r
+ // release all references\r
+ SAFE_RELEASE( captureDevices );\r
+ SAFE_RELEASE( renderDevices );\r
+\r
+ if ( errorText_.empty() )\r
+ return captureDeviceCount + renderDeviceCount;\r
+\r
+ error( RtAudioError::DRIVER_ERROR );\r
+ return 0;\r
+}\r
+\r
+//-----------------------------------------------------------------------------\r
+\r
+RtAudio::DeviceInfo RtApiWasapi::getDeviceInfo( unsigned int device )\r
+{\r
+ RtAudio::DeviceInfo info;\r
+ unsigned int captureDeviceCount = 0;\r
+ unsigned int renderDeviceCount = 0;\r
+ std::string defaultDeviceName;\r
+ bool isCaptureDevice = false;\r
+\r
+ PROPVARIANT deviceNameProp;\r
+ PROPVARIANT defaultDeviceNameProp;\r
+\r
+ IMMDeviceCollection* captureDevices = NULL;\r
+ IMMDeviceCollection* renderDevices = NULL;\r
+ IMMDevice* devicePtr = NULL;\r
+ IMMDevice* defaultDevicePtr = NULL;\r
+ IAudioClient* audioClient = NULL;\r
+ IPropertyStore* devicePropStore = NULL;\r
+ IPropertyStore* defaultDevicePropStore = NULL;\r
+\r
+ WAVEFORMATEX* deviceFormat = NULL;\r
+ WAVEFORMATEX* closestMatchFormat = NULL;\r
+\r
+ // probed\r
+ info.probed = false;\r
+\r
+ // Count capture devices\r
+ errorText_.clear();\r
+ RtAudioError::Type errorType = RtAudioError::DRIVER_ERROR;\r
+ HRESULT hr = deviceEnumerator_->EnumAudioEndpoints( eCapture, DEVICE_STATE_ACTIVE, &captureDevices );\r
+ if ( FAILED( hr ) ) {\r
+ errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve capture device collection.";\r
+ goto Exit;\r
+ }\r
+\r
+ hr = captureDevices->GetCount( &captureDeviceCount );\r
+ if ( FAILED( hr ) ) {\r
+ errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve capture device count.";\r
+ goto Exit;\r
+ }\r
+\r
+ // Count render devices\r
+ hr = deviceEnumerator_->EnumAudioEndpoints( eRender, DEVICE_STATE_ACTIVE, &renderDevices );\r
+ if ( FAILED( hr ) ) {\r
+ errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve render device collection.";\r
+ goto Exit;\r
+ }\r
+\r
+ hr = renderDevices->GetCount( &renderDeviceCount );\r
+ if ( FAILED( hr ) ) {\r
+ errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve render device count.";\r
+ goto Exit;\r
+ }\r
+\r
+ // validate device index\r
+ if ( device >= captureDeviceCount + renderDeviceCount ) {\r
+ errorText_ = "RtApiWasapi::getDeviceInfo: Invalid device index.";\r
+ errorType = RtAudioError::INVALID_USE;\r
+ goto Exit;\r
+ }\r
+\r
+ // determine whether index falls within capture or render devices\r
+ if ( device >= renderDeviceCount ) {\r
+ hr = captureDevices->Item( device - renderDeviceCount, &devicePtr );\r
+ if ( FAILED( hr ) ) {\r
+ errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve capture device handle.";\r
+ goto Exit;\r
+ }\r
+ isCaptureDevice = true;\r
+ }\r
+ else {\r
+ hr = renderDevices->Item( device, &devicePtr );\r
+ if ( FAILED( hr ) ) {\r
+ errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve render device handle.";\r
+ goto Exit;\r
+ }\r
+ isCaptureDevice = false;\r
+ }\r
+\r
+ // get default device name\r
+ if ( isCaptureDevice ) {\r
+ hr = deviceEnumerator_->GetDefaultAudioEndpoint( eCapture, eConsole, &defaultDevicePtr );\r
+ if ( FAILED( hr ) ) {\r
+ errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve default capture device handle.";\r
+ goto Exit;\r
+ }\r
+ }\r
+ else {\r
+ hr = deviceEnumerator_->GetDefaultAudioEndpoint( eRender, eConsole, &defaultDevicePtr );\r
+ if ( FAILED( hr ) ) {\r
+ errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve default render device handle.";\r
+ goto Exit;\r
+ }\r
+ }\r
+\r
+ hr = defaultDevicePtr->OpenPropertyStore( STGM_READ, &defaultDevicePropStore );\r
+ if ( FAILED( hr ) ) {\r
+ errorText_ = "RtApiWasapi::getDeviceInfo: Unable to open default device property store.";\r
+ goto Exit;\r
+ }\r
+ PropVariantInit( &defaultDeviceNameProp );\r
+\r
+ hr = defaultDevicePropStore->GetValue( PKEY_Device_FriendlyName, &defaultDeviceNameProp );\r
+ if ( FAILED( hr ) ) {\r
+ errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve default device property: PKEY_Device_FriendlyName.";\r
+ goto Exit;\r
+ }\r
+\r
+ defaultDeviceName = convertCharPointerToStdString(defaultDeviceNameProp.pwszVal);\r
+\r
+ // name\r
+ hr = devicePtr->OpenPropertyStore( STGM_READ, &devicePropStore );\r
+ if ( FAILED( hr ) ) {\r
+ errorText_ = "RtApiWasapi::getDeviceInfo: Unable to open device property store.";\r
+ goto Exit;\r
+ }\r
+\r
+ PropVariantInit( &deviceNameProp );\r
+\r
+ hr = devicePropStore->GetValue( PKEY_Device_FriendlyName, &deviceNameProp );\r
+ if ( FAILED( hr ) ) {\r
+ errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve device property: PKEY_Device_FriendlyName.";\r
+ goto Exit;\r
+ }\r
+\r
+ info.name =convertCharPointerToStdString(deviceNameProp.pwszVal);\r
+\r
+ // is default\r
+ if ( isCaptureDevice ) {\r
+ info.isDefaultInput = info.name == defaultDeviceName;\r
+ info.isDefaultOutput = false;\r
+ }\r
+ else {\r
+ info.isDefaultInput = false;\r
+ info.isDefaultOutput = info.name == defaultDeviceName;\r
+ }\r
+\r
+ // channel count\r
+ hr = devicePtr->Activate( __uuidof( IAudioClient ), CLSCTX_ALL, NULL, ( void** ) &audioClient );\r
+ if ( FAILED( hr ) ) {\r
+ errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve device audio client.";\r
+ goto Exit;\r
+ }\r
+\r
+ hr = audioClient->GetMixFormat( &deviceFormat );\r
+ if ( FAILED( hr ) ) {\r
+ errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve device mix format.";\r
+ goto Exit;\r
+ }\r
+\r
+ if ( isCaptureDevice ) {\r
+ info.inputChannels = deviceFormat->nChannels;\r
+ info.outputChannels = 0;\r
+ info.duplexChannels = 0;\r
+ }\r
+ else {\r
+ info.inputChannels = 0;\r
+ info.outputChannels = deviceFormat->nChannels;\r
+ info.duplexChannels = 0;\r
+ }\r
+\r
+ // sample rates\r
+ info.sampleRates.clear();\r
+\r
+ // allow support for all sample rates as we have a built-in sample rate converter\r
+ for ( unsigned int i = 0; i < MAX_SAMPLE_RATES; i++ ) {\r
+ info.sampleRates.push_back( SAMPLE_RATES[i] );\r
+ }\r
+ info.preferredSampleRate = deviceFormat->nSamplesPerSec;\r
+\r
+ // native format\r
+ info.nativeFormats = 0;\r
+\r
+ if ( deviceFormat->wFormatTag == WAVE_FORMAT_IEEE_FLOAT ||\r
+ ( deviceFormat->wFormatTag == WAVE_FORMAT_EXTENSIBLE &&\r
+ ( ( WAVEFORMATEXTENSIBLE* ) deviceFormat )->SubFormat == KSDATAFORMAT_SUBTYPE_IEEE_FLOAT ) )\r
+ {\r
+ if ( deviceFormat->wBitsPerSample == 32 ) {\r
+ info.nativeFormats |= RTAUDIO_FLOAT32;\r
+ }\r
+ else if ( deviceFormat->wBitsPerSample == 64 ) {\r
+ info.nativeFormats |= RTAUDIO_FLOAT64;\r
+ }\r
+ }\r
+ else if ( deviceFormat->wFormatTag == WAVE_FORMAT_PCM ||\r
+ ( deviceFormat->wFormatTag == WAVE_FORMAT_EXTENSIBLE &&\r
+ ( ( WAVEFORMATEXTENSIBLE* ) deviceFormat )->SubFormat == KSDATAFORMAT_SUBTYPE_PCM ) )\r
+ {\r
+ if ( deviceFormat->wBitsPerSample == 8 ) {\r
+ info.nativeFormats |= RTAUDIO_SINT8;\r
+ }\r
+ else if ( deviceFormat->wBitsPerSample == 16 ) {\r
+ info.nativeFormats |= RTAUDIO_SINT16;\r
+ }\r
+ else if ( deviceFormat->wBitsPerSample == 24 ) {\r
+ info.nativeFormats |= RTAUDIO_SINT24;\r
+ }\r
+ else if ( deviceFormat->wBitsPerSample == 32 ) {\r
+ info.nativeFormats |= RTAUDIO_SINT32;\r
+ }\r
+ }\r
+\r
+ // probed\r
+ info.probed = true;\r
+\r
+Exit:\r
+ // release all references\r
+ PropVariantClear( &deviceNameProp );\r
+ PropVariantClear( &defaultDeviceNameProp );\r
+\r
+ SAFE_RELEASE( captureDevices );\r
+ SAFE_RELEASE( renderDevices );\r
+ SAFE_RELEASE( devicePtr );\r
+ SAFE_RELEASE( defaultDevicePtr );\r
+ SAFE_RELEASE( audioClient );\r
+ SAFE_RELEASE( devicePropStore );\r
+ SAFE_RELEASE( defaultDevicePropStore );\r
+\r
+ CoTaskMemFree( deviceFormat );\r
+ CoTaskMemFree( closestMatchFormat );\r
+\r
+ if ( !errorText_.empty() )\r
+ error( errorType );\r
+ return info;\r
+}\r
+\r
+//-----------------------------------------------------------------------------\r
+\r
+unsigned int RtApiWasapi::getDefaultOutputDevice( void )\r
+{\r
+ for ( unsigned int i = 0; i < getDeviceCount(); i++ ) {\r
+ if ( getDeviceInfo( i ).isDefaultOutput ) {\r
+ return i;\r
+ }\r
+ }\r
+\r
+ return 0;\r
+}\r
+\r
+//-----------------------------------------------------------------------------\r
+\r
+unsigned int RtApiWasapi::getDefaultInputDevice( void )\r
+{\r
+ for ( unsigned int i = 0; i < getDeviceCount(); i++ ) {\r
+ if ( getDeviceInfo( i ).isDefaultInput ) {\r
+ return i;\r
+ }\r
+ }\r
+\r
+ return 0;\r
+}\r
+\r
+//-----------------------------------------------------------------------------\r
+\r
+void RtApiWasapi::closeStream( void )\r
+{\r
+ if ( stream_.state == STREAM_CLOSED ) {\r
+ errorText_ = "RtApiWasapi::closeStream: No open stream to close.";\r
+ error( RtAudioError::WARNING );\r
+ return;\r
+ }\r
+\r
+ if ( stream_.state != STREAM_STOPPED )\r
+ stopStream();\r
+\r
+ // clean up stream memory\r
+ SAFE_RELEASE( ( ( WasapiHandle* ) stream_.apiHandle )->captureAudioClient )\r
+ SAFE_RELEASE( ( ( WasapiHandle* ) stream_.apiHandle )->renderAudioClient )\r
+\r
+ SAFE_RELEASE( ( ( WasapiHandle* ) stream_.apiHandle )->captureClient )\r
+ SAFE_RELEASE( ( ( WasapiHandle* ) stream_.apiHandle )->renderClient )\r
+\r
+ if ( ( ( WasapiHandle* ) stream_.apiHandle )->captureEvent )\r
+ CloseHandle( ( ( WasapiHandle* ) stream_.apiHandle )->captureEvent );\r
+\r
+ if ( ( ( WasapiHandle* ) stream_.apiHandle )->renderEvent )\r
+ CloseHandle( ( ( WasapiHandle* ) stream_.apiHandle )->renderEvent );\r
+\r
+ delete ( WasapiHandle* ) stream_.apiHandle;\r
+ stream_.apiHandle = NULL;\r
+\r
+ for ( int i = 0; i < 2; i++ ) {\r
+ if ( stream_.userBuffer[i] ) {\r
+ free( stream_.userBuffer[i] );\r
+ stream_.userBuffer[i] = 0;\r
+ }\r
+ }\r
+\r
+ if ( stream_.deviceBuffer ) {\r
+ free( stream_.deviceBuffer );\r
+ stream_.deviceBuffer = 0;\r
+ }\r
+\r
+ // update stream state\r
+ stream_.state = STREAM_CLOSED;\r
+}\r
+\r
+//-----------------------------------------------------------------------------\r
+\r
+void RtApiWasapi::startStream( void )\r
+{\r
+ verifyStream();\r
+\r
+ if ( stream_.state == STREAM_RUNNING ) {\r
+ errorText_ = "RtApiWasapi::startStream: The stream is already running.";\r
+ error( RtAudioError::WARNING );\r
+ return;\r
+ }\r
+\r
+ // update stream state\r
+ stream_.state = STREAM_RUNNING;\r
+\r
+ // create WASAPI stream thread\r
+ stream_.callbackInfo.thread = ( ThreadHandle ) CreateThread( NULL, 0, runWasapiThread, this, CREATE_SUSPENDED, NULL );\r
+\r
+ if ( !stream_.callbackInfo.thread ) {\r
+ errorText_ = "RtApiWasapi::startStream: Unable to instantiate callback thread.";\r
+ error( RtAudioError::THREAD_ERROR );\r
+ }\r
+ else {\r
+ SetThreadPriority( ( void* ) stream_.callbackInfo.thread, stream_.callbackInfo.priority );\r
+ ResumeThread( ( void* ) stream_.callbackInfo.thread );\r
+ }\r
+}\r
+\r
+//-----------------------------------------------------------------------------\r
+\r
+void RtApiWasapi::stopStream( void )\r
+{\r
+ verifyStream();\r
+\r
+ if ( stream_.state == STREAM_STOPPED ) {\r
+ errorText_ = "RtApiWasapi::stopStream: The stream is already stopped.";\r
+ error( RtAudioError::WARNING );\r
+ return;\r
+ }\r
+\r
+ // inform stream thread by setting stream state to STREAM_STOPPING\r
+ stream_.state = STREAM_STOPPING;\r
+\r
+ // wait until stream thread is stopped\r
+ while( stream_.state != STREAM_STOPPED ) {\r
+ Sleep( 1 );\r
+ }\r
+\r
+ // Wait for the last buffer to play before stopping.\r
+ Sleep( 1000 * stream_.bufferSize / stream_.sampleRate );\r
+\r
+ // stop capture client if applicable\r
+ if ( ( ( WasapiHandle* ) stream_.apiHandle )->captureAudioClient ) {\r
+ HRESULT hr = ( ( WasapiHandle* ) stream_.apiHandle )->captureAudioClient->Stop();\r
+ if ( FAILED( hr ) ) {\r
+ errorText_ = "RtApiWasapi::stopStream: Unable to stop capture stream.";\r
+ error( RtAudioError::DRIVER_ERROR );\r
+ return;\r
+ }\r
+ }\r
+\r
+ // stop render client if applicable\r
+ if ( ( ( WasapiHandle* ) stream_.apiHandle )->renderAudioClient ) {\r
+ HRESULT hr = ( ( WasapiHandle* ) stream_.apiHandle )->renderAudioClient->Stop();\r
+ if ( FAILED( hr ) ) {\r
+ errorText_ = "RtApiWasapi::stopStream: Unable to stop render stream.";\r
+ error( RtAudioError::DRIVER_ERROR );\r
+ return;\r
+ }\r
+ }\r
+\r
+ // close thread handle\r
+ if ( stream_.callbackInfo.thread && !CloseHandle( ( void* ) stream_.callbackInfo.thread ) ) {\r
+ errorText_ = "RtApiWasapi::stopStream: Unable to close callback thread.";\r
+ error( RtAudioError::THREAD_ERROR );\r
+ return;\r
+ }\r
+\r
+ stream_.callbackInfo.thread = (ThreadHandle) NULL;\r
+}\r
+\r
+//-----------------------------------------------------------------------------\r
+\r
+void RtApiWasapi::abortStream( void )\r
+{\r
+ verifyStream();\r
+\r
+ if ( stream_.state == STREAM_STOPPED ) {\r
+ errorText_ = "RtApiWasapi::abortStream: The stream is already stopped.";\r
+ error( RtAudioError::WARNING );\r
+ return;\r
+ }\r
+\r
+ // inform stream thread by setting stream state to STREAM_STOPPING\r
+ stream_.state = STREAM_STOPPING;\r
+\r
+ // wait until stream thread is stopped\r
+ while ( stream_.state != STREAM_STOPPED ) {\r
+ Sleep( 1 );\r
+ }\r
+\r
+ // stop capture client if applicable\r
+ if ( ( ( WasapiHandle* ) stream_.apiHandle )->captureAudioClient ) {\r
+ HRESULT hr = ( ( WasapiHandle* ) stream_.apiHandle )->captureAudioClient->Stop();\r
+ if ( FAILED( hr ) ) {\r
+ errorText_ = "RtApiWasapi::abortStream: Unable to stop capture stream.";\r
+ error( RtAudioError::DRIVER_ERROR );\r
+ return;\r
+ }\r
+ }\r
+\r
+ // stop render client if applicable\r
+ if ( ( ( WasapiHandle* ) stream_.apiHandle )->renderAudioClient ) {\r
+ HRESULT hr = ( ( WasapiHandle* ) stream_.apiHandle )->renderAudioClient->Stop();\r
+ if ( FAILED( hr ) ) {\r
+ errorText_ = "RtApiWasapi::abortStream: Unable to stop render stream.";\r
+ error( RtAudioError::DRIVER_ERROR );\r
+ return;\r
+ }\r
+ }\r
+\r
+ // close thread handle\r
+ if ( stream_.callbackInfo.thread && !CloseHandle( ( void* ) stream_.callbackInfo.thread ) ) {\r
+ errorText_ = "RtApiWasapi::abortStream: Unable to close callback thread.";\r
+ error( RtAudioError::THREAD_ERROR );\r
+ return;\r
+ }\r
+\r
+ stream_.callbackInfo.thread = (ThreadHandle) NULL;\r
+}\r
+\r
+//-----------------------------------------------------------------------------\r
+\r
+bool RtApiWasapi::probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels,\r
+ unsigned int firstChannel, unsigned int sampleRate,\r
+ RtAudioFormat format, unsigned int* bufferSize,\r
+ RtAudio::StreamOptions* options )\r
+{\r
+ bool methodResult = FAILURE;\r
+ unsigned int captureDeviceCount = 0;\r
+ unsigned int renderDeviceCount = 0;\r
+\r
+ IMMDeviceCollection* captureDevices = NULL;\r
+ IMMDeviceCollection* renderDevices = NULL;\r
+ IMMDevice* devicePtr = NULL;\r
+ WAVEFORMATEX* deviceFormat = NULL;\r
+ unsigned int bufferBytes;\r
+ stream_.state = STREAM_STOPPED;\r
+\r
+ // create API Handle if not already created\r
+ if ( !stream_.apiHandle )\r
+ stream_.apiHandle = ( void* ) new WasapiHandle();\r
+\r
+ // Count capture devices\r
+ errorText_.clear();\r
+ RtAudioError::Type errorType = RtAudioError::DRIVER_ERROR;\r
+ HRESULT hr = deviceEnumerator_->EnumAudioEndpoints( eCapture, DEVICE_STATE_ACTIVE, &captureDevices );\r
+ if ( FAILED( hr ) ) {\r
+ errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve capture device collection.";\r
+ goto Exit;\r
+ }\r
+\r
+ hr = captureDevices->GetCount( &captureDeviceCount );\r
+ if ( FAILED( hr ) ) {\r
+ errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve capture device count.";\r
+ goto Exit;\r
+ }\r
+\r
+ // Count render devices\r
+ hr = deviceEnumerator_->EnumAudioEndpoints( eRender, DEVICE_STATE_ACTIVE, &renderDevices );\r
+ if ( FAILED( hr ) ) {\r
+ errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve render device collection.";\r
+ goto Exit;\r
+ }\r
+\r
+ hr = renderDevices->GetCount( &renderDeviceCount );\r
+ if ( FAILED( hr ) ) {\r
+ errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve render device count.";\r
+ goto Exit;\r
+ }\r
+\r
+ // validate device index\r
+ if ( device >= captureDeviceCount + renderDeviceCount ) {\r
+ errorType = RtAudioError::INVALID_USE;\r
+ errorText_ = "RtApiWasapi::probeDeviceOpen: Invalid device index.";\r
+ goto Exit;\r
+ }\r
+\r
+ // determine whether index falls within capture or render devices\r
+ if ( device >= renderDeviceCount ) {\r
+ if ( mode != INPUT ) {\r
+ errorType = RtAudioError::INVALID_USE;\r
+ errorText_ = "RtApiWasapi::probeDeviceOpen: Capture device selected as output device.";\r
+ goto Exit;\r
+ }\r
+\r
+ // retrieve captureAudioClient from devicePtr\r
+ IAudioClient*& captureAudioClient = ( ( WasapiHandle* ) stream_.apiHandle )->captureAudioClient;\r
+\r
+ hr = captureDevices->Item( device - renderDeviceCount, &devicePtr );\r
+ if ( FAILED( hr ) ) {\r
+ errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve capture device handle.";\r
+ goto Exit;\r
+ }\r
+\r
+ hr = devicePtr->Activate( __uuidof( IAudioClient ), CLSCTX_ALL,\r
+ NULL, ( void** ) &captureAudioClient );\r
+ if ( FAILED( hr ) ) {\r
+ errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve device audio client.";\r
+ goto Exit;\r
+ }\r
+\r
+ hr = captureAudioClient->GetMixFormat( &deviceFormat );\r
+ if ( FAILED( hr ) ) {\r
+ errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve device mix format.";\r
+ goto Exit;\r
+ }\r
+\r
+ stream_.nDeviceChannels[mode] = deviceFormat->nChannels;\r
+ captureAudioClient->GetStreamLatency( ( long long* ) &stream_.latency[mode] );\r
+ }\r
+ else {\r
+ if ( mode != OUTPUT ) {\r
+ errorType = RtAudioError::INVALID_USE;\r
+ errorText_ = "RtApiWasapi::probeDeviceOpen: Render device selected as input device.";\r
+ goto Exit;\r
+ }\r
+\r
+ // retrieve renderAudioClient from devicePtr\r
+ IAudioClient*& renderAudioClient = ( ( WasapiHandle* ) stream_.apiHandle )->renderAudioClient;\r
+\r
+ hr = renderDevices->Item( device, &devicePtr );\r
+ if ( FAILED( hr ) ) {\r
+ errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve render device handle.";\r
+ goto Exit;\r
+ }\r
+\r
+ hr = devicePtr->Activate( __uuidof( IAudioClient ), CLSCTX_ALL,\r
+ NULL, ( void** ) &renderAudioClient );\r
+ if ( FAILED( hr ) ) {\r
+ errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve device audio client.";\r
+ goto Exit;\r
+ }\r
+\r
+ hr = renderAudioClient->GetMixFormat( &deviceFormat );\r
+ if ( FAILED( hr ) ) {\r
+ errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve device mix format.";\r
+ goto Exit;\r
+ }\r
+\r
+ stream_.nDeviceChannels[mode] = deviceFormat->nChannels;\r
+ renderAudioClient->GetStreamLatency( ( long long* ) &stream_.latency[mode] );\r
+ }\r
+\r
+ // fill stream data\r
+ if ( ( stream_.mode == OUTPUT && mode == INPUT ) ||\r
+ ( stream_.mode == INPUT && mode == OUTPUT ) ) {\r
+ stream_.mode = DUPLEX;\r
+ }\r
+ else {\r
+ stream_.mode = mode;\r
+ }\r
+\r
+ stream_.device[mode] = device;\r
+ stream_.doByteSwap[mode] = false;\r
+ stream_.sampleRate = sampleRate;\r
+ stream_.bufferSize = *bufferSize;\r
+ stream_.nBuffers = 1;\r
+ stream_.nUserChannels[mode] = channels;\r
+ stream_.channelOffset[mode] = firstChannel;\r
+ stream_.userFormat = format;\r
+ stream_.deviceFormat[mode] = getDeviceInfo( device ).nativeFormats;\r
+\r
+ if ( options && options->flags & RTAUDIO_NONINTERLEAVED )\r
+ stream_.userInterleaved = false;\r
+ else\r
+ stream_.userInterleaved = true;\r
+ stream_.deviceInterleaved[mode] = true;\r
+\r
+ // Set flags for buffer conversion.\r
+ stream_.doConvertBuffer[mode] = false;\r
+ if ( stream_.userFormat != stream_.deviceFormat[mode] ||\r
+ stream_.nUserChannels != stream_.nDeviceChannels )\r
+ stream_.doConvertBuffer[mode] = true;\r
+ else if ( stream_.userInterleaved != stream_.deviceInterleaved[mode] &&\r
+ stream_.nUserChannels[mode] > 1 )\r
+ stream_.doConvertBuffer[mode] = true;\r
+\r
+ if ( stream_.doConvertBuffer[mode] )\r
+ setConvertInfo( mode, 0 );\r
+\r
+ // Allocate necessary internal buffers\r
+ bufferBytes = stream_.nUserChannels[mode] * stream_.bufferSize * formatBytes( stream_.userFormat );\r
+\r
+ stream_.userBuffer[mode] = ( char* ) calloc( bufferBytes, 1 );\r
+ if ( !stream_.userBuffer[mode] ) {\r
+ errorType = RtAudioError::MEMORY_ERROR;\r
+ errorText_ = "RtApiWasapi::probeDeviceOpen: Error allocating user buffer memory.";\r
+ goto Exit;\r
+ }\r
+\r
+ if ( options && options->flags & RTAUDIO_SCHEDULE_REALTIME )\r
+ stream_.callbackInfo.priority = 15;\r
+ else\r
+ stream_.callbackInfo.priority = 0;\r
+\r
+ ///! TODO: RTAUDIO_MINIMIZE_LATENCY // Provide stream buffers directly to callback\r
+ ///! TODO: RTAUDIO_HOG_DEVICE // Exclusive mode\r
+\r
+ methodResult = SUCCESS;\r
+\r
+Exit:\r
+ //clean up\r
+ SAFE_RELEASE( captureDevices );\r
+ SAFE_RELEASE( renderDevices );\r
+ SAFE_RELEASE( devicePtr );\r
+ CoTaskMemFree( deviceFormat );\r
+\r
+ // if method failed, close the stream\r
+ if ( methodResult == FAILURE )\r
+ closeStream();\r
+\r
+ if ( !errorText_.empty() )\r
+ error( errorType );\r
+ return methodResult;\r
+}\r
+\r
+//=============================================================================\r
+\r
+DWORD WINAPI RtApiWasapi::runWasapiThread( void* wasapiPtr )\r
+{\r
+ if ( wasapiPtr )\r
+ ( ( RtApiWasapi* ) wasapiPtr )->wasapiThread();\r
+\r
+ return 0;\r
+}\r
+\r
+DWORD WINAPI RtApiWasapi::stopWasapiThread( void* wasapiPtr )\r
+{\r
+ if ( wasapiPtr )\r
+ ( ( RtApiWasapi* ) wasapiPtr )->stopStream();\r
+\r
+ return 0;\r
+}\r
+\r
+DWORD WINAPI RtApiWasapi::abortWasapiThread( void* wasapiPtr )\r
+{\r
+ if ( wasapiPtr )\r
+ ( ( RtApiWasapi* ) wasapiPtr )->abortStream();\r
+\r
+ return 0;\r
+}\r
+\r
+//-----------------------------------------------------------------------------\r
+\r
+void RtApiWasapi::wasapiThread()\r
+{\r
+ // as this is a new thread, we must CoInitialize it\r
+ CoInitialize( NULL );\r
+\r
+ HRESULT hr;\r
+\r
+ IAudioClient* captureAudioClient = ( ( WasapiHandle* ) stream_.apiHandle )->captureAudioClient;\r
+ IAudioClient* renderAudioClient = ( ( WasapiHandle* ) stream_.apiHandle )->renderAudioClient;\r
+ IAudioCaptureClient* captureClient = ( ( WasapiHandle* ) stream_.apiHandle )->captureClient;\r
+ IAudioRenderClient* renderClient = ( ( WasapiHandle* ) stream_.apiHandle )->renderClient;\r
+ HANDLE captureEvent = ( ( WasapiHandle* ) stream_.apiHandle )->captureEvent;\r
+ HANDLE renderEvent = ( ( WasapiHandle* ) stream_.apiHandle )->renderEvent;\r
+\r
+ WAVEFORMATEX* captureFormat = NULL;\r
+ WAVEFORMATEX* renderFormat = NULL;\r
+ float captureSrRatio = 0.0f;\r
+ float renderSrRatio = 0.0f;\r
+ WasapiBuffer captureBuffer;\r
+ WasapiBuffer renderBuffer;\r
+\r
+ // declare local stream variables\r
+ RtAudioCallback callback = ( RtAudioCallback ) stream_.callbackInfo.callback;\r
+ BYTE* streamBuffer = NULL;\r
+ unsigned long captureFlags = 0;\r
+ unsigned int bufferFrameCount = 0;\r
+ unsigned int numFramesPadding = 0;\r
+ unsigned int convBufferSize = 0;\r
+ bool callbackPushed = false;\r
+ bool callbackPulled = false;\r
+ bool callbackStopped = false;\r
+ int callbackResult = 0;\r
+\r
+ // convBuffer is used to store converted buffers between WASAPI and the user\r
+ char* convBuffer = NULL;\r
+ unsigned int convBuffSize = 0;\r
+ unsigned int deviceBuffSize = 0;\r
+\r
+ errorText_.clear();\r
+ RtAudioError::Type errorType = RtAudioError::DRIVER_ERROR;\r
+\r
+ // Attempt to assign "Pro Audio" characteristic to thread\r
+ HMODULE AvrtDll = LoadLibrary( (LPCTSTR) "AVRT.dll" );\r
+ if ( AvrtDll ) {\r
+ DWORD taskIndex = 0;\r
+ TAvSetMmThreadCharacteristicsPtr AvSetMmThreadCharacteristicsPtr = ( TAvSetMmThreadCharacteristicsPtr ) GetProcAddress( AvrtDll, "AvSetMmThreadCharacteristicsW" );\r
+ AvSetMmThreadCharacteristicsPtr( L"Pro Audio", &taskIndex );\r
+ FreeLibrary( AvrtDll );\r
+ }\r
+\r
+ // start capture stream if applicable\r
+ if ( captureAudioClient ) {\r
+ hr = captureAudioClient->GetMixFormat( &captureFormat );\r
+ if ( FAILED( hr ) ) {\r
+ errorText_ = "RtApiWasapi::wasapiThread: Unable to retrieve device mix format.";\r
+ goto Exit;\r
+ }\r
+\r
+ captureSrRatio = ( ( float ) captureFormat->nSamplesPerSec / stream_.sampleRate );\r
+\r
+ // initialize capture stream according to desire buffer size\r
+ float desiredBufferSize = stream_.bufferSize * captureSrRatio;\r
+ REFERENCE_TIME desiredBufferPeriod = ( REFERENCE_TIME ) ( ( float ) desiredBufferSize * 10000000 / captureFormat->nSamplesPerSec );\r
+\r
+ if ( !captureClient ) {\r
+ hr = captureAudioClient->Initialize( AUDCLNT_SHAREMODE_SHARED,\r
+ AUDCLNT_STREAMFLAGS_EVENTCALLBACK,\r
+ desiredBufferPeriod,\r
+ desiredBufferPeriod,\r
+ captureFormat,\r
+ NULL );\r
+ if ( FAILED( hr ) ) {\r
+ errorText_ = "RtApiWasapi::wasapiThread: Unable to initialize capture audio client.";\r
+ goto Exit;\r
+ }\r
+\r
+ hr = captureAudioClient->GetService( __uuidof( IAudioCaptureClient ),\r
+ ( void** ) &captureClient );\r
+ if ( FAILED( hr ) ) {\r
+ errorText_ = "RtApiWasapi::wasapiThread: Unable to retrieve capture client handle.";\r
+ goto Exit;\r
+ }\r
+\r
+ // configure captureEvent to trigger on every available capture buffer\r
+ captureEvent = CreateEvent( NULL, FALSE, FALSE, NULL );\r
+ if ( !captureEvent ) {\r
+ errorType = RtAudioError::SYSTEM_ERROR;\r
+ errorText_ = "RtApiWasapi::wasapiThread: Unable to create capture event.";\r
+ goto Exit;\r
+ }\r
+\r
+ hr = captureAudioClient->SetEventHandle( captureEvent );\r
+ if ( FAILED( hr ) ) {\r
+ errorText_ = "RtApiWasapi::wasapiThread: Unable to set capture event handle.";\r
+ goto Exit;\r
+ }\r
+\r
+ ( ( WasapiHandle* ) stream_.apiHandle )->captureClient = captureClient;\r
+ ( ( WasapiHandle* ) stream_.apiHandle )->captureEvent = captureEvent;\r
+ }\r
+\r
+ unsigned int inBufferSize = 0;\r
+ hr = captureAudioClient->GetBufferSize( &inBufferSize );\r
+ if ( FAILED( hr ) ) {\r
+ errorText_ = "RtApiWasapi::wasapiThread: Unable to get capture buffer size.";\r
+ goto Exit;\r
+ }\r
+\r
+ // scale outBufferSize according to stream->user sample rate ratio\r
+ unsigned int outBufferSize = ( unsigned int ) ( stream_.bufferSize * captureSrRatio ) * stream_.nDeviceChannels[INPUT];\r
+ inBufferSize *= stream_.nDeviceChannels[INPUT];\r
+\r
+ // set captureBuffer size\r
+ captureBuffer.setBufferSize( inBufferSize + outBufferSize, formatBytes( stream_.deviceFormat[INPUT] ) );\r
+\r
+ // reset the capture stream\r
+ hr = captureAudioClient->Reset();\r
+ if ( FAILED( hr ) ) {\r
+ errorText_ = "RtApiWasapi::wasapiThread: Unable to reset capture stream.";\r
+ goto Exit;\r
+ }\r
+\r
+ // start the capture stream\r
+ hr = captureAudioClient->Start();\r
+ if ( FAILED( hr ) ) {\r
+ errorText_ = "RtApiWasapi::wasapiThread: Unable to start capture stream.";\r
+ goto Exit;\r
+ }\r
+ }\r
+\r
+ // start render stream if applicable\r
+ if ( renderAudioClient ) {\r
+ hr = renderAudioClient->GetMixFormat( &renderFormat );\r
+ if ( FAILED( hr ) ) {\r
+ errorText_ = "RtApiWasapi::wasapiThread: Unable to retrieve device mix format.";\r
+ goto Exit;\r
+ }\r
+\r
+ renderSrRatio = ( ( float ) renderFormat->nSamplesPerSec / stream_.sampleRate );\r
+\r
+ // initialize render stream according to desire buffer size\r
+ float desiredBufferSize = stream_.bufferSize * renderSrRatio;\r
+ REFERENCE_TIME desiredBufferPeriod = ( REFERENCE_TIME ) ( ( float ) desiredBufferSize * 10000000 / renderFormat->nSamplesPerSec );\r
+\r
+ if ( !renderClient ) {\r
+ hr = renderAudioClient->Initialize( AUDCLNT_SHAREMODE_SHARED,\r
+ AUDCLNT_STREAMFLAGS_EVENTCALLBACK,\r
+ desiredBufferPeriod,\r
+ desiredBufferPeriod,\r
+ renderFormat,\r
+ NULL );\r
+ if ( FAILED( hr ) ) {\r
+ errorText_ = "RtApiWasapi::wasapiThread: Unable to initialize render audio client.";\r
+ goto Exit;\r
+ }\r
+\r
+ hr = renderAudioClient->GetService( __uuidof( IAudioRenderClient ),\r
+ ( void** ) &renderClient );\r
+ if ( FAILED( hr ) ) {\r
+ errorText_ = "RtApiWasapi::wasapiThread: Unable to retrieve render client handle.";\r
+ goto Exit;\r
+ }\r
+\r
+ // configure renderEvent to trigger on every available render buffer\r
+ renderEvent = CreateEvent( NULL, FALSE, FALSE, NULL );\r
+ if ( !renderEvent ) {\r
+ errorType = RtAudioError::SYSTEM_ERROR;\r
+ errorText_ = "RtApiWasapi::wasapiThread: Unable to create render event.";\r
+ goto Exit;\r
+ }\r
+\r
+ hr = renderAudioClient->SetEventHandle( renderEvent );\r
+ if ( FAILED( hr ) ) {\r
+ errorText_ = "RtApiWasapi::wasapiThread: Unable to set render event handle.";\r
+ goto Exit;\r
+ }\r
+\r
+ ( ( WasapiHandle* ) stream_.apiHandle )->renderClient = renderClient;\r
+ ( ( WasapiHandle* ) stream_.apiHandle )->renderEvent = renderEvent;\r
+ }\r
+\r
+ unsigned int outBufferSize = 0;\r
+ hr = renderAudioClient->GetBufferSize( &outBufferSize );\r
+ if ( FAILED( hr ) ) {\r
+ errorText_ = "RtApiWasapi::wasapiThread: Unable to get render buffer size.";\r
+ goto Exit;\r
+ }\r
+\r
+ // scale inBufferSize according to user->stream sample rate ratio\r
+ unsigned int inBufferSize = ( unsigned int ) ( stream_.bufferSize * renderSrRatio ) * stream_.nDeviceChannels[OUTPUT];\r
+ outBufferSize *= stream_.nDeviceChannels[OUTPUT];\r
+\r
+ // set renderBuffer size\r
+ renderBuffer.setBufferSize( inBufferSize + outBufferSize, formatBytes( stream_.deviceFormat[OUTPUT] ) );\r
+\r
+ // reset the render stream\r
+ hr = renderAudioClient->Reset();\r
+ if ( FAILED( hr ) ) {\r
+ errorText_ = "RtApiWasapi::wasapiThread: Unable to reset render stream.";\r
+ goto Exit;\r
+ }\r
+\r
+ // start the render stream\r
+ hr = renderAudioClient->Start();\r
+ if ( FAILED( hr ) ) {\r
+ errorText_ = "RtApiWasapi::wasapiThread: Unable to start render stream.";\r
+ goto Exit;\r
+ }\r
+ }\r
+\r
+ if ( stream_.mode == INPUT ) {\r
+ convBuffSize = ( size_t ) ( stream_.bufferSize * captureSrRatio ) * stream_.nDeviceChannels[INPUT] * formatBytes( stream_.deviceFormat[INPUT] );\r
+ deviceBuffSize = stream_.bufferSize * stream_.nDeviceChannels[INPUT] * formatBytes( stream_.deviceFormat[INPUT] );\r
+ }\r
+ else if ( stream_.mode == OUTPUT ) {\r
+ convBuffSize = ( size_t ) ( stream_.bufferSize * renderSrRatio ) * stream_.nDeviceChannels[OUTPUT] * formatBytes( stream_.deviceFormat[OUTPUT] );\r
+ deviceBuffSize = stream_.bufferSize * stream_.nDeviceChannels[OUTPUT] * formatBytes( stream_.deviceFormat[OUTPUT] );\r
+ }\r
+ else if ( stream_.mode == DUPLEX ) {\r
+ convBuffSize = std::max( ( size_t ) ( stream_.bufferSize * captureSrRatio ) * stream_.nDeviceChannels[INPUT] * formatBytes( stream_.deviceFormat[INPUT] ),\r
+ ( size_t ) ( stream_.bufferSize * renderSrRatio ) * stream_.nDeviceChannels[OUTPUT] * formatBytes( stream_.deviceFormat[OUTPUT] ) );\r
+ deviceBuffSize = std::max( stream_.bufferSize * stream_.nDeviceChannels[INPUT] * formatBytes( stream_.deviceFormat[INPUT] ),\r
+ stream_.bufferSize * stream_.nDeviceChannels[OUTPUT] * formatBytes( stream_.deviceFormat[OUTPUT] ) );\r
+ }\r
+\r
+ convBuffer = ( char* ) malloc( convBuffSize );\r
+ stream_.deviceBuffer = ( char* ) malloc( deviceBuffSize );\r
+ if ( !convBuffer || !stream_.deviceBuffer ) {\r
+ errorType = RtAudioError::MEMORY_ERROR;\r
+ errorText_ = "RtApiWasapi::wasapiThread: Error allocating device buffer memory.";\r
+ goto Exit;\r
+ }\r
+\r
+ // stream process loop\r
+ while ( stream_.state != STREAM_STOPPING ) {\r
+ if ( !callbackPulled ) {\r
+ // Callback Input\r
+ // ==============\r
+ // 1. Pull callback buffer from inputBuffer\r
+ // 2. If 1. was successful: Convert callback buffer to user sample rate and channel count\r
+ // Convert callback buffer to user format\r
+\r
+ if ( captureAudioClient ) {\r
+ // Pull callback buffer from inputBuffer\r
+ callbackPulled = captureBuffer.pullBuffer( convBuffer,\r
+ ( unsigned int ) ( stream_.bufferSize * captureSrRatio ) * stream_.nDeviceChannels[INPUT],\r
+ stream_.deviceFormat[INPUT] );\r
+\r
+ if ( callbackPulled ) {\r
+ // Convert callback buffer to user sample rate\r
+ convertBufferWasapi( stream_.deviceBuffer,\r
+ convBuffer,\r
+ stream_.nDeviceChannels[INPUT],\r
+ captureFormat->nSamplesPerSec,\r
+ stream_.sampleRate,\r
+ ( unsigned int ) ( stream_.bufferSize * captureSrRatio ),\r
+ convBufferSize,\r
+ stream_.deviceFormat[INPUT] );\r
+\r
+ if ( stream_.doConvertBuffer[INPUT] ) {\r
+ // Convert callback buffer to user format\r
+ convertBuffer( stream_.userBuffer[INPUT],\r
+ stream_.deviceBuffer,\r
+ stream_.convertInfo[INPUT] );\r
+ }\r
+ else {\r
+ // no further conversion, simple copy deviceBuffer to userBuffer\r
+ memcpy( stream_.userBuffer[INPUT],\r
+ stream_.deviceBuffer,\r
+ stream_.bufferSize * stream_.nUserChannels[INPUT] * formatBytes( stream_.userFormat ) );\r
+ }\r
+ }\r
+ }\r
+ else {\r
+ // if there is no capture stream, set callbackPulled flag\r
+ callbackPulled = true;\r
+ }\r
+\r
+ // Execute Callback\r
+ // ================\r
+ // 1. Execute user callback method\r
+ // 2. Handle return value from callback\r
+\r
+ // if callback has not requested the stream to stop\r
+ if ( callbackPulled && !callbackStopped ) {\r
+ // Execute user callback method\r
+ callbackResult = callback( stream_.userBuffer[OUTPUT],\r
+ stream_.userBuffer[INPUT],\r
+ stream_.bufferSize,\r
+ getStreamTime(),\r
+ captureFlags & AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY ? RTAUDIO_INPUT_OVERFLOW : 0,\r
+ stream_.callbackInfo.userData );\r
+\r
+ // Handle return value from callback\r
+ if ( callbackResult == 1 ) {\r
+ // instantiate a thread to stop this thread\r
+ HANDLE threadHandle = CreateThread( NULL, 0, stopWasapiThread, this, 0, NULL );\r
+ if ( !threadHandle ) {\r
+ errorType = RtAudioError::THREAD_ERROR;\r
+ errorText_ = "RtApiWasapi::wasapiThread: Unable to instantiate stream stop thread.";\r
+ goto Exit;\r
+ }\r
+ else if ( !CloseHandle( threadHandle ) ) {\r
+ errorType = RtAudioError::THREAD_ERROR;\r
+ errorText_ = "RtApiWasapi::wasapiThread: Unable to close stream stop thread handle.";\r
+ goto Exit;\r
+ }\r
+\r
+ callbackStopped = true;\r
+ }\r
+ else if ( callbackResult == 2 ) {\r
+ // instantiate a thread to stop this thread\r
+ HANDLE threadHandle = CreateThread( NULL, 0, abortWasapiThread, this, 0, NULL );\r
+ if ( !threadHandle ) {\r
+ errorType = RtAudioError::THREAD_ERROR;\r
+ errorText_ = "RtApiWasapi::wasapiThread: Unable to instantiate stream abort thread.";\r
+ goto Exit;\r
+ }\r
+ else if ( !CloseHandle( threadHandle ) ) {\r
+ errorType = RtAudioError::THREAD_ERROR;\r
+ errorText_ = "RtApiWasapi::wasapiThread: Unable to close stream abort thread handle.";\r
+ goto Exit;\r
+ }\r
+\r
+ callbackStopped = true;\r
+ }\r
+ }\r
+ }\r
+\r
+ // Callback Output\r
+ // ===============\r
+ // 1. Convert callback buffer to stream format\r
+ // 2. Convert callback buffer to stream sample rate and channel count\r
+ // 3. Push callback buffer into outputBuffer\r
+\r
+ if ( renderAudioClient && callbackPulled ) {\r
+ if ( stream_.doConvertBuffer[OUTPUT] ) {\r
+ // Convert callback buffer to stream format\r
+ convertBuffer( stream_.deviceBuffer,\r
+ stream_.userBuffer[OUTPUT],\r
+ stream_.convertInfo[OUTPUT] );\r
+\r
+ }\r
+\r
+ // Convert callback buffer to stream sample rate\r
+ convertBufferWasapi( convBuffer,\r
+ stream_.deviceBuffer,\r
+ stream_.nDeviceChannels[OUTPUT],\r
+ stream_.sampleRate,\r
+ renderFormat->nSamplesPerSec,\r
+ stream_.bufferSize,\r
+ convBufferSize,\r
+ stream_.deviceFormat[OUTPUT] );\r
+\r
+ // Push callback buffer into outputBuffer\r
+ callbackPushed = renderBuffer.pushBuffer( convBuffer,\r
+ convBufferSize * stream_.nDeviceChannels[OUTPUT],\r
+ stream_.deviceFormat[OUTPUT] );\r
+ }\r
+ else {\r
+ // if there is no render stream, set callbackPushed flag\r
+ callbackPushed = true;\r
+ }\r
+\r
+ // Stream Capture\r
+ // ==============\r
+ // 1. Get capture buffer from stream\r
+ // 2. Push capture buffer into inputBuffer\r
+ // 3. If 2. was successful: Release capture buffer\r
+\r
+ if ( captureAudioClient ) {\r
+ // if the callback input buffer was not pulled from captureBuffer, wait for next capture event\r
+ if ( !callbackPulled ) {\r
+ WaitForSingleObject( captureEvent, INFINITE );\r
+ }\r
+\r
+ // Get capture buffer from stream\r
+ hr = captureClient->GetBuffer( &streamBuffer,\r
+ &bufferFrameCount,\r
+ &captureFlags, NULL, NULL );\r
+ if ( FAILED( hr ) ) {\r
+ errorText_ = "RtApiWasapi::wasapiThread: Unable to retrieve capture buffer.";\r
+ goto Exit;\r
+ }\r
+\r
+ if ( bufferFrameCount != 0 ) {\r
+ // Push capture buffer into inputBuffer\r
+ if ( captureBuffer.pushBuffer( ( char* ) streamBuffer,\r
+ bufferFrameCount * stream_.nDeviceChannels[INPUT],\r
+ stream_.deviceFormat[INPUT] ) )\r
+ {\r
+ // Release capture buffer\r
+ hr = captureClient->ReleaseBuffer( bufferFrameCount );\r
+ if ( FAILED( hr ) ) {\r
+ errorText_ = "RtApiWasapi::wasapiThread: Unable to release capture buffer.";\r
+ goto Exit;\r
+ }\r
+ }\r
+ else\r
+ {\r
+ // Inform WASAPI that capture was unsuccessful\r
+ hr = captureClient->ReleaseBuffer( 0 );\r
+ if ( FAILED( hr ) ) {\r
+ errorText_ = "RtApiWasapi::wasapiThread: Unable to release capture buffer.";\r
+ goto Exit;\r
+ }\r
+ }\r
+ }\r
+ else\r
+ {\r
+ // Inform WASAPI that capture was unsuccessful\r
+ hr = captureClient->ReleaseBuffer( 0 );\r
+ if ( FAILED( hr ) ) {\r
+ errorText_ = "RtApiWasapi::wasapiThread: Unable to release capture buffer.";\r
+ goto Exit;\r
+ }\r
+ }\r
+ }\r
+\r
+ // Stream Render\r
+ // =============\r
+ // 1. Get render buffer from stream\r
+ // 2. Pull next buffer from outputBuffer\r
+ // 3. If 2. was successful: Fill render buffer with next buffer\r
+ // Release render buffer\r
+\r
+ if ( renderAudioClient ) {\r
+ // if the callback output buffer was not pushed to renderBuffer, wait for next render event\r
+ if ( callbackPulled && !callbackPushed ) {\r
+ WaitForSingleObject( renderEvent, INFINITE );\r
+ }\r
+\r
+ // Get render buffer from stream\r
+ hr = renderAudioClient->GetBufferSize( &bufferFrameCount );\r
+ if ( FAILED( hr ) ) {\r
+ errorText_ = "RtApiWasapi::wasapiThread: Unable to retrieve render buffer size.";\r
+ goto Exit;\r
+ }\r
+\r
+ hr = renderAudioClient->GetCurrentPadding( &numFramesPadding );\r
+ if ( FAILED( hr ) ) {\r
+ errorText_ = "RtApiWasapi::wasapiThread: Unable to retrieve render buffer padding.";\r
+ goto Exit;\r
+ }\r
+\r
+ bufferFrameCount -= numFramesPadding;\r
+\r
+ if ( bufferFrameCount != 0 ) {\r
+ hr = renderClient->GetBuffer( bufferFrameCount, &streamBuffer );\r
+ if ( FAILED( hr ) ) {\r
+ errorText_ = "RtApiWasapi::wasapiThread: Unable to retrieve render buffer.";\r
+ goto Exit;\r
+ }\r
+\r
+ // Pull next buffer from outputBuffer\r
+ // Fill render buffer with next buffer\r
+ if ( renderBuffer.pullBuffer( ( char* ) streamBuffer,\r
+ bufferFrameCount * stream_.nDeviceChannels[OUTPUT],\r
+ stream_.deviceFormat[OUTPUT] ) )\r
+ {\r
+ // Release render buffer\r
+ hr = renderClient->ReleaseBuffer( bufferFrameCount, 0 );\r
+ if ( FAILED( hr ) ) {\r
+ errorText_ = "RtApiWasapi::wasapiThread: Unable to release render buffer.";\r
+ goto Exit;\r
+ }\r
+ }\r
+ else\r
+ {\r
+ // Inform WASAPI that render was unsuccessful\r
+ hr = renderClient->ReleaseBuffer( 0, 0 );\r
+ if ( FAILED( hr ) ) {\r
+ errorText_ = "RtApiWasapi::wasapiThread: Unable to release render buffer.";\r
+ goto Exit;\r
+ }\r
+ }\r
+ }\r
+ else\r
+ {\r
+ // Inform WASAPI that render was unsuccessful\r
+ hr = renderClient->ReleaseBuffer( 0, 0 );\r
+ if ( FAILED( hr ) ) {\r
+ errorText_ = "RtApiWasapi::wasapiThread: Unable to release render buffer.";\r
+ goto Exit;\r
+ }\r
+ }\r
+ }\r
+\r
+ // if the callback buffer was pushed renderBuffer reset callbackPulled flag\r
+ if ( callbackPushed ) {\r
+ callbackPulled = false;\r
+ // tick stream time\r
+ RtApi::tickStreamTime();\r
+ }\r
+\r
+ }\r
+\r
+Exit:\r
+ // clean up\r
+ CoTaskMemFree( captureFormat );\r
+ CoTaskMemFree( renderFormat );\r
+\r
+ free ( convBuffer );\r
+\r
+ CoUninitialize();\r
+\r
+ // update stream state\r
+ stream_.state = STREAM_STOPPED;\r
+\r
+ if ( errorText_.empty() )\r
+ return;\r
+ else\r
+ error( errorType );\r
+}\r
+\r
+//******************** End of __WINDOWS_WASAPI__ *********************//\r
+#endif\r
+\r
+\r
+#if defined(__WINDOWS_DS__) // Windows DirectSound API\r
+\r
+// Modified by Robin Davies, October 2005\r
+// - Improvements to DirectX pointer chasing.\r
+// - Bug fix for non-power-of-two Asio granularity used by Edirol PCR-A30.\r
+// - Auto-call CoInitialize for DSOUND and ASIO platforms.\r
+// Various revisions for RtAudio 4.0 by Gary Scavone, April 2007\r
+// Changed device query structure for RtAudio 4.0.7, January 2010\r
+\r
+#include <dsound.h>\r
+#include <assert.h>\r
+#include <algorithm>\r
+\r
+#if defined(__MINGW32__)\r
+ // missing from latest mingw winapi\r
+#define WAVE_FORMAT_96M08 0x00010000 /* 96 kHz, Mono, 8-bit */\r
+#define WAVE_FORMAT_96S08 0x00020000 /* 96 kHz, Stereo, 8-bit */\r
+#define WAVE_FORMAT_96M16 0x00040000 /* 96 kHz, Mono, 16-bit */\r
+#define WAVE_FORMAT_96S16 0x00080000 /* 96 kHz, Stereo, 16-bit */\r
+#endif\r
+\r
+#define MINIMUM_DEVICE_BUFFER_SIZE 32768\r
+\r
+#ifdef _MSC_VER // if Microsoft Visual C++\r
+#pragma comment( lib, "winmm.lib" ) // then, auto-link winmm.lib. Otherwise, it has to be added manually.\r
+#endif\r
+\r
+static inline DWORD dsPointerBetween( DWORD pointer, DWORD laterPointer, DWORD earlierPointer, DWORD bufferSize )\r
+{\r
+ if ( pointer > bufferSize ) pointer -= bufferSize;\r
+ if ( laterPointer < earlierPointer ) laterPointer += bufferSize;\r
+ if ( pointer < earlierPointer ) pointer += bufferSize;\r
+ return pointer >= earlierPointer && pointer < laterPointer;\r
+}\r
+\r
+// A structure to hold various information related to the DirectSound\r
+// API implementation.\r
+struct DsHandle {\r
+ unsigned int drainCounter; // Tracks callback counts when draining\r
+ bool internalDrain; // Indicates if stop is initiated from callback or not.\r
+ void *id[2];\r
+ void *buffer[2];\r
+ bool xrun[2];\r
+ UINT bufferPointer[2];\r
+ DWORD dsBufferSize[2];\r
+ DWORD dsPointerLeadTime[2]; // the number of bytes ahead of the safe pointer to lead by.\r
+ HANDLE condition;\r
+\r
+ DsHandle()\r
+ :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; }\r
+};\r
+\r
+// Declarations for utility functions, callbacks, and structures\r
+// specific to the DirectSound implementation.\r
+static BOOL CALLBACK deviceQueryCallback( LPGUID lpguid,\r
+ LPCTSTR description,\r
+ LPCTSTR module,\r
+ LPVOID lpContext );\r
+\r
+static const char* getErrorString( int code );\r
+\r
+static unsigned __stdcall callbackHandler( void *ptr );\r
+\r
+struct DsDevice {\r
+ LPGUID id[2];\r
+ bool validId[2];\r
+ bool found;\r
+ std::string name;\r
+\r
+ DsDevice()\r
+ : found(false) { validId[0] = false; validId[1] = false; }\r
+};\r
+\r
+struct DsProbeData {\r
+ bool isInput;\r
+ std::vector<struct DsDevice>* dsDevices;\r
+};\r
+\r
+RtApiDs :: RtApiDs()\r
+{\r
+ // Dsound will run both-threaded. If CoInitialize fails, then just\r
+ // accept whatever the mainline chose for a threading model.\r
+ coInitialized_ = false;\r
+ HRESULT hr = CoInitialize( NULL );\r
+ if ( !FAILED( hr ) ) coInitialized_ = true;\r
+}\r
+\r
+RtApiDs :: ~RtApiDs()\r
+{\r
+ if ( coInitialized_ ) CoUninitialize(); // balanced call.\r
+ if ( stream_.state != STREAM_CLOSED ) closeStream();\r
+}\r
+\r
+// The DirectSound default output is always the first device.\r
+unsigned int RtApiDs :: getDefaultOutputDevice( void )\r
+{\r
+ return 0;\r
+}\r
+\r
+// The DirectSound default input is always the first input device,\r
+// which is the first capture device enumerated.\r
+unsigned int RtApiDs :: getDefaultInputDevice( void )\r
+{\r
+ return 0;\r
+}\r
+\r
+unsigned int RtApiDs :: getDeviceCount( void )\r
+{\r
+ // Set query flag for previously found devices to false, so that we\r
+ // can check for any devices that have disappeared.\r
+ for ( unsigned int i=0; i<dsDevices.size(); i++ )\r
+ dsDevices[i].found = false;\r
+\r
+ // Query DirectSound devices.\r
+ struct DsProbeData probeInfo;\r
+ probeInfo.isInput = false;\r
+ probeInfo.dsDevices = &dsDevices;\r
+ HRESULT result = DirectSoundEnumerate( (LPDSENUMCALLBACK) deviceQueryCallback, &probeInfo );\r
+ if ( FAILED( result ) ) {\r
+ errorStream_ << "RtApiDs::getDeviceCount: error (" << getErrorString( result ) << ") enumerating output devices!";\r
+ errorText_ = errorStream_.str();\r
+ error( RtAudioError::WARNING );\r
+ }\r
+\r
+ // Query DirectSoundCapture devices.\r
+ probeInfo.isInput = true;\r
+ result = DirectSoundCaptureEnumerate( (LPDSENUMCALLBACK) deviceQueryCallback, &probeInfo );\r
+ if ( FAILED( result ) ) {\r
+ errorStream_ << "RtApiDs::getDeviceCount: error (" << getErrorString( result ) << ") enumerating input devices!";\r
+ errorText_ = errorStream_.str();\r
+ error( RtAudioError::WARNING );\r
+ }\r
+\r
+ // Clean out any devices that may have disappeared (code update submitted by Eli Zehngut).\r
+ for ( unsigned int i=0; i<dsDevices.size(); ) {\r
+ if ( dsDevices[i].found == false ) dsDevices.erase( dsDevices.begin() + i );\r
+ else i++;\r
+ }\r
+\r
+ return static_cast<unsigned int>(dsDevices.size());\r
+}\r
+\r
+RtAudio::DeviceInfo RtApiDs :: getDeviceInfo( unsigned int device )\r
+{\r
+ RtAudio::DeviceInfo info;\r
+ info.probed = false;\r
+\r
+ if ( dsDevices.size() == 0 ) {\r
+ // Force a query of all devices\r
+ getDeviceCount();\r
+ if ( dsDevices.size() == 0 ) {\r
+ errorText_ = "RtApiDs::getDeviceInfo: no devices found!";\r
+ error( RtAudioError::INVALID_USE );\r
+ return info;\r
+ }\r
+ }\r
+\r
+ if ( device >= dsDevices.size() ) {\r
+ errorText_ = "RtApiDs::getDeviceInfo: device ID is invalid!";\r
+ error( RtAudioError::INVALID_USE );\r
+ return info;\r
+ }\r
+\r
+ HRESULT result;\r
+ if ( dsDevices[ device ].validId[0] == false ) goto probeInput;\r
+\r
+ LPDIRECTSOUND output;\r
+ DSCAPS outCaps;\r
+ result = DirectSoundCreate( dsDevices[ device ].id[0], &output, NULL );\r
+ if ( FAILED( result ) ) {\r
+ errorStream_ << "RtApiDs::getDeviceInfo: error (" << getErrorString( result ) << ") opening output device (" << dsDevices[ device ].name << ")!";\r
+ errorText_ = errorStream_.str();\r
+ error( RtAudioError::WARNING );\r
+ goto probeInput;\r
+ }\r
+\r
+ outCaps.dwSize = sizeof( outCaps );\r
+ result = output->GetCaps( &outCaps );\r
+ if ( FAILED( result ) ) {\r
+ output->Release();\r
+ errorStream_ << "RtApiDs::getDeviceInfo: error (" << getErrorString( result ) << ") getting capabilities!";\r
+ errorText_ = errorStream_.str();\r
+ error( RtAudioError::WARNING );\r
+ goto probeInput;\r
+ }\r
+\r
+ // Get output channel information.\r
+ info.outputChannels = ( outCaps.dwFlags & DSCAPS_PRIMARYSTEREO ) ? 2 : 1;\r
+\r
+ // Get sample rate information.\r
+ info.sampleRates.clear();\r
+ for ( unsigned int k=0; k<MAX_SAMPLE_RATES; k++ ) {\r
+ if ( SAMPLE_RATES[k] >= (unsigned int) outCaps.dwMinSecondarySampleRate &&\r
+ SAMPLE_RATES[k] <= (unsigned int) outCaps.dwMaxSecondarySampleRate ) {\r
+ info.sampleRates.push_back( SAMPLE_RATES[k] );\r
+\r
+ if ( !info.preferredSampleRate || ( SAMPLE_RATES[k] <= 48000 && SAMPLE_RATES[k] > info.preferredSampleRate ) )\r
+ info.preferredSampleRate = SAMPLE_RATES[k];\r
+ }\r
+ }\r
+\r
+ // Get format information.\r
+ if ( outCaps.dwFlags & DSCAPS_PRIMARY16BIT ) info.nativeFormats |= RTAUDIO_SINT16;\r
+ if ( outCaps.dwFlags & DSCAPS_PRIMARY8BIT ) info.nativeFormats |= RTAUDIO_SINT8;\r
+\r
+ output->Release();\r
+\r
+ if ( getDefaultOutputDevice() == device )\r
+ info.isDefaultOutput = true;\r
+\r
+ if ( dsDevices[ device ].validId[1] == false ) {\r
+ info.name = dsDevices[ device ].name;\r
+ info.probed = true;\r
+ return info;\r
+ }\r
+\r
+ probeInput:\r
+\r
+ LPDIRECTSOUNDCAPTURE input;\r
+ result = DirectSoundCaptureCreate( dsDevices[ device ].id[1], &input, NULL );\r
+ if ( FAILED( result ) ) {\r
+ errorStream_ << "RtApiDs::getDeviceInfo: error (" << getErrorString( result ) << ") opening input device (" << dsDevices[ device ].name << ")!";\r
+ errorText_ = errorStream_.str();\r
+ error( RtAudioError::WARNING );\r
+ return info;\r
+ }\r
+\r
+ DSCCAPS inCaps;\r
+ inCaps.dwSize = sizeof( inCaps );\r
+ result = input->GetCaps( &inCaps );\r
+ if ( FAILED( result ) ) {\r
+ input->Release();\r
+ errorStream_ << "RtApiDs::getDeviceInfo: error (" << getErrorString( result ) << ") getting object capabilities (" << dsDevices[ device ].name << ")!";\r
+ errorText_ = errorStream_.str();\r
+ error( RtAudioError::WARNING );\r
+ return info;\r
+ }\r
+\r
+ // Get input channel information.\r
+ info.inputChannels = inCaps.dwChannels;\r
+\r
+ // Get sample rate and format information.\r
+ std::vector<unsigned int> rates;\r
+ if ( inCaps.dwChannels >= 2 ) {\r
+ if ( inCaps.dwFormats & WAVE_FORMAT_1S16 ) info.nativeFormats |= RTAUDIO_SINT16;\r
+ if ( inCaps.dwFormats & WAVE_FORMAT_2S16 ) info.nativeFormats |= RTAUDIO_SINT16;\r
+ if ( inCaps.dwFormats & WAVE_FORMAT_4S16 ) info.nativeFormats |= RTAUDIO_SINT16;\r
+ if ( inCaps.dwFormats & WAVE_FORMAT_96S16 ) info.nativeFormats |= RTAUDIO_SINT16;\r
+ if ( inCaps.dwFormats & WAVE_FORMAT_1S08 ) info.nativeFormats |= RTAUDIO_SINT8;\r
+ if ( inCaps.dwFormats & WAVE_FORMAT_2S08 ) info.nativeFormats |= RTAUDIO_SINT8;\r
+ if ( inCaps.dwFormats & WAVE_FORMAT_4S08 ) info.nativeFormats |= RTAUDIO_SINT8;\r
+ if ( inCaps.dwFormats & WAVE_FORMAT_96S08 ) info.nativeFormats |= RTAUDIO_SINT8;\r
+\r
+ if ( info.nativeFormats & RTAUDIO_SINT16 ) {\r
+ if ( inCaps.dwFormats & WAVE_FORMAT_1S16 ) rates.push_back( 11025 );\r
+ if ( inCaps.dwFormats & WAVE_FORMAT_2S16 ) rates.push_back( 22050 );\r
+ if ( inCaps.dwFormats & WAVE_FORMAT_4S16 ) rates.push_back( 44100 );\r
+ if ( inCaps.dwFormats & WAVE_FORMAT_96S16 ) rates.push_back( 96000 );\r
+ }\r
+ else if ( info.nativeFormats & RTAUDIO_SINT8 ) {\r
+ if ( inCaps.dwFormats & WAVE_FORMAT_1S08 ) rates.push_back( 11025 );\r
+ if ( inCaps.dwFormats & WAVE_FORMAT_2S08 ) rates.push_back( 22050 );\r
+ if ( inCaps.dwFormats & WAVE_FORMAT_4S08 ) rates.push_back( 44100 );\r
+ if ( inCaps.dwFormats & WAVE_FORMAT_96S08 ) rates.push_back( 96000 );\r
+ }\r
+ }\r
+ else if ( inCaps.dwChannels == 1 ) {\r
+ if ( inCaps.dwFormats & WAVE_FORMAT_1M16 ) info.nativeFormats |= RTAUDIO_SINT16;\r
+ if ( inCaps.dwFormats & WAVE_FORMAT_2M16 ) info.nativeFormats |= RTAUDIO_SINT16;\r
+ if ( inCaps.dwFormats & WAVE_FORMAT_4M16 ) info.nativeFormats |= RTAUDIO_SINT16;\r
+ if ( inCaps.dwFormats & WAVE_FORMAT_96M16 ) info.nativeFormats |= RTAUDIO_SINT16;\r
+ if ( inCaps.dwFormats & WAVE_FORMAT_1M08 ) info.nativeFormats |= RTAUDIO_SINT8;\r
+ if ( inCaps.dwFormats & WAVE_FORMAT_2M08 ) info.nativeFormats |= RTAUDIO_SINT8;\r
+ if ( inCaps.dwFormats & WAVE_FORMAT_4M08 ) info.nativeFormats |= RTAUDIO_SINT8;\r
+ if ( inCaps.dwFormats & WAVE_FORMAT_96M08 ) info.nativeFormats |= RTAUDIO_SINT8;\r
+\r
+ if ( info.nativeFormats & RTAUDIO_SINT16 ) {\r
+ if ( inCaps.dwFormats & WAVE_FORMAT_1M16 ) rates.push_back( 11025 );\r
+ if ( inCaps.dwFormats & WAVE_FORMAT_2M16 ) rates.push_back( 22050 );\r
+ if ( inCaps.dwFormats & WAVE_FORMAT_4M16 ) rates.push_back( 44100 );\r
+ if ( inCaps.dwFormats & WAVE_FORMAT_96M16 ) rates.push_back( 96000 );\r
+ }\r
+ else if ( info.nativeFormats & RTAUDIO_SINT8 ) {\r
+ if ( inCaps.dwFormats & WAVE_FORMAT_1M08 ) rates.push_back( 11025 );\r
+ if ( inCaps.dwFormats & WAVE_FORMAT_2M08 ) rates.push_back( 22050 );\r
+ if ( inCaps.dwFormats & WAVE_FORMAT_4M08 ) rates.push_back( 44100 );\r
+ if ( inCaps.dwFormats & WAVE_FORMAT_96M08 ) rates.push_back( 96000 );\r
+ }\r
+ }\r
+ else info.inputChannels = 0; // technically, this would be an error\r
+\r
+ input->Release();\r
+\r
+ if ( info.inputChannels == 0 ) return info;\r
+\r
+ // Copy the supported rates to the info structure but avoid duplication.\r
+ bool found;\r
+ for ( unsigned int i=0; i<rates.size(); i++ ) {\r
+ found = false;\r
+ for ( unsigned int j=0; j<info.sampleRates.size(); j++ ) {\r
+ if ( rates[i] == info.sampleRates[j] ) {\r
+ found = true;\r
+ break;\r
+ }\r
+ }\r
+ if ( found == false ) info.sampleRates.push_back( rates[i] );\r
+ }\r
+ std::sort( info.sampleRates.begin(), info.sampleRates.end() );\r
+\r
+ // If device opens for both playback and capture, we determine the channels.\r
+ if ( info.outputChannels > 0 && info.inputChannels > 0 )\r
+ info.duplexChannels = (info.outputChannels > info.inputChannels) ? info.inputChannels : info.outputChannels;\r
+\r
+ if ( device == 0 ) info.isDefaultInput = true;\r
+\r
+ // Copy name and return.\r
+ info.name = dsDevices[ device ].name;\r
+ info.probed = true;\r
+ return info;\r
+}\r
+\r
+bool RtApiDs :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels,\r
+ unsigned int firstChannel, unsigned int sampleRate,\r
+ RtAudioFormat format, unsigned int *bufferSize,\r
+ RtAudio::StreamOptions *options )\r
+{\r
+ if ( channels + firstChannel > 2 ) {\r
+ errorText_ = "RtApiDs::probeDeviceOpen: DirectSound does not support more than 2 channels per device.";\r
+ return FAILURE;\r
+ }\r
+\r
+ size_t nDevices = dsDevices.size();\r
+ if ( nDevices == 0 ) {\r
+ // This should not happen because a check is made before this function is called.\r
+ errorText_ = "RtApiDs::probeDeviceOpen: no devices found!";\r
+ return FAILURE;\r
+ }\r
+\r
+ if ( device >= nDevices ) {\r
+ // This should not happen because a check is made before this function is called.\r
+ errorText_ = "RtApiDs::probeDeviceOpen: device ID is invalid!";\r
+ return FAILURE;\r
+ }\r
+\r
+ if ( mode == OUTPUT ) {\r
+ if ( dsDevices[ device ].validId[0] == false ) {\r
+ errorStream_ << "RtApiDs::probeDeviceOpen: device (" << device << ") does not support output!";\r
+ errorText_ = errorStream_.str();\r
+ return FAILURE;\r
+ }\r
+ }\r
+ else { // mode == INPUT\r
+ if ( dsDevices[ device ].validId[1] == false ) {\r
+ errorStream_ << "RtApiDs::probeDeviceOpen: device (" << device << ") does not support input!";\r
+ errorText_ = errorStream_.str();\r
+ return FAILURE;\r
+ }\r
+ }\r
+\r
+ // According to a note in PortAudio, using GetDesktopWindow()\r
+ // instead of GetForegroundWindow() is supposed to avoid problems\r
+ // that occur when the application's window is not the foreground\r
+ // window. Also, if the application window closes before the\r
+ // DirectSound buffer, DirectSound can crash. In the past, I had\r
+ // problems when using GetDesktopWindow() but it seems fine now\r
+ // (January 2010). I'll leave it commented here.\r
+ // HWND hWnd = GetForegroundWindow();\r
+ HWND hWnd = GetDesktopWindow();\r
+\r
+ // Check the numberOfBuffers parameter and limit the lowest value to\r
+ // two. This is a judgement call and a value of two is probably too\r
+ // low for capture, but it should work for playback.\r
+ int nBuffers = 0;\r
+ if ( options ) nBuffers = options->numberOfBuffers;\r
+ if ( options && options->flags & RTAUDIO_MINIMIZE_LATENCY ) nBuffers = 2;\r
+ if ( nBuffers < 2 ) nBuffers = 3;\r
+\r
+ // Check the lower range of the user-specified buffer size and set\r
+ // (arbitrarily) to a lower bound of 32.\r
+ if ( *bufferSize < 32 ) *bufferSize = 32;\r
+\r
+ // Create the wave format structure. The data format setting will\r
+ // be determined later.\r
+ WAVEFORMATEX waveFormat;\r
+ ZeroMemory( &waveFormat, sizeof(WAVEFORMATEX) );\r
+ waveFormat.wFormatTag = WAVE_FORMAT_PCM;\r
+ waveFormat.nChannels = channels + firstChannel;\r
+ waveFormat.nSamplesPerSec = (unsigned long) sampleRate;\r
+\r
+ // Determine the device buffer size. By default, we'll use the value\r
+ // defined above (32K), but we will grow it to make allowances for\r
+ // very large software buffer sizes.\r
+ DWORD dsBufferSize = MINIMUM_DEVICE_BUFFER_SIZE;\r
+ DWORD dsPointerLeadTime = 0;\r
+\r
+ void *ohandle = 0, *bhandle = 0;\r
+ HRESULT result;\r
+ if ( mode == OUTPUT ) {\r
+\r
+ LPDIRECTSOUND output;\r
+ result = DirectSoundCreate( dsDevices[ device ].id[0], &output, NULL );\r
+ if ( FAILED( result ) ) {\r
+ errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") opening output device (" << dsDevices[ device ].name << ")!";\r
+ errorText_ = errorStream_.str();\r
+ return FAILURE;\r
+ }\r
+\r
+ DSCAPS outCaps;\r
+ outCaps.dwSize = sizeof( outCaps );\r
+ result = output->GetCaps( &outCaps );\r
+ if ( FAILED( result ) ) {\r
+ output->Release();\r
+ errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") getting capabilities (" << dsDevices[ device ].name << ")!";\r
+ errorText_ = errorStream_.str();\r
+ return FAILURE;\r
+ }\r
+\r
+ // Check channel information.\r
+ if ( channels + firstChannel == 2 && !( outCaps.dwFlags & DSCAPS_PRIMARYSTEREO ) ) {\r
+ errorStream_ << "RtApiDs::getDeviceInfo: the output device (" << dsDevices[ device ].name << ") does not support stereo playback.";\r
+ errorText_ = errorStream_.str();\r
+ return FAILURE;\r
+ }\r
+\r
+ // Check format information. Use 16-bit format unless not\r
+ // supported or user requests 8-bit.\r
+ if ( outCaps.dwFlags & DSCAPS_PRIMARY16BIT &&\r
+ !( format == RTAUDIO_SINT8 && outCaps.dwFlags & DSCAPS_PRIMARY8BIT ) ) {\r
+ waveFormat.wBitsPerSample = 16;\r
+ stream_.deviceFormat[mode] = RTAUDIO_SINT16;\r
+ }\r
+ else {\r
+ waveFormat.wBitsPerSample = 8;\r
+ stream_.deviceFormat[mode] = RTAUDIO_SINT8;\r
+ }\r
+ stream_.userFormat = format;\r
+\r
+ // Update wave format structure and buffer information.\r
+ waveFormat.nBlockAlign = waveFormat.nChannels * waveFormat.wBitsPerSample / 8;\r
+ waveFormat.nAvgBytesPerSec = waveFormat.nSamplesPerSec * waveFormat.nBlockAlign;\r
+ dsPointerLeadTime = nBuffers * (*bufferSize) * (waveFormat.wBitsPerSample / 8) * channels;\r
+\r
+ // If the user wants an even bigger buffer, increase the device buffer size accordingly.\r
+ while ( dsPointerLeadTime * 2U > dsBufferSize )\r
+ dsBufferSize *= 2;\r
+\r
+ // Set cooperative level to DSSCL_EXCLUSIVE ... sound stops when window focus changes.\r
+ // result = output->SetCooperativeLevel( hWnd, DSSCL_EXCLUSIVE );\r
+ // Set cooperative level to DSSCL_PRIORITY ... sound remains when window focus changes.\r
+ result = output->SetCooperativeLevel( hWnd, DSSCL_PRIORITY );\r
+ if ( FAILED( result ) ) {\r
+ output->Release();\r
+ errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") setting cooperative level (" << dsDevices[ device ].name << ")!";\r
+ errorText_ = errorStream_.str();\r
+ return FAILURE;\r
+ }\r
+\r
+ // Even though we will write to the secondary buffer, we need to\r
+ // access the primary buffer to set the correct output format\r
+ // (since the default is 8-bit, 22 kHz!). Setup the DS primary\r
+ // buffer description.\r
+ DSBUFFERDESC bufferDescription;\r
+ ZeroMemory( &bufferDescription, sizeof( DSBUFFERDESC ) );\r
+ bufferDescription.dwSize = sizeof( DSBUFFERDESC );\r
+ bufferDescription.dwFlags = DSBCAPS_PRIMARYBUFFER;\r
+\r
+ // Obtain the primary buffer\r
+ LPDIRECTSOUNDBUFFER buffer;\r
+ result = output->CreateSoundBuffer( &bufferDescription, &buffer, NULL );\r
+ if ( FAILED( result ) ) {\r
+ output->Release();\r
+ errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") accessing primary buffer (" << dsDevices[ device ].name << ")!";\r
+ errorText_ = errorStream_.str();\r
+ return FAILURE;\r
+ }\r
+\r
+ // Set the primary DS buffer sound format.\r
+ result = buffer->SetFormat( &waveFormat );\r
+ if ( FAILED( result ) ) {\r
+ output->Release();\r
+ errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") setting primary buffer format (" << dsDevices[ device ].name << ")!";\r
+ errorText_ = errorStream_.str();\r
+ return FAILURE;\r
+ }\r
+\r
+ // Setup the secondary DS buffer description.\r
+ ZeroMemory( &bufferDescription, sizeof( DSBUFFERDESC ) );\r
+ bufferDescription.dwSize = sizeof( DSBUFFERDESC );\r
+ bufferDescription.dwFlags = ( DSBCAPS_STICKYFOCUS |\r
+ DSBCAPS_GLOBALFOCUS |\r
+ DSBCAPS_GETCURRENTPOSITION2 |\r
+ DSBCAPS_LOCHARDWARE ); // Force hardware mixing\r
+ bufferDescription.dwBufferBytes = dsBufferSize;\r
+ bufferDescription.lpwfxFormat = &waveFormat;\r
+\r
+ // Try to create the secondary DS buffer. If that doesn't work,\r
+ // try to use software mixing. Otherwise, there's a problem.\r
+ result = output->CreateSoundBuffer( &bufferDescription, &buffer, NULL );\r
+ if ( FAILED( result ) ) {\r
+ bufferDescription.dwFlags = ( DSBCAPS_STICKYFOCUS |\r
+ DSBCAPS_GLOBALFOCUS |\r
+ DSBCAPS_GETCURRENTPOSITION2 |\r
+ DSBCAPS_LOCSOFTWARE ); // Force software mixing\r
+ result = output->CreateSoundBuffer( &bufferDescription, &buffer, NULL );\r
+ if ( FAILED( result ) ) {\r
+ output->Release();\r
+ errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") creating secondary buffer (" << dsDevices[ device ].name << ")!";\r
+ errorText_ = errorStream_.str();\r
+ return FAILURE;\r
+ }\r
+ }\r
+\r
+ // Get the buffer size ... might be different from what we specified.\r
+ DSBCAPS dsbcaps;\r
+ dsbcaps.dwSize = sizeof( DSBCAPS );\r
+ result = buffer->GetCaps( &dsbcaps );\r
+ if ( FAILED( result ) ) {\r
+ output->Release();\r
+ buffer->Release();\r
+ errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") getting buffer settings (" << dsDevices[ device ].name << ")!";\r
+ errorText_ = errorStream_.str();\r
+ return FAILURE;\r
+ }\r
+\r
+ dsBufferSize = dsbcaps.dwBufferBytes;\r
+\r
+ // Lock the DS buffer\r
+ LPVOID audioPtr;\r
+ DWORD dataLen;\r
+ result = buffer->Lock( 0, dsBufferSize, &audioPtr, &dataLen, NULL, NULL, 0 );\r
+ if ( FAILED( result ) ) {\r
+ output->Release();\r
+ buffer->Release();\r
+ errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") locking buffer (" << dsDevices[ device ].name << ")!";\r
+ errorText_ = errorStream_.str();\r
+ return FAILURE;\r
+ }\r
+\r
+ // Zero the DS buffer\r
+ ZeroMemory( audioPtr, dataLen );\r
+\r
+ // Unlock the DS buffer\r
+ result = buffer->Unlock( audioPtr, dataLen, NULL, 0 );\r
+ if ( FAILED( result ) ) {\r
+ output->Release();\r
+ buffer->Release();\r
+ errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") unlocking buffer (" << dsDevices[ device ].name << ")!";\r
+ errorText_ = errorStream_.str();\r
+ return FAILURE;\r
+ }\r
+\r
+ ohandle = (void *) output;\r
+ bhandle = (void *) buffer;\r
+ }\r
+\r
+ if ( mode == INPUT ) {\r
+\r
+ LPDIRECTSOUNDCAPTURE input;\r
+ result = DirectSoundCaptureCreate( dsDevices[ device ].id[1], &input, NULL );\r
+ if ( FAILED( result ) ) {\r
+ errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") opening input device (" << dsDevices[ device ].name << ")!";\r
+ errorText_ = errorStream_.str();\r
+ return FAILURE;\r
+ }\r
+\r
+ DSCCAPS inCaps;\r
+ inCaps.dwSize = sizeof( inCaps );\r
+ result = input->GetCaps( &inCaps );\r
+ if ( FAILED( result ) ) {\r
+ input->Release();\r
+ errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") getting input capabilities (" << dsDevices[ device ].name << ")!";\r
+ errorText_ = errorStream_.str();\r
+ return FAILURE;\r
+ }\r
+\r
+ // Check channel information.\r
+ if ( inCaps.dwChannels < channels + firstChannel ) {\r
+ errorText_ = "RtApiDs::getDeviceInfo: the input device does not support requested input channels.";\r
+ return FAILURE;\r
+ }\r
+\r
+ // Check format information. Use 16-bit format unless user\r
+ // requests 8-bit.\r
+ DWORD deviceFormats;\r
+ if ( channels + firstChannel == 2 ) {\r
+ deviceFormats = WAVE_FORMAT_1S08 | WAVE_FORMAT_2S08 | WAVE_FORMAT_4S08 | WAVE_FORMAT_96S08;\r
+ if ( format == RTAUDIO_SINT8 && inCaps.dwFormats & deviceFormats ) {\r
+ waveFormat.wBitsPerSample = 8;\r
+ stream_.deviceFormat[mode] = RTAUDIO_SINT8;\r
+ }\r
+ else { // assume 16-bit is supported\r
+ waveFormat.wBitsPerSample = 16;\r
+ stream_.deviceFormat[mode] = RTAUDIO_SINT16;\r
+ }\r
+ }\r
+ else { // channel == 1\r
+ deviceFormats = WAVE_FORMAT_1M08 | WAVE_FORMAT_2M08 | WAVE_FORMAT_4M08 | WAVE_FORMAT_96M08;\r
+ if ( format == RTAUDIO_SINT8 && inCaps.dwFormats & deviceFormats ) {\r
+ waveFormat.wBitsPerSample = 8;\r
+ stream_.deviceFormat[mode] = RTAUDIO_SINT8;\r
+ }\r
+ else { // assume 16-bit is supported\r
+ waveFormat.wBitsPerSample = 16;\r
+ stream_.deviceFormat[mode] = RTAUDIO_SINT16;\r
+ }\r
+ }\r
+ stream_.userFormat = format;\r
+\r
+ // Update wave format structure and buffer information.\r
+ waveFormat.nBlockAlign = waveFormat.nChannels * waveFormat.wBitsPerSample / 8;\r
+ waveFormat.nAvgBytesPerSec = waveFormat.nSamplesPerSec * waveFormat.nBlockAlign;\r
+ dsPointerLeadTime = nBuffers * (*bufferSize) * (waveFormat.wBitsPerSample / 8) * channels;\r
+\r
+ // If the user wants an even bigger buffer, increase the device buffer size accordingly.\r
+ while ( dsPointerLeadTime * 2U > dsBufferSize )\r
+ dsBufferSize *= 2;\r
+\r
+ // Setup the secondary DS buffer description.\r
+ DSCBUFFERDESC bufferDescription;\r
+ ZeroMemory( &bufferDescription, sizeof( DSCBUFFERDESC ) );\r
+ bufferDescription.dwSize = sizeof( DSCBUFFERDESC );\r
+ bufferDescription.dwFlags = 0;\r
+ bufferDescription.dwReserved = 0;\r
+ bufferDescription.dwBufferBytes = dsBufferSize;\r
+ bufferDescription.lpwfxFormat = &waveFormat;\r
+\r
+ // Create the capture buffer.\r
+ LPDIRECTSOUNDCAPTUREBUFFER buffer;\r
+ result = input->CreateCaptureBuffer( &bufferDescription, &buffer, NULL );\r
+ if ( FAILED( result ) ) {\r
+ input->Release();\r
+ errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") creating input buffer (" << dsDevices[ device ].name << ")!";\r
+ errorText_ = errorStream_.str();\r
+ return FAILURE;\r
+ }\r
+\r
+ // Get the buffer size ... might be different from what we specified.\r
+ DSCBCAPS dscbcaps;\r
+ dscbcaps.dwSize = sizeof( DSCBCAPS );\r
+ result = buffer->GetCaps( &dscbcaps );\r
+ if ( FAILED( result ) ) {\r
+ input->Release();\r
+ buffer->Release();\r
+ errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") getting buffer settings (" << dsDevices[ device ].name << ")!";\r
+ errorText_ = errorStream_.str();\r
+ return FAILURE;\r
+ }\r
+\r
+ dsBufferSize = dscbcaps.dwBufferBytes;\r
+\r
+ // NOTE: We could have a problem here if this is a duplex stream\r
+ // and the play and capture hardware buffer sizes are different\r
+ // (I'm actually not sure if that is a problem or not).\r
+ // Currently, we are not verifying that.\r
+\r
+ // Lock the capture buffer\r
+ LPVOID audioPtr;\r
+ DWORD dataLen;\r
+ result = buffer->Lock( 0, dsBufferSize, &audioPtr, &dataLen, NULL, NULL, 0 );\r
+ if ( FAILED( result ) ) {\r
+ input->Release();\r
+ buffer->Release();\r
+ errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") locking input buffer (" << dsDevices[ device ].name << ")!";\r
+ errorText_ = errorStream_.str();\r
+ return FAILURE;\r
+ }\r
+\r
+ // Zero the buffer\r
+ ZeroMemory( audioPtr, dataLen );\r
+\r
+ // Unlock the buffer\r
+ result = buffer->Unlock( audioPtr, dataLen, NULL, 0 );\r
+ if ( FAILED( result ) ) {\r
+ input->Release();\r
+ buffer->Release();\r
+ errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") unlocking input buffer (" << dsDevices[ device ].name << ")!";\r
+ errorText_ = errorStream_.str();\r
+ return FAILURE;\r
+ }\r
+\r
+ ohandle = (void *) input;\r
+ bhandle = (void *) buffer;\r
+ }\r
+\r
+ // Set various stream parameters\r
+ DsHandle *handle = 0;\r
+ stream_.nDeviceChannels[mode] = channels + firstChannel;\r
+ stream_.nUserChannels[mode] = channels;\r
+ stream_.bufferSize = *bufferSize;\r
+ stream_.channelOffset[mode] = firstChannel;\r
+ stream_.deviceInterleaved[mode] = true;\r
+ if ( options && options->flags & RTAUDIO_NONINTERLEAVED ) stream_.userInterleaved = false;\r
+ else stream_.userInterleaved = true;\r
+\r
+ // Set flag for buffer conversion\r
+ stream_.doConvertBuffer[mode] = false;\r
+ if (stream_.nUserChannels[mode] != stream_.nDeviceChannels[mode])\r
+ stream_.doConvertBuffer[mode] = true;\r
+ if (stream_.userFormat != stream_.deviceFormat[mode])\r
+ stream_.doConvertBuffer[mode] = true;\r
+ if ( stream_.userInterleaved != stream_.deviceInterleaved[mode] &&\r
+ stream_.nUserChannels[mode] > 1 )\r
+ stream_.doConvertBuffer[mode] = true;\r
+\r
+ // Allocate necessary internal buffers\r
+ long bufferBytes = stream_.nUserChannels[mode] * *bufferSize * formatBytes( stream_.userFormat );\r
+ stream_.userBuffer[mode] = (char *) calloc( bufferBytes, 1 );\r
+ if ( stream_.userBuffer[mode] == NULL ) {\r
+ errorText_ = "RtApiDs::probeDeviceOpen: error allocating user buffer memory.";\r
+ goto error;\r
+ }\r
+\r
+ if ( stream_.doConvertBuffer[mode] ) {\r
+\r
+ bool makeBuffer = true;\r
+ bufferBytes = stream_.nDeviceChannels[mode] * formatBytes( stream_.deviceFormat[mode] );\r
+ if ( mode == INPUT ) {\r
+ if ( stream_.mode == OUTPUT && stream_.deviceBuffer ) {\r
+ unsigned long bytesOut = stream_.nDeviceChannels[0] * formatBytes( stream_.deviceFormat[0] );\r
+ if ( bufferBytes <= (long) bytesOut ) makeBuffer = false;\r
+ }\r
+ }\r
+\r
+ if ( makeBuffer ) {\r
+ bufferBytes *= *bufferSize;\r
+ if ( stream_.deviceBuffer ) free( stream_.deviceBuffer );\r
+ stream_.deviceBuffer = (char *) calloc( bufferBytes, 1 );\r
+ if ( stream_.deviceBuffer == NULL ) {\r
+ errorText_ = "RtApiDs::probeDeviceOpen: error allocating device buffer memory.";\r
+ goto error;\r
+ }\r
+ }\r
+ }\r
+\r
+ // Allocate our DsHandle structures for the stream.\r
+ if ( stream_.apiHandle == 0 ) {\r
+ try {\r
+ handle = new DsHandle;\r
+ }\r
+ catch ( std::bad_alloc& ) {\r
+ errorText_ = "RtApiDs::probeDeviceOpen: error allocating AsioHandle memory.";\r
+ goto error;\r
+ }\r
+\r
+ // Create a manual-reset event.\r
+ handle->condition = CreateEvent( NULL, // no security\r
+ TRUE, // manual-reset\r
+ FALSE, // non-signaled initially\r
+ NULL ); // unnamed\r
+ stream_.apiHandle = (void *) handle;\r
+ }\r
+ else\r
+ handle = (DsHandle *) stream_.apiHandle;\r
+ handle->id[mode] = ohandle;\r
+ handle->buffer[mode] = bhandle;\r
+ handle->dsBufferSize[mode] = dsBufferSize;\r
+ handle->dsPointerLeadTime[mode] = dsPointerLeadTime;\r
+\r
+ stream_.device[mode] = device;\r
+ stream_.state = STREAM_STOPPED;\r
+ if ( stream_.mode == OUTPUT && mode == INPUT )\r
+ // We had already set up an output stream.\r
+ stream_.mode = DUPLEX;\r
+ else\r
+ stream_.mode = mode;\r
+ stream_.nBuffers = nBuffers;\r
+ stream_.sampleRate = sampleRate;\r
+\r
+ // Setup the buffer conversion information structure.\r
+ if ( stream_.doConvertBuffer[mode] ) setConvertInfo( mode, firstChannel );\r
+\r
+ // Setup the callback thread.\r
+ if ( stream_.callbackInfo.isRunning == false ) {\r
+ unsigned threadId;\r
+ stream_.callbackInfo.isRunning = true;\r
+ stream_.callbackInfo.object = (void *) this;\r
+ stream_.callbackInfo.thread = _beginthreadex( NULL, 0, &callbackHandler,\r
+ &stream_.callbackInfo, 0, &threadId );\r
+ if ( stream_.callbackInfo.thread == 0 ) {\r
+ errorText_ = "RtApiDs::probeDeviceOpen: error creating callback thread!";\r
+ goto error;\r
+ }\r
+\r
+ // Boost DS thread priority\r
+ SetThreadPriority( (HANDLE) stream_.callbackInfo.thread, THREAD_PRIORITY_HIGHEST );\r
+ }\r
+ return SUCCESS;\r
+\r
+ error:\r
+ if ( handle ) {\r
+ if ( handle->buffer[0] ) { // the object pointer can be NULL and valid\r
+ LPDIRECTSOUND object = (LPDIRECTSOUND) handle->id[0];\r
+ LPDIRECTSOUNDBUFFER buffer = (LPDIRECTSOUNDBUFFER) handle->buffer[0];\r
+ if ( buffer ) buffer->Release();\r
+ object->Release();\r
+ }\r
+ if ( handle->buffer[1] ) {\r
+ LPDIRECTSOUNDCAPTURE object = (LPDIRECTSOUNDCAPTURE) handle->id[1];\r
+ LPDIRECTSOUNDCAPTUREBUFFER buffer = (LPDIRECTSOUNDCAPTUREBUFFER) handle->buffer[1];\r
+ if ( buffer ) buffer->Release();\r
+ object->Release();\r
+ }\r
+ CloseHandle( handle->condition );\r
+ delete handle;\r
+ stream_.apiHandle = 0;\r
+ }\r
+\r
+ for ( int i=0; i<2; i++ ) {\r
+ if ( stream_.userBuffer[i] ) {\r
+ free( stream_.userBuffer[i] );\r
+ stream_.userBuffer[i] = 0;\r
+ }\r
+ }\r
+\r
+ if ( stream_.deviceBuffer ) {\r
+ free( stream_.deviceBuffer );\r
+ stream_.deviceBuffer = 0;\r
+ }\r
+\r
+ stream_.state = STREAM_CLOSED;\r
+ return FAILURE;\r
+}\r
+\r
+void RtApiDs :: closeStream()\r
+{\r
+ if ( stream_.state == STREAM_CLOSED ) {\r
+ errorText_ = "RtApiDs::closeStream(): no open stream to close!";\r
+ error( RtAudioError::WARNING );\r
+ return;\r
+ }\r
+\r
+ // Stop the callback thread.\r
+ stream_.callbackInfo.isRunning = false;\r
+ WaitForSingleObject( (HANDLE) stream_.callbackInfo.thread, INFINITE );\r
+ CloseHandle( (HANDLE) stream_.callbackInfo.thread );\r
+\r
+ DsHandle *handle = (DsHandle *) stream_.apiHandle;\r
+ if ( handle ) {\r
+ if ( handle->buffer[0] ) { // the object pointer can be NULL and valid\r
+ LPDIRECTSOUND object = (LPDIRECTSOUND) handle->id[0];\r
+ LPDIRECTSOUNDBUFFER buffer = (LPDIRECTSOUNDBUFFER) handle->buffer[0];\r
+ if ( buffer ) {\r
+ buffer->Stop();\r
+ buffer->Release();\r
+ }\r
+ object->Release();\r
+ }\r
+ if ( handle->buffer[1] ) {\r
+ LPDIRECTSOUNDCAPTURE object = (LPDIRECTSOUNDCAPTURE) handle->id[1];\r
+ LPDIRECTSOUNDCAPTUREBUFFER buffer = (LPDIRECTSOUNDCAPTUREBUFFER) handle->buffer[1];\r
+ if ( buffer ) {\r
+ buffer->Stop();\r
+ buffer->Release();\r
+ }\r
+ object->Release();\r
+ }\r
+ CloseHandle( handle->condition );\r
+ delete handle;\r
+ stream_.apiHandle = 0;\r
+ }\r
+\r
+ for ( int i=0; i<2; i++ ) {\r
+ if ( stream_.userBuffer[i] ) {\r
+ free( stream_.userBuffer[i] );\r
+ stream_.userBuffer[i] = 0;\r
+ }\r
+ }\r
+\r
+ if ( stream_.deviceBuffer ) {\r
+ free( stream_.deviceBuffer );\r
+ stream_.deviceBuffer = 0;\r
+ }\r
+\r
+ stream_.mode = UNINITIALIZED;\r
+ stream_.state = STREAM_CLOSED;\r
+}\r
+\r
+void RtApiDs :: startStream()\r
+{\r
+ verifyStream();\r
+ if ( stream_.state == STREAM_RUNNING ) {\r
+ errorText_ = "RtApiDs::startStream(): the stream is already running!";\r
+ error( RtAudioError::WARNING );\r
+ return;\r
+ }\r
+\r
+ DsHandle *handle = (DsHandle *) stream_.apiHandle;\r
+\r
+ // Increase scheduler frequency on lesser windows (a side-effect of\r
+ // increasing timer accuracy). On greater windows (Win2K or later),\r
+ // this is already in effect.\r
+ timeBeginPeriod( 1 );\r
+\r
+ buffersRolling = false;\r
+ duplexPrerollBytes = 0;\r
+\r
+ if ( stream_.mode == DUPLEX ) {\r
+ // 0.5 seconds of silence in DUPLEX mode while the devices spin up and synchronize.\r
+ duplexPrerollBytes = (int) ( 0.5 * stream_.sampleRate * formatBytes( stream_.deviceFormat[1] ) * stream_.nDeviceChannels[1] );\r
+ }\r
+\r
+ HRESULT result = 0;\r
+ if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) {\r
+\r
+ LPDIRECTSOUNDBUFFER buffer = (LPDIRECTSOUNDBUFFER) handle->buffer[0];\r
+ result = buffer->Play( 0, 0, DSBPLAY_LOOPING );\r
+ if ( FAILED( result ) ) {\r
+ errorStream_ << "RtApiDs::startStream: error (" << getErrorString( result ) << ") starting output buffer!";\r
+ errorText_ = errorStream_.str();\r
+ goto unlock;\r
+ }\r
+ }\r
+\r
+ if ( stream_.mode == INPUT || stream_.mode == DUPLEX ) {\r
+\r
+ LPDIRECTSOUNDCAPTUREBUFFER buffer = (LPDIRECTSOUNDCAPTUREBUFFER) handle->buffer[1];\r
+ result = buffer->Start( DSCBSTART_LOOPING );\r
+ if ( FAILED( result ) ) {\r
+ errorStream_ << "RtApiDs::startStream: error (" << getErrorString( result ) << ") starting input buffer!";\r
+ errorText_ = errorStream_.str();\r
+ goto unlock;\r
+ }\r
+ }\r
+\r
+ handle->drainCounter = 0;\r
+ handle->internalDrain = false;\r
+ ResetEvent( handle->condition );\r
+ stream_.state = STREAM_RUNNING;\r
+\r
+ unlock:\r
+ if ( FAILED( result ) ) error( RtAudioError::SYSTEM_ERROR );\r
+}\r
+\r
+void RtApiDs :: stopStream()\r
+{\r
+ verifyStream();\r
+ if ( stream_.state == STREAM_STOPPED ) {\r
+ errorText_ = "RtApiDs::stopStream(): the stream is already stopped!";\r
+ error( RtAudioError::WARNING );\r
+ return;\r
+ }\r
+\r
+ HRESULT result = 0;\r
+ LPVOID audioPtr;\r
+ DWORD dataLen;\r
+ DsHandle *handle = (DsHandle *) stream_.apiHandle;\r
+ if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) {\r
+ if ( handle->drainCounter == 0 ) {\r
+ handle->drainCounter = 2;\r
+ WaitForSingleObject( handle->condition, INFINITE ); // block until signaled\r
+ }\r
+\r
+ stream_.state = STREAM_STOPPED;\r
+\r
+ MUTEX_LOCK( &stream_.mutex );\r
+\r
+ // Stop the buffer and clear memory\r
+ LPDIRECTSOUNDBUFFER buffer = (LPDIRECTSOUNDBUFFER) handle->buffer[0];\r
+ result = buffer->Stop();\r
+ if ( FAILED( result ) ) {\r
+ errorStream_ << "RtApiDs::stopStream: error (" << getErrorString( result ) << ") stopping output buffer!";\r
+ errorText_ = errorStream_.str();\r
+ goto unlock;\r
+ }\r
+\r
+ // Lock the buffer and clear it so that if we start to play again,\r
+ // we won't have old data playing.\r
+ result = buffer->Lock( 0, handle->dsBufferSize[0], &audioPtr, &dataLen, NULL, NULL, 0 );\r
+ if ( FAILED( result ) ) {\r
+ errorStream_ << "RtApiDs::stopStream: error (" << getErrorString( result ) << ") locking output buffer!";\r
+ errorText_ = errorStream_.str();\r
+ goto unlock;\r
+ }\r
+\r
+ // Zero the DS buffer\r
+ ZeroMemory( audioPtr, dataLen );\r
+\r
+ // Unlock the DS buffer\r
+ result = buffer->Unlock( audioPtr, dataLen, NULL, 0 );\r
+ if ( FAILED( result ) ) {\r
+ errorStream_ << "RtApiDs::stopStream: error (" << getErrorString( result ) << ") unlocking output buffer!";\r
+ errorText_ = errorStream_.str();\r
+ goto unlock;\r
+ }\r
+\r
+ // If we start playing again, we must begin at beginning of buffer.\r
+ handle->bufferPointer[0] = 0;\r
+ }\r
+\r
+ if ( stream_.mode == INPUT || stream_.mode == DUPLEX ) {\r
+ LPDIRECTSOUNDCAPTUREBUFFER buffer = (LPDIRECTSOUNDCAPTUREBUFFER) handle->buffer[1];\r
+ audioPtr = NULL;\r
+ dataLen = 0;\r
+\r
+ stream_.state = STREAM_STOPPED;\r
+\r
+ if ( stream_.mode != DUPLEX )\r
+ MUTEX_LOCK( &stream_.mutex );\r
+\r
+ result = buffer->Stop();\r
+ if ( FAILED( result ) ) {\r
+ errorStream_ << "RtApiDs::stopStream: error (" << getErrorString( result ) << ") stopping input buffer!";\r
+ errorText_ = errorStream_.str();\r
+ goto unlock;\r
+ }\r
+\r
+ // Lock the buffer and clear it so that if we start to play again,\r
+ // we won't have old data playing.\r
+ result = buffer->Lock( 0, handle->dsBufferSize[1], &audioPtr, &dataLen, NULL, NULL, 0 );\r
+ if ( FAILED( result ) ) {\r
+ errorStream_ << "RtApiDs::stopStream: error (" << getErrorString( result ) << ") locking input buffer!";\r
+ errorText_ = errorStream_.str();\r
+ goto unlock;\r
+ }\r
+\r
+ // Zero the DS buffer\r
+ ZeroMemory( audioPtr, dataLen );\r
+\r
+ // Unlock the DS buffer\r
+ result = buffer->Unlock( audioPtr, dataLen, NULL, 0 );\r
+ if ( FAILED( result ) ) {\r
+ errorStream_ << "RtApiDs::stopStream: error (" << getErrorString( result ) << ") unlocking input buffer!";\r
+ errorText_ = errorStream_.str();\r
+ goto unlock;\r
+ }\r
+\r
+ // If we start recording again, we must begin at beginning of buffer.\r
+ handle->bufferPointer[1] = 0;\r
+ }\r
+\r
+ unlock:\r
+ timeEndPeriod( 1 ); // revert to normal scheduler frequency on lesser windows.\r
+ MUTEX_UNLOCK( &stream_.mutex );\r
+\r
+ if ( FAILED( result ) ) error( RtAudioError::SYSTEM_ERROR );\r
+}\r
+\r
+void RtApiDs :: abortStream()\r
+{\r
+ verifyStream();\r
+ if ( stream_.state == STREAM_STOPPED ) {\r
+ errorText_ = "RtApiDs::abortStream(): the stream is already stopped!";\r
+ error( RtAudioError::WARNING );\r
+ return;\r
+ }\r
+\r
+ DsHandle *handle = (DsHandle *) stream_.apiHandle;\r
+ handle->drainCounter = 2;\r
+\r
+ stopStream();\r
+}\r
+\r
+void RtApiDs :: callbackEvent()\r
+{\r
+ if ( stream_.state == STREAM_STOPPED || stream_.state == STREAM_STOPPING ) {\r
+ Sleep( 50 ); // sleep 50 milliseconds\r
+ return;\r
+ }\r
+\r
+ if ( stream_.state == STREAM_CLOSED ) {\r
+ errorText_ = "RtApiDs::callbackEvent(): the stream is closed ... this shouldn't happen!";\r
+ error( RtAudioError::WARNING );\r
+ return;\r
+ }\r
+\r
+ CallbackInfo *info = (CallbackInfo *) &stream_.callbackInfo;\r
+ DsHandle *handle = (DsHandle *) stream_.apiHandle;\r
+\r
+ // Check if we were draining the stream and signal is finished.\r
+ if ( handle->drainCounter > stream_.nBuffers + 2 ) {\r
+\r
+ stream_.state = STREAM_STOPPING;\r
+ if ( handle->internalDrain == false )\r
+ SetEvent( handle->condition );\r
+ else\r
+ stopStream();\r
+ return;\r
+ }\r
+\r
+ // Invoke user callback to get fresh output data UNLESS we are\r
+ // draining stream.\r
+ if ( handle->drainCounter == 0 ) {\r
+ RtAudioCallback callback = (RtAudioCallback) info->callback;\r
+ double streamTime = getStreamTime();\r
+ RtAudioStreamStatus status = 0;\r
+ if ( stream_.mode != INPUT && handle->xrun[0] == true ) {\r
+ status |= RTAUDIO_OUTPUT_UNDERFLOW;\r
+ handle->xrun[0] = false;\r
+ }\r
+ if ( stream_.mode != OUTPUT && handle->xrun[1] == true ) {\r
+ status |= RTAUDIO_INPUT_OVERFLOW;\r
+ handle->xrun[1] = false;\r
+ }\r
+ int cbReturnValue = callback( stream_.userBuffer[0], stream_.userBuffer[1],\r
+ stream_.bufferSize, streamTime, status, info->userData );\r
+ if ( cbReturnValue == 2 ) {\r
+ stream_.state = STREAM_STOPPING;\r
+ handle->drainCounter = 2;\r
+ abortStream();\r
+ return;\r
+ }\r
+ else if ( cbReturnValue == 1 ) {\r
+ handle->drainCounter = 1;\r
+ handle->internalDrain = true;\r
+ }\r
+ }\r
+\r
+ HRESULT result;\r
+ DWORD currentWritePointer, safeWritePointer;\r
+ DWORD currentReadPointer, safeReadPointer;\r
+ UINT nextWritePointer;\r
+\r
+ LPVOID buffer1 = NULL;\r
+ LPVOID buffer2 = NULL;\r
+ DWORD bufferSize1 = 0;\r
+ DWORD bufferSize2 = 0;\r
+\r
+ char *buffer;\r
+ long bufferBytes;\r
+\r
+ MUTEX_LOCK( &stream_.mutex );\r
+ if ( stream_.state == STREAM_STOPPED ) {\r
+ MUTEX_UNLOCK( &stream_.mutex );\r
+ return;\r
+ }\r
+\r
+ if ( buffersRolling == false ) {\r
+ if ( stream_.mode == DUPLEX ) {\r
+ //assert( handle->dsBufferSize[0] == handle->dsBufferSize[1] );\r
+\r
+ // It takes a while for the devices to get rolling. As a result,\r
+ // there's no guarantee that the capture and write device pointers\r
+ // will move in lockstep. Wait here for both devices to start\r
+ // rolling, and then set our buffer pointers accordingly.\r
+ // e.g. Crystal Drivers: the capture buffer starts up 5700 to 9600\r
+ // bytes later than the write buffer.\r
+\r
+ // Stub: a serious risk of having a pre-emptive scheduling round\r
+ // take place between the two GetCurrentPosition calls... but I'm\r
+ // really not sure how to solve the problem. Temporarily boost to\r
+ // Realtime priority, maybe; but I'm not sure what priority the\r
+ // DirectSound service threads run at. We *should* be roughly\r
+ // within a ms or so of correct.\r
+\r
+ LPDIRECTSOUNDBUFFER dsWriteBuffer = (LPDIRECTSOUNDBUFFER) handle->buffer[0];\r
+ LPDIRECTSOUNDCAPTUREBUFFER dsCaptureBuffer = (LPDIRECTSOUNDCAPTUREBUFFER) handle->buffer[1];\r
+\r
+ DWORD startSafeWritePointer, startSafeReadPointer;\r
+\r
+ result = dsWriteBuffer->GetCurrentPosition( NULL, &startSafeWritePointer );\r
+ if ( FAILED( result ) ) {\r
+ errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current write position!";\r
+ errorText_ = errorStream_.str();\r
+ MUTEX_UNLOCK( &stream_.mutex );\r
+ error( RtAudioError::SYSTEM_ERROR );\r
+ return;\r
+ }\r
+ result = dsCaptureBuffer->GetCurrentPosition( NULL, &startSafeReadPointer );\r
+ if ( FAILED( result ) ) {\r
+ errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current read position!";\r
+ errorText_ = errorStream_.str();\r
+ MUTEX_UNLOCK( &stream_.mutex );\r
+ error( RtAudioError::SYSTEM_ERROR );\r
+ return;\r
+ }\r
+ while ( true ) {\r
+ result = dsWriteBuffer->GetCurrentPosition( NULL, &safeWritePointer );\r
+ if ( FAILED( result ) ) {\r
+ errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current write position!";\r
+ errorText_ = errorStream_.str();\r
+ MUTEX_UNLOCK( &stream_.mutex );\r
+ error( RtAudioError::SYSTEM_ERROR );\r
+ return;\r
+ }\r
+ result = dsCaptureBuffer->GetCurrentPosition( NULL, &safeReadPointer );\r
+ if ( FAILED( result ) ) {\r
+ errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current read position!";\r
+ errorText_ = errorStream_.str();\r
+ MUTEX_UNLOCK( &stream_.mutex );\r
+ error( RtAudioError::SYSTEM_ERROR );\r
+ return;\r
+ }\r
+ if ( safeWritePointer != startSafeWritePointer && safeReadPointer != startSafeReadPointer ) break;\r
+ Sleep( 1 );\r
+ }\r
+\r
+ //assert( handle->dsBufferSize[0] == handle->dsBufferSize[1] );\r
+\r
+ handle->bufferPointer[0] = safeWritePointer + handle->dsPointerLeadTime[0];\r
+ if ( handle->bufferPointer[0] >= handle->dsBufferSize[0] ) handle->bufferPointer[0] -= handle->dsBufferSize[0];\r
+ handle->bufferPointer[1] = safeReadPointer;\r
+ }\r
+ else if ( stream_.mode == OUTPUT ) {\r
+\r
+ // Set the proper nextWritePosition after initial startup.\r
+ LPDIRECTSOUNDBUFFER dsWriteBuffer = (LPDIRECTSOUNDBUFFER) handle->buffer[0];\r
+ result = dsWriteBuffer->GetCurrentPosition( ¤tWritePointer, &safeWritePointer );\r
+ if ( FAILED( result ) ) {\r
+ errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current write position!";\r
+ errorText_ = errorStream_.str();\r
+ MUTEX_UNLOCK( &stream_.mutex );\r
+ error( RtAudioError::SYSTEM_ERROR );\r
+ return;\r
+ }\r
+ handle->bufferPointer[0] = safeWritePointer + handle->dsPointerLeadTime[0];\r
+ if ( handle->bufferPointer[0] >= handle->dsBufferSize[0] ) handle->bufferPointer[0] -= handle->dsBufferSize[0];\r
+ }\r
+\r
+ buffersRolling = true;\r
+ }\r
+\r
+ if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) {\r
+\r
+ LPDIRECTSOUNDBUFFER dsBuffer = (LPDIRECTSOUNDBUFFER) handle->buffer[0];\r
+\r
+ if ( handle->drainCounter > 1 ) { // write zeros to the output stream\r
+ bufferBytes = stream_.bufferSize * stream_.nUserChannels[0];\r
+ bufferBytes *= formatBytes( stream_.userFormat );\r
+ memset( stream_.userBuffer[0], 0, bufferBytes );\r
+ }\r
+\r
+ // Setup parameters and do buffer conversion if necessary.\r
+ if ( stream_.doConvertBuffer[0] ) {\r
+ buffer = stream_.deviceBuffer;\r
+ convertBuffer( buffer, stream_.userBuffer[0], stream_.convertInfo[0] );\r
+ bufferBytes = stream_.bufferSize * stream_.nDeviceChannels[0];\r
+ bufferBytes *= formatBytes( stream_.deviceFormat[0] );\r
+ }\r
+ else {\r
+ buffer = stream_.userBuffer[0];\r
+ bufferBytes = stream_.bufferSize * stream_.nUserChannels[0];\r
+ bufferBytes *= formatBytes( stream_.userFormat );\r
+ }\r
+\r
+ // No byte swapping necessary in DirectSound implementation.\r
+\r
+ // Ahhh ... windoze. 16-bit data is signed but 8-bit data is\r
+ // unsigned. So, we need to convert our signed 8-bit data here to\r
+ // unsigned.\r
+ if ( stream_.deviceFormat[0] == RTAUDIO_SINT8 )\r
+ for ( int i=0; i<bufferBytes; i++ ) buffer[i] = (unsigned char) ( buffer[i] + 128 );\r
+\r
+ DWORD dsBufferSize = handle->dsBufferSize[0];\r
+ nextWritePointer = handle->bufferPointer[0];\r
+\r
+ DWORD endWrite, leadPointer;\r
+ while ( true ) {\r
+ // Find out where the read and "safe write" pointers are.\r
+ result = dsBuffer->GetCurrentPosition( ¤tWritePointer, &safeWritePointer );\r
+ if ( FAILED( result ) ) {\r
+ errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current write position!";\r
+ errorText_ = errorStream_.str();\r
+ MUTEX_UNLOCK( &stream_.mutex );\r
+ error( RtAudioError::SYSTEM_ERROR );\r
+ return;\r
+ }\r
+\r
+ // We will copy our output buffer into the region between\r
+ // safeWritePointer and leadPointer. If leadPointer is not\r
+ // beyond the next endWrite position, wait until it is.\r
+ leadPointer = safeWritePointer + handle->dsPointerLeadTime[0];\r
+ //std::cout << "safeWritePointer = " << safeWritePointer << ", leadPointer = " << leadPointer << ", nextWritePointer = " << nextWritePointer << std::endl;\r
+ if ( leadPointer > dsBufferSize ) leadPointer -= dsBufferSize;\r
+ if ( leadPointer < nextWritePointer ) leadPointer += dsBufferSize; // unwrap offset\r
+ endWrite = nextWritePointer + bufferBytes;\r
+\r
+ // Check whether the entire write region is behind the play pointer.\r
+ if ( leadPointer >= endWrite ) break;\r
+\r
+ // If we are here, then we must wait until the leadPointer advances\r
+ // beyond the end of our next write region. We use the\r
+ // Sleep() function to suspend operation until that happens.\r
+ double millis = ( endWrite - leadPointer ) * 1000.0;\r
+ millis /= ( formatBytes( stream_.deviceFormat[0]) * stream_.nDeviceChannels[0] * stream_.sampleRate);\r
+ if ( millis < 1.0 ) millis = 1.0;\r
+ Sleep( (DWORD) millis );\r
+ }\r
+\r
+ if ( dsPointerBetween( nextWritePointer, safeWritePointer, currentWritePointer, dsBufferSize )\r
+ || dsPointerBetween( endWrite, safeWritePointer, currentWritePointer, dsBufferSize ) ) {\r
+ // We've strayed into the forbidden zone ... resync the read pointer.\r
+ handle->xrun[0] = true;\r
+ nextWritePointer = safeWritePointer + handle->dsPointerLeadTime[0] - bufferBytes;\r
+ if ( nextWritePointer >= dsBufferSize ) nextWritePointer -= dsBufferSize;\r
+ handle->bufferPointer[0] = nextWritePointer;\r
+ endWrite = nextWritePointer + bufferBytes;\r
+ }\r
+\r
+ // Lock free space in the buffer\r
+ result = dsBuffer->Lock( nextWritePointer, bufferBytes, &buffer1,\r
+ &bufferSize1, &buffer2, &bufferSize2, 0 );\r
+ if ( FAILED( result ) ) {\r
+ errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") locking buffer during playback!";\r
+ errorText_ = errorStream_.str();\r
+ MUTEX_UNLOCK( &stream_.mutex );\r
+ error( RtAudioError::SYSTEM_ERROR );\r
+ return;\r
+ }\r
+\r
+ // Copy our buffer into the DS buffer\r
+ CopyMemory( buffer1, buffer, bufferSize1 );\r
+ if ( buffer2 != NULL ) CopyMemory( buffer2, buffer+bufferSize1, bufferSize2 );\r
+\r
+ // Update our buffer offset and unlock sound buffer\r
+ dsBuffer->Unlock( buffer1, bufferSize1, buffer2, bufferSize2 );\r
+ if ( FAILED( result ) ) {\r
+ errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") unlocking buffer during playback!";\r
+ errorText_ = errorStream_.str();\r
+ MUTEX_UNLOCK( &stream_.mutex );\r
+ error( RtAudioError::SYSTEM_ERROR );\r
+ return;\r
+ }\r
+ nextWritePointer = ( nextWritePointer + bufferSize1 + bufferSize2 ) % dsBufferSize;\r
+ handle->bufferPointer[0] = nextWritePointer;\r
+ }\r
+\r
+ // Don't bother draining input\r
+ if ( handle->drainCounter ) {\r
+ handle->drainCounter++;\r
+ goto unlock;\r
+ }\r
+\r
+ if ( stream_.mode == INPUT || stream_.mode == DUPLEX ) {\r
+\r
+ // Setup parameters.\r
+ if ( stream_.doConvertBuffer[1] ) {\r
+ buffer = stream_.deviceBuffer;\r
+ bufferBytes = stream_.bufferSize * stream_.nDeviceChannels[1];\r
+ bufferBytes *= formatBytes( stream_.deviceFormat[1] );\r
+ }\r
+ else {\r
+ buffer = stream_.userBuffer[1];\r
+ bufferBytes = stream_.bufferSize * stream_.nUserChannels[1];\r
+ bufferBytes *= formatBytes( stream_.userFormat );\r
+ }\r
+\r
+ LPDIRECTSOUNDCAPTUREBUFFER dsBuffer = (LPDIRECTSOUNDCAPTUREBUFFER) handle->buffer[1];\r
+ long nextReadPointer = handle->bufferPointer[1];\r
+ DWORD dsBufferSize = handle->dsBufferSize[1];\r
+\r
+ // Find out where the write and "safe read" pointers are.\r
+ result = dsBuffer->GetCurrentPosition( ¤tReadPointer, &safeReadPointer );\r
+ if ( FAILED( result ) ) {\r
+ errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current read position!";\r
+ errorText_ = errorStream_.str();\r
+ MUTEX_UNLOCK( &stream_.mutex );\r
+ error( RtAudioError::SYSTEM_ERROR );\r
+ return;\r
+ }\r
+\r
+ if ( safeReadPointer < (DWORD)nextReadPointer ) safeReadPointer += dsBufferSize; // unwrap offset\r
+ DWORD endRead = nextReadPointer + bufferBytes;\r
+\r
+ // Handling depends on whether we are INPUT or DUPLEX.\r
+ // If we're in INPUT mode then waiting is a good thing. If we're in DUPLEX mode,\r
+ // then a wait here will drag the write pointers into the forbidden zone.\r
+ //\r
+ // In DUPLEX mode, rather than wait, we will back off the read pointer until\r
+ // it's in a safe position. This causes dropouts, but it seems to be the only\r
+ // practical way to sync up the read and write pointers reliably, given the\r
+ // the very complex relationship between phase and increment of the read and write\r
+ // pointers.\r
+ //\r
+ // In order to minimize audible dropouts in DUPLEX mode, we will\r
+ // provide a pre-roll period of 0.5 seconds in which we return\r
+ // zeros from the read buffer while the pointers sync up.\r
+\r
+ if ( stream_.mode == DUPLEX ) {\r
+ if ( safeReadPointer < endRead ) {\r
+ if ( duplexPrerollBytes <= 0 ) {\r
+ // Pre-roll time over. Be more agressive.\r
+ int adjustment = endRead-safeReadPointer;\r
+\r
+ handle->xrun[1] = true;\r
+ // Two cases:\r
+ // - large adjustments: we've probably run out of CPU cycles, so just resync exactly,\r
+ // and perform fine adjustments later.\r
+ // - small adjustments: back off by twice as much.\r
+ if ( adjustment >= 2*bufferBytes )\r
+ nextReadPointer = safeReadPointer-2*bufferBytes;\r
+ else\r
+ nextReadPointer = safeReadPointer-bufferBytes-adjustment;\r
+\r
+ if ( nextReadPointer < 0 ) nextReadPointer += dsBufferSize;\r
+\r
+ }\r
+ else {\r
+ // In pre=roll time. Just do it.\r
+ nextReadPointer = safeReadPointer - bufferBytes;\r
+ while ( nextReadPointer < 0 ) nextReadPointer += dsBufferSize;\r
+ }\r
+ endRead = nextReadPointer + bufferBytes;\r
+ }\r
+ }\r
+ else { // mode == INPUT\r
+ while ( safeReadPointer < endRead && stream_.callbackInfo.isRunning ) {\r
+ // See comments for playback.\r
+ double millis = (endRead - safeReadPointer) * 1000.0;\r
+ millis /= ( formatBytes(stream_.deviceFormat[1]) * stream_.nDeviceChannels[1] * stream_.sampleRate);\r
+ if ( millis < 1.0 ) millis = 1.0;\r
+ Sleep( (DWORD) millis );\r
+\r
+ // Wake up and find out where we are now.\r
+ result = dsBuffer->GetCurrentPosition( ¤tReadPointer, &safeReadPointer );\r
+ if ( FAILED( result ) ) {\r
+ errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current read position!";\r
+ errorText_ = errorStream_.str();\r
+ MUTEX_UNLOCK( &stream_.mutex );\r
+ error( RtAudioError::SYSTEM_ERROR );\r
+ return;\r
+ }\r
+\r
+ if ( safeReadPointer < (DWORD)nextReadPointer ) safeReadPointer += dsBufferSize; // unwrap offset\r
+ }\r
+ }\r
+\r
+ // Lock free space in the buffer\r
+ result = dsBuffer->Lock( nextReadPointer, bufferBytes, &buffer1,\r
+ &bufferSize1, &buffer2, &bufferSize2, 0 );\r
+ if ( FAILED( result ) ) {\r
+ errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") locking capture buffer!";\r
+ errorText_ = errorStream_.str();\r
+ MUTEX_UNLOCK( &stream_.mutex );\r
+ error( RtAudioError::SYSTEM_ERROR );\r
+ return;\r
+ }\r
+\r
+ if ( duplexPrerollBytes <= 0 ) {\r
+ // Copy our buffer into the DS buffer\r
+ CopyMemory( buffer, buffer1, bufferSize1 );\r
+ if ( buffer2 != NULL ) CopyMemory( buffer+bufferSize1, buffer2, bufferSize2 );\r
+ }\r
+ else {\r
+ memset( buffer, 0, bufferSize1 );\r
+ if ( buffer2 != NULL ) memset( buffer + bufferSize1, 0, bufferSize2 );\r
+ duplexPrerollBytes -= bufferSize1 + bufferSize2;\r
+ }\r
+\r
+ // Update our buffer offset and unlock sound buffer\r
+ nextReadPointer = ( nextReadPointer + bufferSize1 + bufferSize2 ) % dsBufferSize;\r
+ dsBuffer->Unlock( buffer1, bufferSize1, buffer2, bufferSize2 );\r
+ if ( FAILED( result ) ) {\r
+ errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") unlocking capture buffer!";\r
+ errorText_ = errorStream_.str();\r
+ MUTEX_UNLOCK( &stream_.mutex );\r
+ error( RtAudioError::SYSTEM_ERROR );\r
+ return;\r
+ }\r
+ handle->bufferPointer[1] = nextReadPointer;\r
+\r
+ // No byte swapping necessary in DirectSound implementation.\r
+\r
+ // If necessary, convert 8-bit data from unsigned to signed.\r
+ if ( stream_.deviceFormat[1] == RTAUDIO_SINT8 )\r
+ for ( int j=0; j<bufferBytes; j++ ) buffer[j] = (signed char) ( buffer[j] - 128 );\r
+\r
+ // Do buffer conversion if necessary.\r
+ if ( stream_.doConvertBuffer[1] )\r
+ convertBuffer( stream_.userBuffer[1], stream_.deviceBuffer, stream_.convertInfo[1] );\r
+ }\r
+\r
+ unlock:\r
+ MUTEX_UNLOCK( &stream_.mutex );\r
+ RtApi::tickStreamTime();\r
+}\r
+\r
+// Definitions for utility functions and callbacks\r
+// specific to the DirectSound implementation.\r
+\r
+static unsigned __stdcall callbackHandler( void *ptr )\r
+{\r
+ CallbackInfo *info = (CallbackInfo *) ptr;\r
+ RtApiDs *object = (RtApiDs *) info->object;\r
+ bool* isRunning = &info->isRunning;\r
+\r
+ while ( *isRunning == true ) {\r
+ object->callbackEvent();\r
+ }\r
+\r
+ _endthreadex( 0 );\r
+ return 0;\r
+}\r
+\r
+static BOOL CALLBACK deviceQueryCallback( LPGUID lpguid,\r
+ LPCTSTR description,\r
+ LPCTSTR /*module*/,\r
+ LPVOID lpContext )\r
+{\r
+ struct DsProbeData& probeInfo = *(struct DsProbeData*) lpContext;\r
+ std::vector<struct DsDevice>& dsDevices = *probeInfo.dsDevices;\r
+\r
+ HRESULT hr;\r
+ bool validDevice = false;\r
+ if ( probeInfo.isInput == true ) {\r
+ DSCCAPS caps;\r
+ LPDIRECTSOUNDCAPTURE object;\r
+\r
+ hr = DirectSoundCaptureCreate( lpguid, &object, NULL );\r
+ if ( hr != DS_OK ) return TRUE;\r
+\r
+ caps.dwSize = sizeof(caps);\r
+ hr = object->GetCaps( &caps );\r
+ if ( hr == DS_OK ) {\r
+ if ( caps.dwChannels > 0 && caps.dwFormats > 0 )\r
+ validDevice = true;\r
+ }\r
+ object->Release();\r
+ }\r
+ else {\r
+ DSCAPS caps;\r
+ LPDIRECTSOUND object;\r
+ hr = DirectSoundCreate( lpguid, &object, NULL );\r
+ if ( hr != DS_OK ) return TRUE;\r
+\r
+ caps.dwSize = sizeof(caps);\r
+ hr = object->GetCaps( &caps );\r
+ if ( hr == DS_OK ) {\r
+ if ( caps.dwFlags & DSCAPS_PRIMARYMONO || caps.dwFlags & DSCAPS_PRIMARYSTEREO )\r
+ validDevice = true;\r
+ }\r
+ object->Release();\r
+ }\r
+\r
+ // If good device, then save its name and guid.\r
+ std::string name = convertCharPointerToStdString( description );\r
+ //if ( name == "Primary Sound Driver" || name == "Primary Sound Capture Driver" )\r
+ if ( lpguid == NULL )\r
+ name = "Default Device";\r
+ if ( validDevice ) {\r
+ for ( unsigned int i=0; i<dsDevices.size(); i++ ) {\r
+ if ( dsDevices[i].name == name ) {\r
+ dsDevices[i].found = true;\r
+ if ( probeInfo.isInput ) {\r
+ dsDevices[i].id[1] = lpguid;\r
+ dsDevices[i].validId[1] = true;\r
+ }\r
+ else {\r
+ dsDevices[i].id[0] = lpguid;\r
+ dsDevices[i].validId[0] = true;\r
+ }\r
+ return TRUE;\r
+ }\r
+ }\r
+\r
+ DsDevice device;\r
+ device.name = name;\r
+ device.found = true;\r
+ if ( probeInfo.isInput ) {\r
+ device.id[1] = lpguid;\r
+ device.validId[1] = true;\r
+ }\r
+ else {\r
+ device.id[0] = lpguid;\r
+ device.validId[0] = true;\r
+ }\r
+ dsDevices.push_back( device );\r
+ }\r
+\r
+ return TRUE;\r
+}\r
+\r
+static const char* getErrorString( int code )\r
+{\r
+ switch ( code ) {\r
+\r
+ case DSERR_ALLOCATED:\r
+ return "Already allocated";\r
+\r
+ case DSERR_CONTROLUNAVAIL:\r
+ return "Control unavailable";\r
+\r
+ case DSERR_INVALIDPARAM:\r
+ return "Invalid parameter";\r
+\r
+ case DSERR_INVALIDCALL:\r
+ return "Invalid call";\r
+\r
+ case DSERR_GENERIC:\r
+ return "Generic error";\r
+\r
+ case DSERR_PRIOLEVELNEEDED:\r
+ return "Priority level needed";\r
+\r
+ case DSERR_OUTOFMEMORY:\r
+ return "Out of memory";\r
+\r
+ case DSERR_BADFORMAT:\r
+ return "The sample rate or the channel format is not supported";\r
+\r
+ case DSERR_UNSUPPORTED:\r
+ return "Not supported";\r
+\r
+ case DSERR_NODRIVER:\r
+ return "No driver";\r
+\r
+ case DSERR_ALREADYINITIALIZED:\r
+ return "Already initialized";\r
+\r
+ case DSERR_NOAGGREGATION:\r
+ return "No aggregation";\r
+\r
+ case DSERR_BUFFERLOST:\r
+ return "Buffer lost";\r
+\r
+ case DSERR_OTHERAPPHASPRIO:\r
+ return "Another application already has priority";\r
+\r
+ case DSERR_UNINITIALIZED:\r
+ return "Uninitialized";\r
+\r
+ default:\r
+ return "DirectSound unknown error";\r
+ }\r
+}\r
+//******************** End of __WINDOWS_DS__ *********************//\r
+#endif\r
+\r
+\r
+#if defined(__LINUX_ALSA__)\r
+\r
+#include <alsa/asoundlib.h>\r
+#include <unistd.h>\r
+\r
+ // A structure to hold various information related to the ALSA API\r
+ // implementation.\r
+struct AlsaHandle {\r
+ snd_pcm_t *handles[2];\r
+ bool synchronized;\r
+ bool xrun[2];\r
+ pthread_cond_t runnable_cv;\r
+ bool runnable;\r
+\r
+ AlsaHandle()\r
+ :synchronized(false), runnable(false) { xrun[0] = false; xrun[1] = false; }\r
+};\r
+\r
+static void *alsaCallbackHandler( void * ptr );\r
+\r
+RtApiAlsa :: RtApiAlsa()\r
+{\r
+ // Nothing to do here.\r
+}\r
+\r
+RtApiAlsa :: ~RtApiAlsa()\r
+{\r
+ if ( stream_.state != STREAM_CLOSED ) closeStream();\r
+}\r
+\r
+unsigned int RtApiAlsa :: getDeviceCount( void )\r
+{\r
+ unsigned nDevices = 0;\r
+ int result, subdevice, card;\r
+ char name[64];\r
+ snd_ctl_t *handle;\r
+\r
+ // Count cards and devices\r
+ card = -1;\r
+ snd_card_next( &card );\r
+ while ( card >= 0 ) {\r
+ sprintf( name, "hw:%d", card );\r
+ result = snd_ctl_open( &handle, name, 0 );\r
+ if ( result < 0 ) {\r
+ errorStream_ << "RtApiAlsa::getDeviceCount: control open, card = " << card << ", " << snd_strerror( result ) << ".";\r
+ errorText_ = errorStream_.str();\r
+ error( RtAudioError::WARNING );\r
+ goto nextcard;\r
+ }\r
+ subdevice = -1;\r
+ while( 1 ) {\r
+ result = snd_ctl_pcm_next_device( handle, &subdevice );\r
+ if ( result < 0 ) {\r
+ errorStream_ << "RtApiAlsa::getDeviceCount: control next device, card = " << card << ", " << snd_strerror( result ) << ".";\r
+ errorText_ = errorStream_.str();\r
+ error( RtAudioError::WARNING );\r
+ break;\r
+ }\r
+ if ( subdevice < 0 )\r
+ break;\r
+ nDevices++;\r
+ }\r
+ nextcard:\r
+ snd_ctl_close( handle );\r
+ snd_card_next( &card );\r
+ }\r
+\r
+ result = snd_ctl_open( &handle, "default", 0 );\r
+ if (result == 0) {\r
+ nDevices++;\r
+ snd_ctl_close( handle );\r
+ }\r
+\r
+ return nDevices;\r
+}\r
+\r
+RtAudio::DeviceInfo RtApiAlsa :: getDeviceInfo( unsigned int device )\r
+{\r
+ RtAudio::DeviceInfo info;\r
+ info.probed = false;\r
+\r
+ unsigned nDevices = 0;\r
+ int result, subdevice, card;\r
+ char name[64];\r
+ snd_ctl_t *chandle;\r
+\r
+ // Count cards and devices\r
+ card = -1;\r
+ subdevice = -1;\r
+ snd_card_next( &card );\r
+ while ( card >= 0 ) {\r
+ sprintf( name, "hw:%d", card );\r
+ result = snd_ctl_open( &chandle, name, SND_CTL_NONBLOCK );\r
+ if ( result < 0 ) {\r
+ errorStream_ << "RtApiAlsa::getDeviceInfo: control open, card = " << card << ", " << snd_strerror( result ) << ".";\r
+ errorText_ = errorStream_.str();\r
+ error( RtAudioError::WARNING );\r
+ goto nextcard;\r
+ }\r
+ subdevice = -1;\r
+ while( 1 ) {\r
+ result = snd_ctl_pcm_next_device( chandle, &subdevice );\r
+ if ( result < 0 ) {\r
+ errorStream_ << "RtApiAlsa::getDeviceInfo: control next device, card = " << card << ", " << snd_strerror( result ) << ".";\r
+ errorText_ = errorStream_.str();\r
+ error( RtAudioError::WARNING );\r
+ break;\r
+ }\r
+ if ( subdevice < 0 ) break;\r
+ if ( nDevices == device ) {\r
+ sprintf( name, "hw:%d,%d", card, subdevice );\r
+ goto foundDevice;\r
+ }\r
+ nDevices++;\r
+ }\r
+ nextcard:\r
+ snd_ctl_close( chandle );\r
+ snd_card_next( &card );\r
+ }\r
+\r
+ result = snd_ctl_open( &chandle, "default", SND_CTL_NONBLOCK );\r
+ if ( result == 0 ) {\r
+ if ( nDevices == device ) {\r
+ strcpy( name, "default" );\r
+ goto foundDevice;\r
+ }\r
+ nDevices++;\r
+ }\r
+\r
+ if ( nDevices == 0 ) {\r
+ errorText_ = "RtApiAlsa::getDeviceInfo: no devices found!";\r
+ error( RtAudioError::INVALID_USE );\r
+ return info;\r
+ }\r
+\r
+ if ( device >= nDevices ) {\r
+ errorText_ = "RtApiAlsa::getDeviceInfo: device ID is invalid!";\r
+ error( RtAudioError::INVALID_USE );\r
+ return info;\r
+ }\r
+\r
+ foundDevice:\r
+\r
+ // If a stream is already open, we cannot probe the stream devices.\r
+ // Thus, use the saved results.\r
+ if ( stream_.state != STREAM_CLOSED &&\r
+ ( stream_.device[0] == device || stream_.device[1] == device ) ) {\r
+ snd_ctl_close( chandle );\r
+ if ( device >= devices_.size() ) {\r
+ errorText_ = "RtApiAlsa::getDeviceInfo: device ID was not present before stream was opened.";\r
+ error( RtAudioError::WARNING );\r
+ return info;\r
+ }\r
+ return devices_[ device ];\r
+ }\r
+\r
+ int openMode = SND_PCM_ASYNC;\r
+ snd_pcm_stream_t stream;\r
+ snd_pcm_info_t *pcminfo;\r
+ snd_pcm_info_alloca( &pcminfo );\r
+ snd_pcm_t *phandle;\r
+ snd_pcm_hw_params_t *params;\r
+ snd_pcm_hw_params_alloca( ¶ms );\r
+\r
+ // First try for playback unless default device (which has subdev -1)\r
+ stream = SND_PCM_STREAM_PLAYBACK;\r
+ snd_pcm_info_set_stream( pcminfo, stream );\r
+ if ( subdevice != -1 ) {\r
+ snd_pcm_info_set_device( pcminfo, subdevice );\r
+ snd_pcm_info_set_subdevice( pcminfo, 0 );\r
+\r
+ result = snd_ctl_pcm_info( chandle, pcminfo );\r
+ if ( result < 0 ) {\r
+ // Device probably doesn't support playback.\r
+ goto captureProbe;\r
+ }\r
+ }\r
+\r
+ result = snd_pcm_open( &phandle, name, stream, openMode | SND_PCM_NONBLOCK );\r
+ if ( result < 0 ) {\r
+ errorStream_ << "RtApiAlsa::getDeviceInfo: snd_pcm_open error for device (" << name << "), " << snd_strerror( result ) << ".";\r
+ errorText_ = errorStream_.str();\r
+ error( RtAudioError::WARNING );\r
+ goto captureProbe;\r
+ }\r
+\r
+ // The device is open ... fill the parameter structure.\r
+ result = snd_pcm_hw_params_any( phandle, params );\r
+ if ( result < 0 ) {\r
+ snd_pcm_close( phandle );\r
+ errorStream_ << "RtApiAlsa::getDeviceInfo: snd_pcm_hw_params error for device (" << name << "), " << snd_strerror( result ) << ".";\r
+ errorText_ = errorStream_.str();\r
+ error( RtAudioError::WARNING );\r
+ goto captureProbe;\r
+ }\r
+\r
+ // Get output channel information.\r
+ unsigned int value;\r
+ result = snd_pcm_hw_params_get_channels_max( params, &value );\r
+ if ( result < 0 ) {\r
+ snd_pcm_close( phandle );\r
+ errorStream_ << "RtApiAlsa::getDeviceInfo: error getting device (" << name << ") output channels, " << snd_strerror( result ) << ".";\r
+ errorText_ = errorStream_.str();\r
+ error( RtAudioError::WARNING );\r
+ goto captureProbe;\r
+ }\r
+ info.outputChannels = value;\r
+ snd_pcm_close( phandle );\r
+\r
+ captureProbe:\r
+ stream = SND_PCM_STREAM_CAPTURE;\r
+ snd_pcm_info_set_stream( pcminfo, stream );\r
+\r
+ // Now try for capture unless default device (with subdev = -1)\r
+ if ( subdevice != -1 ) {\r
+ result = snd_ctl_pcm_info( chandle, pcminfo );\r
+ snd_ctl_close( chandle );\r
+ if ( result < 0 ) {\r
+ // Device probably doesn't support capture.\r
+ if ( info.outputChannels == 0 ) return info;\r
+ goto probeParameters;\r
+ }\r
+ }\r
+ else\r
+ snd_ctl_close( chandle );\r
+\r
+ result = snd_pcm_open( &phandle, name, stream, openMode | SND_PCM_NONBLOCK);\r
+ if ( result < 0 ) {\r
+ errorStream_ << "RtApiAlsa::getDeviceInfo: snd_pcm_open error for device (" << name << "), " << snd_strerror( result ) << ".";\r
+ errorText_ = errorStream_.str();\r
+ error( RtAudioError::WARNING );\r
+ if ( info.outputChannels == 0 ) return info;\r
+ goto probeParameters;\r
+ }\r
+\r
+ // The device is open ... fill the parameter structure.\r
+ result = snd_pcm_hw_params_any( phandle, params );\r
+ if ( result < 0 ) {\r
+ snd_pcm_close( phandle );\r
+ errorStream_ << "RtApiAlsa::getDeviceInfo: snd_pcm_hw_params error for device (" << name << "), " << snd_strerror( result ) << ".";\r
+ errorText_ = errorStream_.str();\r
+ error( RtAudioError::WARNING );\r
+ if ( info.outputChannels == 0 ) return info;\r
+ goto probeParameters;\r
+ }\r
+\r
+ result = snd_pcm_hw_params_get_channels_max( params, &value );\r
+ if ( result < 0 ) {\r
+ snd_pcm_close( phandle );\r
+ errorStream_ << "RtApiAlsa::getDeviceInfo: error getting device (" << name << ") input channels, " << snd_strerror( result ) << ".";\r
+ errorText_ = errorStream_.str();\r
+ error( RtAudioError::WARNING );\r
+ if ( info.outputChannels == 0 ) return info;\r
+ goto probeParameters;\r
+ }\r
+ info.inputChannels = value;\r
+ snd_pcm_close( phandle );\r
+\r
+ // If device opens for both playback and capture, we determine the channels.\r
+ if ( info.outputChannels > 0 && info.inputChannels > 0 )\r
+ info.duplexChannels = (info.outputChannels > info.inputChannels) ? info.inputChannels : info.outputChannels;\r
+\r
+ // ALSA doesn't provide default devices so we'll use the first available one.\r
+ if ( device == 0 && info.outputChannels > 0 )\r
+ info.isDefaultOutput = true;\r
+ if ( device == 0 && info.inputChannels > 0 )\r
+ info.isDefaultInput = true;\r
+\r
+ probeParameters:\r
+ // At this point, we just need to figure out the supported data\r
+ // formats and sample rates. We'll proceed by opening the device in\r
+ // the direction with the maximum number of channels, or playback if\r
+ // they are equal. This might limit our sample rate options, but so\r
+ // be it.\r
+\r
+ if ( info.outputChannels >= info.inputChannels )\r
+ stream = SND_PCM_STREAM_PLAYBACK;\r
+ else\r
+ stream = SND_PCM_STREAM_CAPTURE;\r
+ snd_pcm_info_set_stream( pcminfo, stream );\r
+\r
+ result = snd_pcm_open( &phandle, name, stream, openMode | SND_PCM_NONBLOCK);\r
+ if ( result < 0 ) {\r
+ errorStream_ << "RtApiAlsa::getDeviceInfo: snd_pcm_open error for device (" << name << "), " << snd_strerror( result ) << ".";\r
+ errorText_ = errorStream_.str();\r
+ error( RtAudioError::WARNING );\r
+ return info;\r
+ }\r
+\r
+ // The device is open ... fill the parameter structure.\r
+ result = snd_pcm_hw_params_any( phandle, params );\r
+ if ( result < 0 ) {\r
+ snd_pcm_close( phandle );\r
+ errorStream_ << "RtApiAlsa::getDeviceInfo: snd_pcm_hw_params error for device (" << name << "), " << snd_strerror( result ) << ".";\r
+ errorText_ = errorStream_.str();\r
+ error( RtAudioError::WARNING );\r
+ return info;\r
+ }\r
+\r
+ // Test our discrete set of sample rate values.\r
+ info.sampleRates.clear();\r
+ for ( unsigned int i=0; i<MAX_SAMPLE_RATES; i++ ) {\r
+ if ( snd_pcm_hw_params_test_rate( phandle, params, SAMPLE_RATES[i], 0 ) == 0 ) {\r
+ info.sampleRates.push_back( SAMPLE_RATES[i] );\r
+\r
+ if ( !info.preferredSampleRate || ( SAMPLE_RATES[i] <= 48000 && SAMPLE_RATES[i] > info.preferredSampleRate ) )\r
+ info.preferredSampleRate = SAMPLE_RATES[i];\r
+ }\r
+ }\r
+ if ( info.sampleRates.size() == 0 ) {\r
+ snd_pcm_close( phandle );\r
+ errorStream_ << "RtApiAlsa::getDeviceInfo: no supported sample rates found for device (" << name << ").";\r
+ errorText_ = errorStream_.str();\r
+ error( RtAudioError::WARNING );\r
+ return info;\r
+ }\r
+\r
+ // Probe the supported data formats ... we don't care about endian-ness just yet\r
+ snd_pcm_format_t format;\r
+ info.nativeFormats = 0;\r
+ format = SND_PCM_FORMAT_S8;\r
+ if ( snd_pcm_hw_params_test_format( phandle, params, format ) == 0 )\r
+ info.nativeFormats |= RTAUDIO_SINT8;\r
+ format = SND_PCM_FORMAT_S16;\r
+ if ( snd_pcm_hw_params_test_format( phandle, params, format ) == 0 )\r
+ info.nativeFormats |= RTAUDIO_SINT16;\r
+ format = SND_PCM_FORMAT_S24;\r
+ if ( snd_pcm_hw_params_test_format( phandle, params, format ) == 0 )\r
+ info.nativeFormats |= RTAUDIO_SINT24;\r
+ format = SND_PCM_FORMAT_S32;\r
+ if ( snd_pcm_hw_params_test_format( phandle, params, format ) == 0 )\r
+ info.nativeFormats |= RTAUDIO_SINT32;\r
+ format = SND_PCM_FORMAT_FLOAT;\r
+ if ( snd_pcm_hw_params_test_format( phandle, params, format ) == 0 )\r
+ info.nativeFormats |= RTAUDIO_FLOAT32;\r
+ format = SND_PCM_FORMAT_FLOAT64;\r
+ if ( snd_pcm_hw_params_test_format( phandle, params, format ) == 0 )\r
+ info.nativeFormats |= RTAUDIO_FLOAT64;\r
+\r
+ // Check that we have at least one supported format\r
+ if ( info.nativeFormats == 0 ) {\r
+ snd_pcm_close( phandle );\r
+ errorStream_ << "RtApiAlsa::getDeviceInfo: pcm device (" << name << ") data format not supported by RtAudio.";\r
+ errorText_ = errorStream_.str();\r
+ error( RtAudioError::WARNING );\r
+ return info;\r
+ }\r
+\r
+ // Get the device name\r
+ char *cardname;\r
+ result = snd_card_get_name( card, &cardname );\r
+ if ( result >= 0 ) {\r
+ sprintf( name, "hw:%s,%d", cardname, subdevice );\r
+ free( cardname );\r
+ }\r
+ info.name = name;\r
+\r
+ // That's all ... close the device and return\r
+ snd_pcm_close( phandle );\r
+ info.probed = true;\r
+ return info;\r
+}\r
+\r
+void RtApiAlsa :: saveDeviceInfo( void )\r
+{\r
+ devices_.clear();\r
+\r
+ unsigned int nDevices = getDeviceCount();\r
+ devices_.resize( nDevices );\r
+ for ( unsigned int i=0; i<nDevices; i++ )\r
+ devices_[i] = getDeviceInfo( i );\r
+}\r
+\r
+bool RtApiAlsa :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels,\r
+ unsigned int firstChannel, unsigned int sampleRate,\r
+ RtAudioFormat format, unsigned int *bufferSize,\r
+ RtAudio::StreamOptions *options )\r
+\r
+{\r
+#if defined(__RTAUDIO_DEBUG__)\r
+ snd_output_t *out;\r
+ snd_output_stdio_attach(&out, stderr, 0);\r
+#endif\r
+\r
+ // I'm not using the "plug" interface ... too much inconsistent behavior.\r
+\r
+ unsigned nDevices = 0;\r
+ int result, subdevice, card;\r
+ char name[64];\r
+ snd_ctl_t *chandle;\r
+\r
+ if ( options && options->flags & RTAUDIO_ALSA_USE_DEFAULT )\r
+ snprintf(name, sizeof(name), "%s", "default");\r
+ else {\r
+ // Count cards and devices\r
+ card = -1;\r
+ snd_card_next( &card );\r
+ while ( card >= 0 ) {\r
+ sprintf( name, "hw:%d", card );\r
+ result = snd_ctl_open( &chandle, name, SND_CTL_NONBLOCK );\r
+ if ( result < 0 ) {\r
+ errorStream_ << "RtApiAlsa::probeDeviceOpen: control open, card = " << card << ", " << snd_strerror( result ) << ".";\r
+ errorText_ = errorStream_.str();\r
+ return FAILURE;\r
+ }\r
+ subdevice = -1;\r
+ while( 1 ) {\r
+ result = snd_ctl_pcm_next_device( chandle, &subdevice );\r
+ if ( result < 0 ) break;\r
+ if ( subdevice < 0 ) break;\r
+ if ( nDevices == device ) {\r
+ sprintf( name, "hw:%d,%d", card, subdevice );\r
+ snd_ctl_close( chandle );\r
+ goto foundDevice;\r
+ }\r
+ nDevices++;\r
+ }\r
+ snd_ctl_close( chandle );\r
+ snd_card_next( &card );\r
+ }\r
+\r
+ result = snd_ctl_open( &chandle, "default", SND_CTL_NONBLOCK );\r
+ if ( result == 0 ) {\r
+ if ( nDevices == device ) {\r
+ strcpy( name, "default" );\r
+ goto foundDevice;\r
+ }\r
+ nDevices++;\r
+ }\r
+\r
+ if ( nDevices == 0 ) {\r
+ // This should not happen because a check is made before this function is called.\r
+ errorText_ = "RtApiAlsa::probeDeviceOpen: no devices found!";\r
+ return FAILURE;\r
+ }\r
+\r
+ if ( device >= nDevices ) {\r
+ // This should not happen because a check is made before this function is called.\r
+ errorText_ = "RtApiAlsa::probeDeviceOpen: device ID is invalid!";\r
+ return FAILURE;\r
+ }\r
+ }\r
+\r
+ foundDevice:\r
+\r
+ // The getDeviceInfo() function will not work for a device that is\r
+ // already open. Thus, we'll probe the system before opening a\r
+ // stream and save the results for use by getDeviceInfo().\r
+ if ( mode == OUTPUT || ( mode == INPUT && stream_.mode != OUTPUT ) ) // only do once\r
+ this->saveDeviceInfo();\r
+\r
+ snd_pcm_stream_t stream;\r
+ if ( mode == OUTPUT )\r
+ stream = SND_PCM_STREAM_PLAYBACK;\r
+ else\r
+ stream = SND_PCM_STREAM_CAPTURE;\r
+\r
+ snd_pcm_t *phandle;\r
+ int openMode = SND_PCM_ASYNC;\r
+ result = snd_pcm_open( &phandle, name, stream, openMode );\r
+ if ( result < 0 ) {\r
+ if ( mode == OUTPUT )\r
+ errorStream_ << "RtApiAlsa::probeDeviceOpen: pcm device (" << name << ") won't open for output.";\r
+ else\r
+ errorStream_ << "RtApiAlsa::probeDeviceOpen: pcm device (" << name << ") won't open for input.";\r
+ errorText_ = errorStream_.str();\r
+ return FAILURE;\r
+ }\r
+\r
+ // Fill the parameter structure.\r
+ snd_pcm_hw_params_t *hw_params;\r
+ snd_pcm_hw_params_alloca( &hw_params );\r
+ result = snd_pcm_hw_params_any( phandle, hw_params );\r
+ if ( result < 0 ) {\r
+ snd_pcm_close( phandle );\r
+ errorStream_ << "RtApiAlsa::probeDeviceOpen: error getting pcm device (" << name << ") parameters, " << snd_strerror( result ) << ".";\r
+ errorText_ = errorStream_.str();\r
+ return FAILURE;\r
+ }\r
+\r
+#if defined(__RTAUDIO_DEBUG__)\r
+ fprintf( stderr, "\nRtApiAlsa: dump hardware params just after device open:\n\n" );\r
+ snd_pcm_hw_params_dump( hw_params, out );\r
+#endif\r
+\r
+ // Set access ... check user preference.\r
+ if ( options && options->flags & RTAUDIO_NONINTERLEAVED ) {\r
+ stream_.userInterleaved = false;\r
+ result = snd_pcm_hw_params_set_access( phandle, hw_params, SND_PCM_ACCESS_RW_NONINTERLEAVED );\r
+ if ( result < 0 ) {\r
+ result = snd_pcm_hw_params_set_access( phandle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED );\r
+ stream_.deviceInterleaved[mode] = true;\r
+ }\r
+ else\r
+ stream_.deviceInterleaved[mode] = false;\r
+ }\r
+ else {\r
+ stream_.userInterleaved = true;\r
+ result = snd_pcm_hw_params_set_access( phandle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED );\r
+ if ( result < 0 ) {\r
+ result = snd_pcm_hw_params_set_access( phandle, hw_params, SND_PCM_ACCESS_RW_NONINTERLEAVED );\r
+ stream_.deviceInterleaved[mode] = false;\r
+ }\r
+ else\r
+ stream_.deviceInterleaved[mode] = true;\r
+ }\r
+\r
+ if ( result < 0 ) {\r
+ snd_pcm_close( phandle );\r
+ errorStream_ << "RtApiAlsa::probeDeviceOpen: error setting pcm device (" << name << ") access, " << snd_strerror( result ) << ".";\r
+ errorText_ = errorStream_.str();\r
+ return FAILURE;\r
+ }\r
+\r
+ // Determine how to set the device format.\r
+ stream_.userFormat = format;\r
+ snd_pcm_format_t deviceFormat = SND_PCM_FORMAT_UNKNOWN;\r
+\r
+ if ( format == RTAUDIO_SINT8 )\r
+ deviceFormat = SND_PCM_FORMAT_S8;\r
+ else if ( format == RTAUDIO_SINT16 )\r
+ deviceFormat = SND_PCM_FORMAT_S16;\r
+ else if ( format == RTAUDIO_SINT24 )\r
+ deviceFormat = SND_PCM_FORMAT_S24;\r
+ else if ( format == RTAUDIO_SINT32 )\r
+ deviceFormat = SND_PCM_FORMAT_S32;\r
+ else if ( format == RTAUDIO_FLOAT32 )\r
+ deviceFormat = SND_PCM_FORMAT_FLOAT;\r
+ else if ( format == RTAUDIO_FLOAT64 )\r
+ deviceFormat = SND_PCM_FORMAT_FLOAT64;\r
+\r
+ if ( snd_pcm_hw_params_test_format(phandle, hw_params, deviceFormat) == 0) {\r
+ stream_.deviceFormat[mode] = format;\r
+ goto setFormat;\r
+ }\r
+\r
+ // The user requested format is not natively supported by the device.\r
+ deviceFormat = SND_PCM_FORMAT_FLOAT64;\r
+ if ( snd_pcm_hw_params_test_format( phandle, hw_params, deviceFormat ) == 0 ) {\r
+ stream_.deviceFormat[mode] = RTAUDIO_FLOAT64;\r
+ goto setFormat;\r
+ }\r
+\r
+ deviceFormat = SND_PCM_FORMAT_FLOAT;\r
+ if ( snd_pcm_hw_params_test_format(phandle, hw_params, deviceFormat ) == 0 ) {\r
+ stream_.deviceFormat[mode] = RTAUDIO_FLOAT32;\r
+ goto setFormat;\r
+ }\r
+\r
+ deviceFormat = SND_PCM_FORMAT_S32;\r
+ if ( snd_pcm_hw_params_test_format(phandle, hw_params, deviceFormat ) == 0 ) {\r
+ stream_.deviceFormat[mode] = RTAUDIO_SINT32;\r
+ goto setFormat;\r
+ }\r
+\r
+ deviceFormat = SND_PCM_FORMAT_S24;\r
+ if ( snd_pcm_hw_params_test_format(phandle, hw_params, deviceFormat ) == 0 ) {\r
+ stream_.deviceFormat[mode] = RTAUDIO_SINT24;\r
+ goto setFormat;\r
+ }\r
+\r
+ deviceFormat = SND_PCM_FORMAT_S16;\r
+ if ( snd_pcm_hw_params_test_format(phandle, hw_params, deviceFormat ) == 0 ) {\r
+ stream_.deviceFormat[mode] = RTAUDIO_SINT16;\r
+ goto setFormat;\r
+ }\r
+\r
+ deviceFormat = SND_PCM_FORMAT_S8;\r
+ if ( snd_pcm_hw_params_test_format(phandle, hw_params, deviceFormat ) == 0 ) {\r
+ stream_.deviceFormat[mode] = RTAUDIO_SINT8;\r
+ goto setFormat;\r
+ }\r
+\r
+ // If we get here, no supported format was found.\r
+ snd_pcm_close( phandle );\r
+ errorStream_ << "RtApiAlsa::probeDeviceOpen: pcm device " << device << " data format not supported by RtAudio.";\r
+ errorText_ = errorStream_.str();\r
+ return FAILURE;\r
+\r
+ setFormat:\r
+ result = snd_pcm_hw_params_set_format( phandle, hw_params, deviceFormat );\r
+ if ( result < 0 ) {\r
+ snd_pcm_close( phandle );\r
+ errorStream_ << "RtApiAlsa::probeDeviceOpen: error setting pcm device (" << name << ") data format, " << snd_strerror( result ) << ".";\r
+ errorText_ = errorStream_.str();\r
+ return FAILURE;\r
+ }\r
+\r
+ // Determine whether byte-swaping is necessary.\r
+ stream_.doByteSwap[mode] = false;\r
+ if ( deviceFormat != SND_PCM_FORMAT_S8 ) {\r
+ result = snd_pcm_format_cpu_endian( deviceFormat );\r
+ if ( result == 0 )\r
+ stream_.doByteSwap[mode] = true;\r
+ else if (result < 0) {\r
+ snd_pcm_close( phandle );\r
+ errorStream_ << "RtApiAlsa::probeDeviceOpen: error getting pcm device (" << name << ") endian-ness, " << snd_strerror( result ) << ".";\r
+ errorText_ = errorStream_.str();\r
+ return FAILURE;\r
+ }\r
+ }\r
+\r
+ // Set the sample rate.\r
+ result = snd_pcm_hw_params_set_rate_near( phandle, hw_params, (unsigned int*) &sampleRate, 0 );\r
+ if ( result < 0 ) {\r
+ snd_pcm_close( phandle );\r
+ errorStream_ << "RtApiAlsa::probeDeviceOpen: error setting sample rate on device (" << name << "), " << snd_strerror( result ) << ".";\r
+ errorText_ = errorStream_.str();\r
+ return FAILURE;\r
+ }\r
+\r
+ // Determine the number of channels for this device. We support a possible\r
+ // minimum device channel number > than the value requested by the user.\r
+ stream_.nUserChannels[mode] = channels;\r
+ unsigned int value;\r
+ result = snd_pcm_hw_params_get_channels_max( hw_params, &value );\r
+ unsigned int deviceChannels = value;\r
+ if ( result < 0 || deviceChannels < channels + firstChannel ) {\r
+ snd_pcm_close( phandle );\r
+ errorStream_ << "RtApiAlsa::probeDeviceOpen: requested channel parameters not supported by device (" << name << "), " << snd_strerror( result ) << ".";\r
+ errorText_ = errorStream_.str();\r
+ return FAILURE;\r
+ }\r
+\r
+ result = snd_pcm_hw_params_get_channels_min( hw_params, &value );\r
+ if ( result < 0 ) {\r
+ snd_pcm_close( phandle );\r
+ errorStream_ << "RtApiAlsa::probeDeviceOpen: error getting minimum channels for device (" << name << "), " << snd_strerror( result ) << ".";\r
+ errorText_ = errorStream_.str();\r
+ return FAILURE;\r
+ }\r
+ deviceChannels = value;\r
+ if ( deviceChannels < channels + firstChannel ) deviceChannels = channels + firstChannel;\r
+ stream_.nDeviceChannels[mode] = deviceChannels;\r
+\r
+ // Set the device channels.\r
+ result = snd_pcm_hw_params_set_channels( phandle, hw_params, deviceChannels );\r
+ if ( result < 0 ) {\r
+ snd_pcm_close( phandle );\r
+ errorStream_ << "RtApiAlsa::probeDeviceOpen: error setting channels for device (" << name << "), " << snd_strerror( result ) << ".";\r
+ errorText_ = errorStream_.str();\r
+ return FAILURE;\r
+ }\r
+\r
+ // Set the buffer (or period) size.\r
+ int dir = 0;\r
+ snd_pcm_uframes_t periodSize = *bufferSize;\r
+ result = snd_pcm_hw_params_set_period_size_near( phandle, hw_params, &periodSize, &dir );\r
+ if ( result < 0 ) {\r
+ snd_pcm_close( phandle );\r
+ errorStream_ << "RtApiAlsa::probeDeviceOpen: error setting period size for device (" << name << "), " << snd_strerror( result ) << ".";\r
+ errorText_ = errorStream_.str();\r
+ return FAILURE;\r
+ }\r
+ *bufferSize = periodSize;\r
+\r
+ // Set the buffer number, which in ALSA is referred to as the "period".\r
+ unsigned int periods = 0;\r
+ if ( options && options->flags & RTAUDIO_MINIMIZE_LATENCY ) periods = 2;\r
+ if ( options && options->numberOfBuffers > 0 ) periods = options->numberOfBuffers;\r
+ if ( periods < 2 ) periods = 4; // a fairly safe default value\r
+ result = snd_pcm_hw_params_set_periods_near( phandle, hw_params, &periods, &dir );\r
+ if ( result < 0 ) {\r
+ snd_pcm_close( phandle );\r
+ errorStream_ << "RtApiAlsa::probeDeviceOpen: error setting periods for device (" << name << "), " << snd_strerror( result ) << ".";\r
+ errorText_ = errorStream_.str();\r
+ return FAILURE;\r
+ }\r
+\r
+ // If attempting to setup a duplex stream, the bufferSize parameter\r
+ // MUST be the same in both directions!\r
+ if ( stream_.mode == OUTPUT && mode == INPUT && *bufferSize != stream_.bufferSize ) {\r
+ snd_pcm_close( phandle );\r
+ errorStream_ << "RtApiAlsa::probeDeviceOpen: system error setting buffer size for duplex stream on device (" << name << ").";\r
+ errorText_ = errorStream_.str();\r
+ return FAILURE;\r
+ }\r
+\r
+ stream_.bufferSize = *bufferSize;\r
+\r
+ // Install the hardware configuration\r
+ result = snd_pcm_hw_params( phandle, hw_params );\r
+ if ( result < 0 ) {\r
+ snd_pcm_close( phandle );\r
+ errorStream_ << "RtApiAlsa::probeDeviceOpen: error installing hardware configuration on device (" << name << "), " << snd_strerror( result ) << ".";\r
+ errorText_ = errorStream_.str();\r
+ return FAILURE;\r
+ }\r
+\r
+#if defined(__RTAUDIO_DEBUG__)\r
+ fprintf(stderr, "\nRtApiAlsa: dump hardware params after installation:\n\n");\r
+ snd_pcm_hw_params_dump( hw_params, out );\r
+#endif\r
+\r
+ // Set the software configuration to fill buffers with zeros and prevent device stopping on xruns.\r
+ snd_pcm_sw_params_t *sw_params = NULL;\r
+ snd_pcm_sw_params_alloca( &sw_params );\r
+ snd_pcm_sw_params_current( phandle, sw_params );\r
+ snd_pcm_sw_params_set_start_threshold( phandle, sw_params, *bufferSize );\r
+ snd_pcm_sw_params_set_stop_threshold( phandle, sw_params, ULONG_MAX );\r
+ snd_pcm_sw_params_set_silence_threshold( phandle, sw_params, 0 );\r
+\r
+ // The following two settings were suggested by Theo Veenker\r
+ //snd_pcm_sw_params_set_avail_min( phandle, sw_params, *bufferSize );\r
+ //snd_pcm_sw_params_set_xfer_align( phandle, sw_params, 1 );\r
+\r
+ // here are two options for a fix\r
+ //snd_pcm_sw_params_set_silence_size( phandle, sw_params, ULONG_MAX );\r
+ snd_pcm_uframes_t val;\r
+ snd_pcm_sw_params_get_boundary( sw_params, &val );\r
+ snd_pcm_sw_params_set_silence_size( phandle, sw_params, val );\r
+\r
+ result = snd_pcm_sw_params( phandle, sw_params );\r
+ if ( result < 0 ) {\r
+ snd_pcm_close( phandle );\r
+ errorStream_ << "RtApiAlsa::probeDeviceOpen: error installing software configuration on device (" << name << "), " << snd_strerror( result ) << ".";\r
+ errorText_ = errorStream_.str();\r
+ return FAILURE;\r
+ }\r
+\r
+#if defined(__RTAUDIO_DEBUG__)\r
+ fprintf(stderr, "\nRtApiAlsa: dump software params after installation:\n\n");\r
+ snd_pcm_sw_params_dump( sw_params, out );\r
+#endif\r
+\r
+ // Set flags for buffer conversion\r
+ stream_.doConvertBuffer[mode] = false;\r
+ if ( stream_.userFormat != stream_.deviceFormat[mode] )\r
+ stream_.doConvertBuffer[mode] = true;\r
+ if ( stream_.nUserChannels[mode] < stream_.nDeviceChannels[mode] )\r
+ stream_.doConvertBuffer[mode] = true;\r
+ if ( stream_.userInterleaved != stream_.deviceInterleaved[mode] &&\r
+ stream_.nUserChannels[mode] > 1 )\r
+ stream_.doConvertBuffer[mode] = true;\r
+\r
+ // Allocate the ApiHandle if necessary and then save.\r
+ AlsaHandle *apiInfo = 0;\r
+ if ( stream_.apiHandle == 0 ) {\r
+ try {\r
+ apiInfo = (AlsaHandle *) new AlsaHandle;\r
+ }\r
+ catch ( std::bad_alloc& ) {\r
+ errorText_ = "RtApiAlsa::probeDeviceOpen: error allocating AlsaHandle memory.";\r
+ goto error;\r
+ }\r
+\r
+ if ( pthread_cond_init( &apiInfo->runnable_cv, NULL ) ) {\r
+ errorText_ = "RtApiAlsa::probeDeviceOpen: error initializing pthread condition variable.";\r
+ goto error;\r
+ }\r
+\r
+ stream_.apiHandle = (void *) apiInfo;\r
+ apiInfo->handles[0] = 0;\r
+ apiInfo->handles[1] = 0;\r
+ }\r
+ else {\r
+ apiInfo = (AlsaHandle *) stream_.apiHandle;\r
+ }\r
+ apiInfo->handles[mode] = phandle;\r
+ phandle = 0;\r
+\r
+ // Allocate necessary internal buffers.\r
+ unsigned long bufferBytes;\r
+ bufferBytes = stream_.nUserChannels[mode] * *bufferSize * formatBytes( stream_.userFormat );\r
+ stream_.userBuffer[mode] = (char *) calloc( bufferBytes, 1 );\r
+ if ( stream_.userBuffer[mode] == NULL ) {\r
+ errorText_ = "RtApiAlsa::probeDeviceOpen: error allocating user buffer memory.";\r
+ goto error;\r
+ }\r
+\r
+ if ( stream_.doConvertBuffer[mode] ) {\r
+\r
+ bool makeBuffer = true;\r
+ bufferBytes = stream_.nDeviceChannels[mode] * formatBytes( stream_.deviceFormat[mode] );\r
+ if ( mode == INPUT ) {\r
+ if ( stream_.mode == OUTPUT && stream_.deviceBuffer ) {\r
+ unsigned long bytesOut = stream_.nDeviceChannels[0] * formatBytes( stream_.deviceFormat[0] );\r
+ if ( bufferBytes <= bytesOut ) makeBuffer = false;\r
+ }\r
+ }\r
+\r
+ if ( makeBuffer ) {\r
+ bufferBytes *= *bufferSize;\r
+ if ( stream_.deviceBuffer ) free( stream_.deviceBuffer );\r
+ stream_.deviceBuffer = (char *) calloc( bufferBytes, 1 );\r
+ if ( stream_.deviceBuffer == NULL ) {\r
+ errorText_ = "RtApiAlsa::probeDeviceOpen: error allocating device buffer memory.";\r
+ goto error;\r
+ }\r
+ }\r
+ }\r
+\r
+ stream_.sampleRate = sampleRate;\r
+ stream_.nBuffers = periods;\r
+ stream_.device[mode] = device;\r
+ stream_.state = STREAM_STOPPED;\r
+\r
+ // Setup the buffer conversion information structure.\r
+ if ( stream_.doConvertBuffer[mode] ) setConvertInfo( mode, firstChannel );\r
+\r
+ // Setup thread if necessary.\r
+ if ( stream_.mode == OUTPUT && mode == INPUT ) {\r
+ // We had already set up an output stream.\r
+ stream_.mode = DUPLEX;\r
+ // Link the streams if possible.\r
+ apiInfo->synchronized = false;\r
+ if ( snd_pcm_link( apiInfo->handles[0], apiInfo->handles[1] ) == 0 )\r
+ apiInfo->synchronized = true;\r
+ else {\r
+ errorText_ = "RtApiAlsa::probeDeviceOpen: unable to synchronize input and output devices.";\r
+ error( RtAudioError::WARNING );\r
+ }\r
+ }\r
+ else {\r
+ stream_.mode = mode;\r
+\r
+ // Setup callback thread.\r
+ stream_.callbackInfo.object = (void *) this;\r
+\r
+ // Set the thread attributes for joinable and realtime scheduling\r
+ // priority (optional). The higher priority will only take affect\r
+ // if the program is run as root or suid. Note, under Linux\r
+ // processes with CAP_SYS_NICE privilege, a user can change\r
+ // scheduling policy and priority (thus need not be root). See\r
+ // POSIX "capabilities".\r
+ pthread_attr_t attr;\r
+ pthread_attr_init( &attr );\r
+ pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_JOINABLE );\r
+\r
+#ifdef SCHED_RR // Undefined with some OSes (eg: NetBSD 1.6.x with GNU Pthread)\r
+ if ( options && options->flags & RTAUDIO_SCHEDULE_REALTIME ) {\r
+ // We previously attempted to increase the audio callback priority\r
+ // to SCHED_RR here via the attributes. However, while no errors\r
+ // were reported in doing so, it did not work. So, now this is\r
+ // done in the alsaCallbackHandler function.\r
+ stream_.callbackInfo.doRealtime = true;\r
+ int priority = options->priority;\r
+ int min = sched_get_priority_min( SCHED_RR );\r
+ int max = sched_get_priority_max( SCHED_RR );\r
+ if ( priority < min ) priority = min;\r
+ else if ( priority > max ) priority = max;\r
+ stream_.callbackInfo.priority = priority;\r
+ }\r
+#endif\r
+\r
+ stream_.callbackInfo.isRunning = true;\r
+ result = pthread_create( &stream_.callbackInfo.thread, &attr, alsaCallbackHandler, &stream_.callbackInfo );\r
+ pthread_attr_destroy( &attr );\r
+ if ( result ) {\r
+ stream_.callbackInfo.isRunning = false;\r
+ errorText_ = "RtApiAlsa::error creating callback thread!";\r
+ goto error;\r
+ }\r
+ }\r
+\r
+ return SUCCESS;\r
+\r
+ error:\r
+ if ( apiInfo ) {\r
+ pthread_cond_destroy( &apiInfo->runnable_cv );\r
+ if ( apiInfo->handles[0] ) snd_pcm_close( apiInfo->handles[0] );\r
+ if ( apiInfo->handles[1] ) snd_pcm_close( apiInfo->handles[1] );\r
+ delete apiInfo;\r
+ stream_.apiHandle = 0;\r
+ }\r
+\r
+ if ( phandle) snd_pcm_close( phandle );\r
+\r
+ for ( int i=0; i<2; i++ ) {\r
+ if ( stream_.userBuffer[i] ) {\r
+ free( stream_.userBuffer[i] );\r
+ stream_.userBuffer[i] = 0;\r
+ }\r
+ }\r
+\r
+ if ( stream_.deviceBuffer ) {\r
+ free( stream_.deviceBuffer );\r
+ stream_.deviceBuffer = 0;\r
+ }\r
+\r
+ stream_.state = STREAM_CLOSED;\r
+ return FAILURE;\r
+}\r
+\r
+void RtApiAlsa :: closeStream()\r
+{\r
+ if ( stream_.state == STREAM_CLOSED ) {\r
+ errorText_ = "RtApiAlsa::closeStream(): no open stream to close!";\r
+ error( RtAudioError::WARNING );\r
+ return;\r
+ }\r
+\r
+ AlsaHandle *apiInfo = (AlsaHandle *) stream_.apiHandle;\r
+ stream_.callbackInfo.isRunning = false;\r
+ MUTEX_LOCK( &stream_.mutex );\r
+ if ( stream_.state == STREAM_STOPPED ) {\r
+ apiInfo->runnable = true;\r
+ pthread_cond_signal( &apiInfo->runnable_cv );\r
+ }\r
+ MUTEX_UNLOCK( &stream_.mutex );\r
+ pthread_join( stream_.callbackInfo.thread, NULL );\r
+\r
+ if ( stream_.state == STREAM_RUNNING ) {\r
+ stream_.state = STREAM_STOPPED;\r
+ if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX )\r
+ snd_pcm_drop( apiInfo->handles[0] );\r
+ if ( stream_.mode == INPUT || stream_.mode == DUPLEX )\r
+ snd_pcm_drop( apiInfo->handles[1] );\r
+ }\r
+\r
+ if ( apiInfo ) {\r
+ pthread_cond_destroy( &apiInfo->runnable_cv );\r
+ if ( apiInfo->handles[0] ) snd_pcm_close( apiInfo->handles[0] );\r
+ if ( apiInfo->handles[1] ) snd_pcm_close( apiInfo->handles[1] );\r
+ delete apiInfo;\r
+ stream_.apiHandle = 0;\r
+ }\r
+\r
+ for ( int i=0; i<2; i++ ) {\r
+ if ( stream_.userBuffer[i] ) {\r
+ free( stream_.userBuffer[i] );\r
+ stream_.userBuffer[i] = 0;\r
+ }\r
+ }\r
+\r
+ if ( stream_.deviceBuffer ) {\r
+ free( stream_.deviceBuffer );\r
+ stream_.deviceBuffer = 0;\r
+ }\r
+\r
+ stream_.mode = UNINITIALIZED;\r
+ stream_.state = STREAM_CLOSED;\r
+}\r
+\r
+void RtApiAlsa :: startStream()\r
+{\r
+ // This method calls snd_pcm_prepare if the device isn't already in that state.\r
+\r
+ verifyStream();\r
+ if ( stream_.state == STREAM_RUNNING ) {\r
+ errorText_ = "RtApiAlsa::startStream(): the stream is already running!";\r
+ error( RtAudioError::WARNING );\r
+ return;\r
+ }\r
+\r
+ MUTEX_LOCK( &stream_.mutex );\r
+\r
+ int result = 0;\r
+ snd_pcm_state_t state;\r
+ AlsaHandle *apiInfo = (AlsaHandle *) stream_.apiHandle;\r
+ snd_pcm_t **handle = (snd_pcm_t **) apiInfo->handles;\r
+ if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) {\r
+ state = snd_pcm_state( handle[0] );\r
+ if ( state != SND_PCM_STATE_PREPARED ) {\r
+ result = snd_pcm_prepare( handle[0] );\r
+ if ( result < 0 ) {\r
+ errorStream_ << "RtApiAlsa::startStream: error preparing output pcm device, " << snd_strerror( result ) << ".";\r
+ errorText_ = errorStream_.str();\r
+ goto unlock;\r
+ }\r
+ }\r
+ }\r
+\r
+ if ( ( stream_.mode == INPUT || stream_.mode == DUPLEX ) && !apiInfo->synchronized ) {\r
+ result = snd_pcm_drop(handle[1]); // fix to remove stale data received since device has been open\r
+ state = snd_pcm_state( handle[1] );\r
+ if ( state != SND_PCM_STATE_PREPARED ) {\r
+ result = snd_pcm_prepare( handle[1] );\r
+ if ( result < 0 ) {\r
+ errorStream_ << "RtApiAlsa::startStream: error preparing input pcm device, " << snd_strerror( result ) << ".";\r
+ errorText_ = errorStream_.str();\r
+ goto unlock;\r
+ }\r
+ }\r
+ }\r
+\r
+ stream_.state = STREAM_RUNNING;\r
+\r
+ unlock:\r
+ apiInfo->runnable = true;\r
+ pthread_cond_signal( &apiInfo->runnable_cv );\r
+ MUTEX_UNLOCK( &stream_.mutex );\r
+\r
+ if ( result >= 0 ) return;\r
+ error( RtAudioError::SYSTEM_ERROR );\r
+}\r
+\r
+void RtApiAlsa :: stopStream()\r
+{\r
+ verifyStream();\r
+ if ( stream_.state == STREAM_STOPPED ) {\r
+ errorText_ = "RtApiAlsa::stopStream(): the stream is already stopped!";\r
+ error( RtAudioError::WARNING );\r
+ return;\r
+ }\r
+\r
+ stream_.state = STREAM_STOPPED;\r
+ MUTEX_LOCK( &stream_.mutex );\r
+\r
+ int result = 0;\r
+ AlsaHandle *apiInfo = (AlsaHandle *) stream_.apiHandle;\r
+ snd_pcm_t **handle = (snd_pcm_t **) apiInfo->handles;\r
+ if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) {\r
+ if ( apiInfo->synchronized )\r
+ result = snd_pcm_drop( handle[0] );\r
+ else\r
+ result = snd_pcm_drain( handle[0] );\r
+ if ( result < 0 ) {\r
+ errorStream_ << "RtApiAlsa::stopStream: error draining output pcm device, " << snd_strerror( result ) << ".";\r
+ errorText_ = errorStream_.str();\r
+ goto unlock;\r
+ }\r
+ }\r
+\r
+ if ( ( stream_.mode == INPUT || stream_.mode == DUPLEX ) && !apiInfo->synchronized ) {\r
+ result = snd_pcm_drop( handle[1] );\r
+ if ( result < 0 ) {\r
+ errorStream_ << "RtApiAlsa::stopStream: error stopping input pcm device, " << snd_strerror( result ) << ".";\r
+ errorText_ = errorStream_.str();\r
+ goto unlock;\r
+ }\r
+ }\r
+\r
+ unlock:\r
+ apiInfo->runnable = false; // fixes high CPU usage when stopped\r
+ MUTEX_UNLOCK( &stream_.mutex );\r
+\r
+ if ( result >= 0 ) return;\r
+ error( RtAudioError::SYSTEM_ERROR );\r
+}\r
+\r
+void RtApiAlsa :: abortStream()\r
+{\r
+ verifyStream();\r
+ if ( stream_.state == STREAM_STOPPED ) {\r
+ errorText_ = "RtApiAlsa::abortStream(): the stream is already stopped!";\r
+ error( RtAudioError::WARNING );\r
+ return;\r
+ }\r
+\r
+ stream_.state = STREAM_STOPPED;\r
+ MUTEX_LOCK( &stream_.mutex );\r
+\r
+ int result = 0;\r
+ AlsaHandle *apiInfo = (AlsaHandle *) stream_.apiHandle;\r
+ snd_pcm_t **handle = (snd_pcm_t **) apiInfo->handles;\r
+ if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) {\r
+ result = snd_pcm_drop( handle[0] );\r
+ if ( result < 0 ) {\r
+ errorStream_ << "RtApiAlsa::abortStream: error aborting output pcm device, " << snd_strerror( result ) << ".";\r
+ errorText_ = errorStream_.str();\r
+ goto unlock;\r
+ }\r
+ }\r
+\r
+ if ( ( stream_.mode == INPUT || stream_.mode == DUPLEX ) && !apiInfo->synchronized ) {\r
+ result = snd_pcm_drop( handle[1] );\r
+ if ( result < 0 ) {\r
+ errorStream_ << "RtApiAlsa::abortStream: error aborting input pcm device, " << snd_strerror( result ) << ".";\r
+ errorText_ = errorStream_.str();\r
+ goto unlock;\r
+ }\r
+ }\r
+\r
+ unlock:\r
+ apiInfo->runnable = false; // fixes high CPU usage when stopped\r
+ MUTEX_UNLOCK( &stream_.mutex );\r
+\r
+ if ( result >= 0 ) return;\r
+ error( RtAudioError::SYSTEM_ERROR );\r
+}\r
+\r
+void RtApiAlsa :: callbackEvent()\r
+{\r
+ AlsaHandle *apiInfo = (AlsaHandle *) stream_.apiHandle;\r
+ if ( stream_.state == STREAM_STOPPED ) {\r
+ MUTEX_LOCK( &stream_.mutex );\r
+ while ( !apiInfo->runnable )\r
+ pthread_cond_wait( &apiInfo->runnable_cv, &stream_.mutex );\r
+\r
+ if ( stream_.state != STREAM_RUNNING ) {\r
+ MUTEX_UNLOCK( &stream_.mutex );\r
+ return;\r
+ }\r
+ MUTEX_UNLOCK( &stream_.mutex );\r
+ }\r
+\r
+ if ( stream_.state == STREAM_CLOSED ) {\r
+ errorText_ = "RtApiAlsa::callbackEvent(): the stream is closed ... this shouldn't happen!";\r
+ error( RtAudioError::WARNING );\r
+ return;\r
+ }\r
+\r
+ int doStopStream = 0;\r
+ RtAudioCallback callback = (RtAudioCallback) stream_.callbackInfo.callback;\r
+ double streamTime = getStreamTime();\r
+ RtAudioStreamStatus status = 0;\r
+ if ( stream_.mode != INPUT && apiInfo->xrun[0] == true ) {\r
+ status |= RTAUDIO_OUTPUT_UNDERFLOW;\r
+ apiInfo->xrun[0] = false;\r
+ }\r
+ if ( stream_.mode != OUTPUT && apiInfo->xrun[1] == true ) {\r
+ status |= RTAUDIO_INPUT_OVERFLOW;\r
+ apiInfo->xrun[1] = false;\r
+ }\r
+ doStopStream = callback( stream_.userBuffer[0], stream_.userBuffer[1],\r
+ stream_.bufferSize, streamTime, status, stream_.callbackInfo.userData );\r
+\r
+ if ( doStopStream == 2 ) {\r
+ abortStream();\r
+ return;\r
+ }\r
+\r
+ MUTEX_LOCK( &stream_.mutex );\r
+\r
+ // The state might change while waiting on a mutex.\r
+ if ( stream_.state == STREAM_STOPPED ) goto unlock;\r
+\r
+ int result;\r
+ char *buffer;\r
+ int channels;\r
+ snd_pcm_t **handle;\r
+ snd_pcm_sframes_t frames;\r
+ RtAudioFormat format;\r
+ handle = (snd_pcm_t **) apiInfo->handles;\r
+\r
+ if ( stream_.mode == INPUT || stream_.mode == DUPLEX ) {\r
+\r
+ // Setup parameters.\r
+ if ( stream_.doConvertBuffer[1] ) {\r
+ buffer = stream_.deviceBuffer;\r
+ channels = stream_.nDeviceChannels[1];\r
+ format = stream_.deviceFormat[1];\r
+ }\r
+ else {\r
+ buffer = stream_.userBuffer[1];\r
+ channels = stream_.nUserChannels[1];\r
+ format = stream_.userFormat;\r
+ }\r
+\r
+ // Read samples from device in interleaved/non-interleaved format.\r
+ if ( stream_.deviceInterleaved[1] )\r
+ result = snd_pcm_readi( handle[1], buffer, stream_.bufferSize );\r
+ else {\r
+ void *bufs[channels];\r
+ size_t offset = stream_.bufferSize * formatBytes( format );\r
+ for ( int i=0; i<channels; i++ )\r
+ bufs[i] = (void *) (buffer + (i * offset));\r
+ result = snd_pcm_readn( handle[1], bufs, stream_.bufferSize );\r
+ }\r
+\r
+ if ( result < (int) stream_.bufferSize ) {\r
+ // Either an error or overrun occured.\r
+ if ( result == -EPIPE ) {\r
+ snd_pcm_state_t state = snd_pcm_state( handle[1] );\r
+ if ( state == SND_PCM_STATE_XRUN ) {\r
+ apiInfo->xrun[1] = true;\r
+ result = snd_pcm_prepare( handle[1] );\r
+ if ( result < 0 ) {\r
+ errorStream_ << "RtApiAlsa::callbackEvent: error preparing device after overrun, " << snd_strerror( result ) << ".";\r
+ errorText_ = errorStream_.str();\r
+ }\r
+ }\r
+ else {\r
+ errorStream_ << "RtApiAlsa::callbackEvent: error, current state is " << snd_pcm_state_name( state ) << ", " << snd_strerror( result ) << ".";\r
+ errorText_ = errorStream_.str();\r
+ }\r
+ }\r
+ else {\r
+ errorStream_ << "RtApiAlsa::callbackEvent: audio read error, " << snd_strerror( result ) << ".";\r
+ errorText_ = errorStream_.str();\r
+ }\r
+ error( RtAudioError::WARNING );\r
+ goto tryOutput;\r
+ }\r
+\r
+ // Do byte swapping if necessary.\r
+ if ( stream_.doByteSwap[1] )\r
+ byteSwapBuffer( buffer, stream_.bufferSize * channels, format );\r
+\r
+ // Do buffer conversion if necessary.\r
+ if ( stream_.doConvertBuffer[1] )\r
+ convertBuffer( stream_.userBuffer[1], stream_.deviceBuffer, stream_.convertInfo[1] );\r
+\r
+ // Check stream latency\r
+ result = snd_pcm_delay( handle[1], &frames );\r
+ if ( result == 0 && frames > 0 ) stream_.latency[1] = frames;\r
+ }\r
+\r
+ tryOutput:\r
+\r
+ if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) {\r
+\r
+ // Setup parameters and do buffer conversion if necessary.\r
+ if ( stream_.doConvertBuffer[0] ) {\r
+ buffer = stream_.deviceBuffer;\r
+ convertBuffer( buffer, stream_.userBuffer[0], stream_.convertInfo[0] );\r
+ channels = stream_.nDeviceChannels[0];\r
+ format = stream_.deviceFormat[0];\r
+ }\r
+ else {\r
+ buffer = stream_.userBuffer[0];\r
+ channels = stream_.nUserChannels[0];\r
+ format = stream_.userFormat;\r
+ }\r
+\r
+ // Do byte swapping if necessary.\r
+ if ( stream_.doByteSwap[0] )\r
+ byteSwapBuffer(buffer, stream_.bufferSize * channels, format);\r
+\r
+ // Write samples to device in interleaved/non-interleaved format.\r
+ if ( stream_.deviceInterleaved[0] )\r
+ result = snd_pcm_writei( handle[0], buffer, stream_.bufferSize );\r
+ else {\r
+ void *bufs[channels];\r
+ size_t offset = stream_.bufferSize * formatBytes( format );\r
+ for ( int i=0; i<channels; i++ )\r
+ bufs[i] = (void *) (buffer + (i * offset));\r
+ result = snd_pcm_writen( handle[0], bufs, stream_.bufferSize );\r
+ }\r
+\r
+ if ( result < (int) stream_.bufferSize ) {\r
+ // Either an error or underrun occured.\r
+ if ( result == -EPIPE ) {\r
+ snd_pcm_state_t state = snd_pcm_state( handle[0] );\r
+ if ( state == SND_PCM_STATE_XRUN ) {\r
+ apiInfo->xrun[0] = true;\r
+ result = snd_pcm_prepare( handle[0] );\r
+ if ( result < 0 ) {\r
+ errorStream_ << "RtApiAlsa::callbackEvent: error preparing device after underrun, " << snd_strerror( result ) << ".";\r
+ errorText_ = errorStream_.str();\r
+ }\r
+ else\r
+ errorText_ = "RtApiAlsa::callbackEvent: audio write error, underrun.";\r
+ }\r
+ else {\r
+ errorStream_ << "RtApiAlsa::callbackEvent: error, current state is " << snd_pcm_state_name( state ) << ", " << snd_strerror( result ) << ".";\r
+ errorText_ = errorStream_.str();\r
+ }\r
+ }\r
+ else {\r
+ errorStream_ << "RtApiAlsa::callbackEvent: audio write error, " << snd_strerror( result ) << ".";\r
+ errorText_ = errorStream_.str();\r
+ }\r
+ error( RtAudioError::WARNING );\r
+ goto unlock;\r
+ }\r
+\r
+ // Check stream latency\r
+ result = snd_pcm_delay( handle[0], &frames );\r
+ if ( result == 0 && frames > 0 ) stream_.latency[0] = frames;\r
+ }\r
+\r
+ unlock:\r
+ MUTEX_UNLOCK( &stream_.mutex );\r
+\r
+ RtApi::tickStreamTime();\r
+ if ( doStopStream == 1 ) this->stopStream();\r
+}\r
+\r
+static void *alsaCallbackHandler( void *ptr )\r
+{\r
+ CallbackInfo *info = (CallbackInfo *) ptr;\r
+ RtApiAlsa *object = (RtApiAlsa *) info->object;\r
+ bool *isRunning = &info->isRunning;\r
+\r
+#ifdef SCHED_RR // Undefined with some OSes (eg: NetBSD 1.6.x with GNU Pthread)\r
+ if ( info->doRealtime ) {\r
+ pthread_t tID = pthread_self(); // ID of this thread\r
+ sched_param prio = { info->priority }; // scheduling priority of thread\r
+ pthread_setschedparam( tID, SCHED_RR, &prio );\r
+ }\r
+#endif\r
+\r
+ while ( *isRunning == true ) {\r
+ pthread_testcancel();\r
+ object->callbackEvent();\r
+ }\r
+\r
+ pthread_exit( NULL );\r
+}\r
+\r
+//******************** End of __LINUX_ALSA__ *********************//\r
+#endif\r
+\r
+#if defined(__LINUX_PULSE__)\r
+\r
+// Code written by Peter Meerwald, pmeerw@pmeerw.net\r
+// and Tristan Matthews.\r
+\r
+#include <pulse/error.h>\r
+#include <pulse/simple.h>\r
+#include <cstdio>\r
+\r
+static const unsigned int SUPPORTED_SAMPLERATES[] = { 8000, 16000, 22050, 32000,\r
+ 44100, 48000, 96000, 0};\r
+\r
+struct rtaudio_pa_format_mapping_t {\r
+ RtAudioFormat rtaudio_format;\r
+ pa_sample_format_t pa_format;\r
+};\r
+\r
+static const rtaudio_pa_format_mapping_t supported_sampleformats[] = {\r
+ {RTAUDIO_SINT16, PA_SAMPLE_S16LE},\r
+ {RTAUDIO_SINT32, PA_SAMPLE_S32LE},\r
+ {RTAUDIO_FLOAT32, PA_SAMPLE_FLOAT32LE},\r
+ {0, PA_SAMPLE_INVALID}};\r
+\r
+struct PulseAudioHandle {\r
+ pa_simple *s_play;\r
+ pa_simple *s_rec;\r
+ pthread_t thread;\r
+ pthread_cond_t runnable_cv;\r
+ bool runnable;\r
+ PulseAudioHandle() : s_play(0), s_rec(0), runnable(false) { }\r
+};\r
+\r
+RtApiPulse::~RtApiPulse()\r
+{\r
+ if ( stream_.state != STREAM_CLOSED )\r
+ closeStream();\r
+}\r
+\r
+unsigned int RtApiPulse::getDeviceCount( void )\r
+{\r
+ return 1;\r
+}\r
+\r
+RtAudio::DeviceInfo RtApiPulse::getDeviceInfo( unsigned int /*device*/ )\r
+{\r
+ RtAudio::DeviceInfo info;\r
+ info.probed = true;\r
+ info.name = "PulseAudio";\r
+ info.outputChannels = 2;\r
+ info.inputChannels = 2;\r
+ info.duplexChannels = 2;\r
+ info.isDefaultOutput = true;\r
+ info.isDefaultInput = true;\r
+\r
+ for ( const unsigned int *sr = SUPPORTED_SAMPLERATES; *sr; ++sr )\r
+ info.sampleRates.push_back( *sr );\r
+\r
+ info.preferredSampleRate = 48000;\r
+ info.nativeFormats = RTAUDIO_SINT16 | RTAUDIO_SINT32 | RTAUDIO_FLOAT32;\r
+\r
+ return info;\r
+}\r
+\r
+static void *pulseaudio_callback( void * user )\r
+{\r
+ CallbackInfo *cbi = static_cast<CallbackInfo *>( user );\r
+ RtApiPulse *context = static_cast<RtApiPulse *>( cbi->object );\r
+ volatile bool *isRunning = &cbi->isRunning;\r
+\r
+ while ( *isRunning ) {\r
+ pthread_testcancel();\r
+ context->callbackEvent();\r
+ }\r
+\r
+ pthread_exit( NULL );\r
+}\r
+\r
+void RtApiPulse::closeStream( void )\r
+{\r
+ PulseAudioHandle *pah = static_cast<PulseAudioHandle *>( stream_.apiHandle );\r
+\r
+ stream_.callbackInfo.isRunning = false;\r
+ if ( pah ) {\r
+ MUTEX_LOCK( &stream_.mutex );\r
+ if ( stream_.state == STREAM_STOPPED ) {\r
+ pah->runnable = true;\r
+ pthread_cond_signal( &pah->runnable_cv );\r
+ }\r
+ MUTEX_UNLOCK( &stream_.mutex );\r
+\r
+ pthread_join( pah->thread, 0 );\r
+ if ( pah->s_play ) {\r
+ pa_simple_flush( pah->s_play, NULL );\r
+ pa_simple_free( pah->s_play );\r
+ }\r
+ if ( pah->s_rec )\r
+ pa_simple_free( pah->s_rec );\r
+\r
+ pthread_cond_destroy( &pah->runnable_cv );\r
+ delete pah;\r
+ stream_.apiHandle = 0;\r
+ }\r
+\r
+ if ( stream_.userBuffer[0] ) {\r
+ free( stream_.userBuffer[0] );\r
+ stream_.userBuffer[0] = 0;\r
+ }\r
+ if ( stream_.userBuffer[1] ) {\r
+ free( stream_.userBuffer[1] );\r
+ stream_.userBuffer[1] = 0;\r
+ }\r
+\r
+ stream_.state = STREAM_CLOSED;\r
+ stream_.mode = UNINITIALIZED;\r
+}\r
+\r
+void RtApiPulse::callbackEvent( void )\r
+{\r
+ PulseAudioHandle *pah = static_cast<PulseAudioHandle *>( stream_.apiHandle );\r
+\r
+ if ( stream_.state == STREAM_STOPPED ) {\r
+ MUTEX_LOCK( &stream_.mutex );\r
+ while ( !pah->runnable )\r
+ pthread_cond_wait( &pah->runnable_cv, &stream_.mutex );\r
+\r
+ if ( stream_.state != STREAM_RUNNING ) {\r
+ MUTEX_UNLOCK( &stream_.mutex );\r
+ return;\r
+ }\r
+ MUTEX_UNLOCK( &stream_.mutex );\r
+ }\r
+\r
+ if ( stream_.state == STREAM_CLOSED ) {\r
+ errorText_ = "RtApiPulse::callbackEvent(): the stream is closed ... "\r
+ "this shouldn't happen!";\r
+ error( RtAudioError::WARNING );\r
+ return;\r
+ }\r
+\r
+ RtAudioCallback callback = (RtAudioCallback) stream_.callbackInfo.callback;\r
+ double streamTime = getStreamTime();\r
+ RtAudioStreamStatus status = 0;\r
+ int doStopStream = callback( stream_.userBuffer[OUTPUT], stream_.userBuffer[INPUT],\r
+ stream_.bufferSize, streamTime, status,\r
+ stream_.callbackInfo.userData );\r
+\r
+ if ( doStopStream == 2 ) {\r
+ abortStream();\r
+ return;\r
+ }\r
+\r
+ MUTEX_LOCK( &stream_.mutex );\r
+ void *pulse_in = stream_.doConvertBuffer[INPUT] ? stream_.deviceBuffer : stream_.userBuffer[INPUT];\r
+ void *pulse_out = stream_.doConvertBuffer[OUTPUT] ? stream_.deviceBuffer : stream_.userBuffer[OUTPUT];\r
+\r
+ if ( stream_.state != STREAM_RUNNING )\r
+ goto unlock;\r
+\r
+ int pa_error;\r
+ size_t bytes;\r
+ if (stream_.mode == OUTPUT || stream_.mode == DUPLEX ) {\r
+ if ( stream_.doConvertBuffer[OUTPUT] ) {\r
+ convertBuffer( stream_.deviceBuffer,\r
+ stream_.userBuffer[OUTPUT],\r
+ stream_.convertInfo[OUTPUT] );\r
+ bytes = stream_.nDeviceChannels[OUTPUT] * stream_.bufferSize *\r
+ formatBytes( stream_.deviceFormat[OUTPUT] );\r
+ } else\r
+ bytes = stream_.nUserChannels[OUTPUT] * stream_.bufferSize *\r
+ formatBytes( stream_.userFormat );\r
+\r
+ if ( pa_simple_write( pah->s_play, pulse_out, bytes, &pa_error ) < 0 ) {\r
+ errorStream_ << "RtApiPulse::callbackEvent: audio write error, " <<\r
+ pa_strerror( pa_error ) << ".";\r
+ errorText_ = errorStream_.str();\r
+ error( RtAudioError::WARNING );\r
+ }\r
+ }\r
+\r
+ if ( stream_.mode == INPUT || stream_.mode == DUPLEX) {\r
+ if ( stream_.doConvertBuffer[INPUT] )\r
+ bytes = stream_.nDeviceChannels[INPUT] * stream_.bufferSize *\r
+ formatBytes( stream_.deviceFormat[INPUT] );\r
+ else\r
+ bytes = stream_.nUserChannels[INPUT] * stream_.bufferSize *\r
+ formatBytes( stream_.userFormat );\r
+\r
+ if ( pa_simple_read( pah->s_rec, pulse_in, bytes, &pa_error ) < 0 ) {\r
+ errorStream_ << "RtApiPulse::callbackEvent: audio read error, " <<\r
+ pa_strerror( pa_error ) << ".";\r
+ errorText_ = errorStream_.str();\r
+ error( RtAudioError::WARNING );\r
+ }\r
+ if ( stream_.doConvertBuffer[INPUT] ) {\r
+ convertBuffer( stream_.userBuffer[INPUT],\r
+ stream_.deviceBuffer,\r
+ stream_.convertInfo[INPUT] );\r
+ }\r
+ }\r
+\r
+ unlock:\r
+ MUTEX_UNLOCK( &stream_.mutex );\r
+ RtApi::tickStreamTime();\r
+\r
+ if ( doStopStream == 1 )\r
+ stopStream();\r
+}\r
+\r
+void RtApiPulse::startStream( void )\r
+{\r
+ PulseAudioHandle *pah = static_cast<PulseAudioHandle *>( stream_.apiHandle );\r
+\r
+ if ( stream_.state == STREAM_CLOSED ) {\r
+ errorText_ = "RtApiPulse::startStream(): the stream is not open!";\r
+ error( RtAudioError::INVALID_USE );\r
+ return;\r
+ }\r
+ if ( stream_.state == STREAM_RUNNING ) {\r
+ errorText_ = "RtApiPulse::startStream(): the stream is already running!";\r
+ error( RtAudioError::WARNING );\r
+ return;\r
+ }\r
+\r
+ MUTEX_LOCK( &stream_.mutex );\r
+\r
+ stream_.state = STREAM_RUNNING;\r
+\r
+ pah->runnable = true;\r
+ pthread_cond_signal( &pah->runnable_cv );\r
+ MUTEX_UNLOCK( &stream_.mutex );\r
+}\r
+\r
+void RtApiPulse::stopStream( void )\r
+{\r
+ PulseAudioHandle *pah = static_cast<PulseAudioHandle *>( stream_.apiHandle );\r
+\r
+ if ( stream_.state == STREAM_CLOSED ) {\r
+ errorText_ = "RtApiPulse::stopStream(): the stream is not open!";\r
+ error( RtAudioError::INVALID_USE );\r
+ return;\r
+ }\r
+ if ( stream_.state == STREAM_STOPPED ) {\r
+ errorText_ = "RtApiPulse::stopStream(): the stream is already stopped!";\r
+ error( RtAudioError::WARNING );\r
+ return;\r
+ }\r
+\r
+ stream_.state = STREAM_STOPPED;\r
+ MUTEX_LOCK( &stream_.mutex );\r
+\r
+ if ( pah && pah->s_play ) {\r
+ int pa_error;\r
+ if ( pa_simple_drain( pah->s_play, &pa_error ) < 0 ) {\r
+ errorStream_ << "RtApiPulse::stopStream: error draining output device, " <<\r
+ pa_strerror( pa_error ) << ".";\r
+ errorText_ = errorStream_.str();\r
+ MUTEX_UNLOCK( &stream_.mutex );\r
+ error( RtAudioError::SYSTEM_ERROR );\r
+ return;\r
+ }\r
+ }\r
+\r
+ stream_.state = STREAM_STOPPED;\r
+ MUTEX_UNLOCK( &stream_.mutex );\r
+}\r
+\r
+void RtApiPulse::abortStream( void )\r
+{\r
+ PulseAudioHandle *pah = static_cast<PulseAudioHandle*>( stream_.apiHandle );\r
+\r
+ if ( stream_.state == STREAM_CLOSED ) {\r
+ errorText_ = "RtApiPulse::abortStream(): the stream is not open!";\r
+ error( RtAudioError::INVALID_USE );\r
+ return;\r
+ }\r
+ if ( stream_.state == STREAM_STOPPED ) {\r
+ errorText_ = "RtApiPulse::abortStream(): the stream is already stopped!";\r
+ error( RtAudioError::WARNING );\r
+ return;\r
+ }\r
+\r
+ stream_.state = STREAM_STOPPED;\r
+ MUTEX_LOCK( &stream_.mutex );\r
+\r
+ if ( pah && pah->s_play ) {\r
+ int pa_error;\r
+ if ( pa_simple_flush( pah->s_play, &pa_error ) < 0 ) {\r
+ errorStream_ << "RtApiPulse::abortStream: error flushing output device, " <<\r
+ pa_strerror( pa_error ) << ".";\r
+ errorText_ = errorStream_.str();\r
+ MUTEX_UNLOCK( &stream_.mutex );\r
+ error( RtAudioError::SYSTEM_ERROR );\r
+ return;\r
+ }\r
+ }\r
+\r
+ stream_.state = STREAM_STOPPED;\r
+ MUTEX_UNLOCK( &stream_.mutex );\r
+}\r
+\r
+bool RtApiPulse::probeDeviceOpen( unsigned int device, StreamMode mode,\r
+ unsigned int channels, unsigned int firstChannel,\r
+ unsigned int sampleRate, RtAudioFormat format,\r
+ unsigned int *bufferSize, RtAudio::StreamOptions *options )\r
+{\r
+ PulseAudioHandle *pah = 0;\r
+ unsigned long bufferBytes = 0;\r
+ pa_sample_spec ss;\r
+\r
+ if ( device != 0 ) return false;\r
+ if ( mode != INPUT && mode != OUTPUT ) return false;\r
+ if ( channels != 1 && channels != 2 ) {\r
+ errorText_ = "RtApiPulse::probeDeviceOpen: unsupported number of channels.";\r
+ return false;\r
+ }\r
+ ss.channels = channels;\r
+\r
+ if ( firstChannel != 0 ) return false;\r
+\r
+ bool sr_found = false;\r
+ for ( const unsigned int *sr = SUPPORTED_SAMPLERATES; *sr; ++sr ) {\r
+ if ( sampleRate == *sr ) {\r
+ sr_found = true;\r
+ stream_.sampleRate = sampleRate;\r
+ ss.rate = sampleRate;\r
+ break;\r
+ }\r
+ }\r
+ if ( !sr_found ) {\r
+ errorText_ = "RtApiPulse::probeDeviceOpen: unsupported sample rate.";\r
+ return false;\r
+ }\r
+\r
+ bool sf_found = 0;\r
+ for ( const rtaudio_pa_format_mapping_t *sf = supported_sampleformats;\r
+ sf->rtaudio_format && sf->pa_format != PA_SAMPLE_INVALID; ++sf ) {\r
+ if ( format == sf->rtaudio_format ) {\r
+ sf_found = true;\r
+ stream_.userFormat = sf->rtaudio_format;\r
+ stream_.deviceFormat[mode] = stream_.userFormat;\r
+ ss.format = sf->pa_format;\r
+ break;\r
+ }\r
+ }\r
+ if ( !sf_found ) { // Use internal data format conversion.\r
+ stream_.userFormat = format;\r
+ stream_.deviceFormat[mode] = RTAUDIO_FLOAT32;\r
+ ss.format = PA_SAMPLE_FLOAT32LE;\r
+ }\r
+\r
+ // Set other stream parameters.\r
+ if ( options && options->flags & RTAUDIO_NONINTERLEAVED ) stream_.userInterleaved = false;\r
+ else stream_.userInterleaved = true;\r
+ stream_.deviceInterleaved[mode] = true;\r
+ stream_.nBuffers = 1;\r
+ stream_.doByteSwap[mode] = false;\r
+ stream_.nUserChannels[mode] = channels;\r
+ stream_.nDeviceChannels[mode] = channels + firstChannel;\r
+ stream_.channelOffset[mode] = 0;\r
+ std::string streamName = "RtAudio";\r
+\r
+ // Set flags for buffer conversion.\r
+ stream_.doConvertBuffer[mode] = false;\r
+ if ( stream_.userFormat != stream_.deviceFormat[mode] )\r
+ stream_.doConvertBuffer[mode] = true;\r
+ if ( stream_.nUserChannels[mode] < stream_.nDeviceChannels[mode] )\r
+ stream_.doConvertBuffer[mode] = true;\r
+\r
+ // Allocate necessary internal buffers.\r
+ bufferBytes = stream_.nUserChannels[mode] * *bufferSize * formatBytes( stream_.userFormat );\r
+ stream_.userBuffer[mode] = (char *) calloc( bufferBytes, 1 );\r
+ if ( stream_.userBuffer[mode] == NULL ) {\r
+ errorText_ = "RtApiPulse::probeDeviceOpen: error allocating user buffer memory.";\r
+ goto error;\r
+ }\r
+ stream_.bufferSize = *bufferSize;\r
+\r
+ if ( stream_.doConvertBuffer[mode] ) {\r
+\r
+ bool makeBuffer = true;\r
+ bufferBytes = stream_.nDeviceChannels[mode] * formatBytes( stream_.deviceFormat[mode] );\r
+ if ( mode == INPUT ) {\r
+ if ( stream_.mode == OUTPUT && stream_.deviceBuffer ) {\r
+ unsigned long bytesOut = stream_.nDeviceChannels[0] * formatBytes( stream_.deviceFormat[0] );\r
+ if ( bufferBytes <= bytesOut ) makeBuffer = false;\r
+ }\r
+ }\r
+\r
+ if ( makeBuffer ) {\r
+ bufferBytes *= *bufferSize;\r
+ if ( stream_.deviceBuffer ) free( stream_.deviceBuffer );\r
+ stream_.deviceBuffer = (char *) calloc( bufferBytes, 1 );\r
+ if ( stream_.deviceBuffer == NULL ) {\r
+ errorText_ = "RtApiPulse::probeDeviceOpen: error allocating device buffer memory.";\r
+ goto error;\r
+ }\r
+ }\r
+ }\r
+\r
+ stream_.device[mode] = device;\r
+\r
+ // Setup the buffer conversion information structure.\r
+ if ( stream_.doConvertBuffer[mode] ) setConvertInfo( mode, firstChannel );\r
+\r
+ if ( !stream_.apiHandle ) {\r
+ PulseAudioHandle *pah = new PulseAudioHandle;\r
+ if ( !pah ) {\r
+ errorText_ = "RtApiPulse::probeDeviceOpen: error allocating memory for handle.";\r
+ goto error;\r
+ }\r
+\r
+ stream_.apiHandle = pah;\r
+ if ( pthread_cond_init( &pah->runnable_cv, NULL ) != 0 ) {\r
+ errorText_ = "RtApiPulse::probeDeviceOpen: error creating condition variable.";\r
+ goto error;\r
+ }\r
+ }\r
+ pah = static_cast<PulseAudioHandle *>( stream_.apiHandle );\r
+\r
+ int error;\r
+ if ( options && !options->streamName.empty() ) streamName = options->streamName;\r
+ switch ( mode ) {\r
+ case INPUT:\r
+ pa_buffer_attr buffer_attr;\r
+ buffer_attr.fragsize = bufferBytes;\r
+ buffer_attr.maxlength = -1;\r
+\r
+ pah->s_rec = pa_simple_new( NULL, streamName.c_str(), PA_STREAM_RECORD, NULL, "Record", &ss, NULL, &buffer_attr, &error );\r
+ if ( !pah->s_rec ) {\r
+ errorText_ = "RtApiPulse::probeDeviceOpen: error connecting input to PulseAudio server.";\r
+ goto error;\r
+ }\r
+ break;\r
+ case OUTPUT:\r
+ pah->s_play = pa_simple_new( NULL, streamName.c_str(), PA_STREAM_PLAYBACK, NULL, "Playback", &ss, NULL, NULL, &error );\r
+ if ( !pah->s_play ) {\r
+ errorText_ = "RtApiPulse::probeDeviceOpen: error connecting output to PulseAudio server.";\r
+ goto error;\r
+ }\r
+ break;\r
+ default:\r
+ goto error;\r
+ }\r
+\r
+ if ( stream_.mode == UNINITIALIZED )\r
+ stream_.mode = mode;\r
+ else if ( stream_.mode == mode )\r
+ goto error;\r
+ else\r
+ stream_.mode = DUPLEX;\r
+\r
+ if ( !stream_.callbackInfo.isRunning ) {\r
+ stream_.callbackInfo.object = this;\r
+ stream_.callbackInfo.isRunning = true;\r
+ if ( pthread_create( &pah->thread, NULL, pulseaudio_callback, (void *)&stream_.callbackInfo) != 0 ) {\r
+ errorText_ = "RtApiPulse::probeDeviceOpen: error creating thread.";\r
+ goto error;\r
+ }\r
+ }\r
+\r
+ stream_.state = STREAM_STOPPED;\r
+ return true;\r
+\r
+ error:\r
+ if ( pah && stream_.callbackInfo.isRunning ) {\r
+ pthread_cond_destroy( &pah->runnable_cv );\r
+ delete pah;\r
+ stream_.apiHandle = 0;\r
+ }\r
+\r
+ for ( int i=0; i<2; i++ ) {\r
+ if ( stream_.userBuffer[i] ) {\r
+ free( stream_.userBuffer[i] );\r
+ stream_.userBuffer[i] = 0;\r
+ }\r
+ }\r
+\r
+ if ( stream_.deviceBuffer ) {\r
+ free( stream_.deviceBuffer );\r
+ stream_.deviceBuffer = 0;\r
+ }\r
+\r
+ return FAILURE;\r
+}\r
+\r
+//******************** End of __LINUX_PULSE__ *********************//\r
+#endif\r
+\r
+#if defined(__LINUX_OSS__)\r
+\r
+#include <unistd.h>\r
+#include <sys/ioctl.h>\r
+#include <unistd.h>\r
+#include <fcntl.h>\r
+#include <sys/soundcard.h>\r
+#include <errno.h>\r
+#include <math.h>\r
+\r
+static void *ossCallbackHandler(void * ptr);\r
+\r
+// A structure to hold various information related to the OSS API\r
+// implementation.\r
+struct OssHandle {\r
+ int id[2]; // device ids\r
+ bool xrun[2];\r
+ bool triggered;\r
+ pthread_cond_t runnable;\r
+\r
+ OssHandle()\r
+ :triggered(false) { id[0] = 0; id[1] = 0; xrun[0] = false; xrun[1] = false; }\r
+};\r
+\r
+RtApiOss :: RtApiOss()\r
+{\r
+ // Nothing to do here.\r
+}\r
+\r
+RtApiOss :: ~RtApiOss()\r
+{\r
+ if ( stream_.state != STREAM_CLOSED ) closeStream();\r
+}\r
+\r
+unsigned int RtApiOss :: getDeviceCount( void )\r
+{\r
+ int mixerfd = open( "/dev/mixer", O_RDWR, 0 );\r
+ if ( mixerfd == -1 ) {\r
+ errorText_ = "RtApiOss::getDeviceCount: error opening '/dev/mixer'.";\r
+ error( RtAudioError::WARNING );\r
+ return 0;\r
+ }\r
+\r
+ oss_sysinfo sysinfo;\r
+ if ( ioctl( mixerfd, SNDCTL_SYSINFO, &sysinfo ) == -1 ) {\r
+ close( mixerfd );\r
+ errorText_ = "RtApiOss::getDeviceCount: error getting sysinfo, OSS version >= 4.0 is required.";\r
+ error( RtAudioError::WARNING );\r
+ return 0;\r
+ }\r
+\r
+ close( mixerfd );\r
+ return sysinfo.numaudios;\r
+}\r
+\r
+RtAudio::DeviceInfo RtApiOss :: getDeviceInfo( unsigned int device )\r
+{\r
+ RtAudio::DeviceInfo info;\r
+ info.probed = false;\r
+\r
+ int mixerfd = open( "/dev/mixer", O_RDWR, 0 );\r
+ if ( mixerfd == -1 ) {\r
+ errorText_ = "RtApiOss::getDeviceInfo: error opening '/dev/mixer'.";\r
+ error( RtAudioError::WARNING );\r
+ return info;\r
+ }\r
+\r
+ oss_sysinfo sysinfo;\r
+ int result = ioctl( mixerfd, SNDCTL_SYSINFO, &sysinfo );\r
+ if ( result == -1 ) {\r
+ close( mixerfd );\r
+ errorText_ = "RtApiOss::getDeviceInfo: error getting sysinfo, OSS version >= 4.0 is required.";\r
+ error( RtAudioError::WARNING );\r
+ return info;\r
+ }\r
+\r
+ unsigned nDevices = sysinfo.numaudios;\r
+ if ( nDevices == 0 ) {\r
+ close( mixerfd );\r
+ errorText_ = "RtApiOss::getDeviceInfo: no devices found!";\r
+ error( RtAudioError::INVALID_USE );\r
+ return info;\r
+ }\r
+\r
+ if ( device >= nDevices ) {\r
+ close( mixerfd );\r
+ errorText_ = "RtApiOss::getDeviceInfo: device ID is invalid!";\r
+ error( RtAudioError::INVALID_USE );\r
+ return info;\r
+ }\r
+\r
+ oss_audioinfo ainfo;\r
+ ainfo.dev = device;\r
+ result = ioctl( mixerfd, SNDCTL_AUDIOINFO, &ainfo );\r
+ close( mixerfd );\r
+ if ( result == -1 ) {\r
+ errorStream_ << "RtApiOss::getDeviceInfo: error getting device (" << ainfo.name << ") info.";\r
+ errorText_ = errorStream_.str();\r
+ error( RtAudioError::WARNING );\r
+ return info;\r
+ }\r
+\r
+ // Probe channels\r
+ if ( ainfo.caps & PCM_CAP_OUTPUT ) info.outputChannels = ainfo.max_channels;\r
+ if ( ainfo.caps & PCM_CAP_INPUT ) info.inputChannels = ainfo.max_channels;\r
+ if ( ainfo.caps & PCM_CAP_DUPLEX ) {\r
+ if ( info.outputChannels > 0 && info.inputChannels > 0 && ainfo.caps & PCM_CAP_DUPLEX )\r
+ info.duplexChannels = (info.outputChannels > info.inputChannels) ? info.inputChannels : info.outputChannels;\r
+ }\r
+\r
+ // Probe data formats ... do for input\r
+ unsigned long mask = ainfo.iformats;\r
+ if ( mask & AFMT_S16_LE || mask & AFMT_S16_BE )\r
+ info.nativeFormats |= RTAUDIO_SINT16;\r
+ if ( mask & AFMT_S8 )\r
+ info.nativeFormats |= RTAUDIO_SINT8;\r
+ if ( mask & AFMT_S32_LE || mask & AFMT_S32_BE )\r
+ info.nativeFormats |= RTAUDIO_SINT32;\r
+ if ( mask & AFMT_FLOAT )\r
+ info.nativeFormats |= RTAUDIO_FLOAT32;\r
+ if ( mask & AFMT_S24_LE || mask & AFMT_S24_BE )\r
+ info.nativeFormats |= RTAUDIO_SINT24;\r
+\r
+ // Check that we have at least one supported format\r
+ if ( info.nativeFormats == 0 ) {\r
+ errorStream_ << "RtApiOss::getDeviceInfo: device (" << ainfo.name << ") data format not supported by RtAudio.";\r
+ errorText_ = errorStream_.str();\r
+ error( RtAudioError::WARNING );\r
+ return info;\r
+ }\r
+\r
+ // Probe the supported sample rates.\r
+ info.sampleRates.clear();\r
+ if ( ainfo.nrates ) {\r
+ for ( unsigned int i=0; i<ainfo.nrates; i++ ) {\r
+ for ( unsigned int k=0; k<MAX_SAMPLE_RATES; k++ ) {\r
+ if ( ainfo.rates[i] == SAMPLE_RATES[k] ) {\r
+ info.sampleRates.push_back( SAMPLE_RATES[k] );\r
+\r
+ if ( !info.preferredSampleRate || ( SAMPLE_RATES[k] <= 48000 && SAMPLE_RATES[k] > info.preferredSampleRate ) )\r
+ info.preferredSampleRate = SAMPLE_RATES[k];\r
+\r
+ break;\r
+ }\r
+ }\r
+ }\r
+ }\r
+ else {\r
+ // Check min and max rate values;\r
+ for ( unsigned int k=0; k<MAX_SAMPLE_RATES; k++ ) {\r
+ if ( ainfo.min_rate <= (int) SAMPLE_RATES[k] && ainfo.max_rate >= (int) SAMPLE_RATES[k] ) {\r
+ info.sampleRates.push_back( SAMPLE_RATES[k] );\r
+\r
+ if ( !info.preferredSampleRate || ( SAMPLE_RATES[k] <= 48000 && SAMPLE_RATES[k] > info.preferredSampleRate ) )\r
+ info.preferredSampleRate = SAMPLE_RATES[k];\r
+ }\r
+ }\r
+ }\r
+\r
+ if ( info.sampleRates.size() == 0 ) {\r
+ errorStream_ << "RtApiOss::getDeviceInfo: no supported sample rates found for device (" << ainfo.name << ").";\r
+ errorText_ = errorStream_.str();\r
+ error( RtAudioError::WARNING );\r
+ }\r
+ else {\r
+ info.probed = true;\r
+ info.name = ainfo.name;\r
+ }\r
+\r
+ return info;\r
+}\r
+\r
+\r
+bool RtApiOss :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels,\r
+ unsigned int firstChannel, unsigned int sampleRate,\r
+ RtAudioFormat format, unsigned int *bufferSize,\r
+ RtAudio::StreamOptions *options )\r
+{\r
+ int mixerfd = open( "/dev/mixer", O_RDWR, 0 );\r
+ if ( mixerfd == -1 ) {\r
+ errorText_ = "RtApiOss::probeDeviceOpen: error opening '/dev/mixer'.";\r
+ return FAILURE;\r
+ }\r
+\r
+ oss_sysinfo sysinfo;\r
+ int result = ioctl( mixerfd, SNDCTL_SYSINFO, &sysinfo );\r
+ if ( result == -1 ) {\r
+ close( mixerfd );\r
+ errorText_ = "RtApiOss::probeDeviceOpen: error getting sysinfo, OSS version >= 4.0 is required.";\r
+ return FAILURE;\r
+ }\r
+\r
+ unsigned nDevices = sysinfo.numaudios;\r
+ if ( nDevices == 0 ) {\r
+ // This should not happen because a check is made before this function is called.\r
+ close( mixerfd );\r
+ errorText_ = "RtApiOss::probeDeviceOpen: no devices found!";\r
+ return FAILURE;\r
+ }\r
+\r
+ if ( device >= nDevices ) {\r
+ // This should not happen because a check is made before this function is called.\r
+ close( mixerfd );\r
+ errorText_ = "RtApiOss::probeDeviceOpen: device ID is invalid!";\r
+ return FAILURE;\r
+ }\r
+\r
+ oss_audioinfo ainfo;\r
+ ainfo.dev = device;\r
+ result = ioctl( mixerfd, SNDCTL_AUDIOINFO, &ainfo );\r
+ close( mixerfd );\r
+ if ( result == -1 ) {\r
+ errorStream_ << "RtApiOss::getDeviceInfo: error getting device (" << ainfo.name << ") info.";\r
+ errorText_ = errorStream_.str();\r
+ return FAILURE;\r
+ }\r
+\r
+ // Check if device supports input or output\r
+ if ( ( mode == OUTPUT && !( ainfo.caps & PCM_CAP_OUTPUT ) ) ||\r
+ ( mode == INPUT && !( ainfo.caps & PCM_CAP_INPUT ) ) ) {\r
+ if ( mode == OUTPUT )\r
+ errorStream_ << "RtApiOss::probeDeviceOpen: device (" << ainfo.name << ") does not support output.";\r
+ else\r
+ errorStream_ << "RtApiOss::probeDeviceOpen: device (" << ainfo.name << ") does not support input.";\r
+ errorText_ = errorStream_.str();\r
+ return FAILURE;\r
+ }\r
+\r
+ int flags = 0;\r
+ OssHandle *handle = (OssHandle *) stream_.apiHandle;\r
+ if ( mode == OUTPUT )\r
+ flags |= O_WRONLY;\r
+ else { // mode == INPUT\r
+ if (stream_.mode == OUTPUT && stream_.device[0] == device) {\r
+ // We just set the same device for playback ... close and reopen for duplex (OSS only).\r
+ close( handle->id[0] );\r
+ handle->id[0] = 0;\r
+ if ( !( ainfo.caps & PCM_CAP_DUPLEX ) ) {\r
+ errorStream_ << "RtApiOss::probeDeviceOpen: device (" << ainfo.name << ") does not support duplex mode.";\r
+ errorText_ = errorStream_.str();\r
+ return FAILURE;\r
+ }\r
+ // Check that the number previously set channels is the same.\r
+ if ( stream_.nUserChannels[0] != channels ) {\r
+ errorStream_ << "RtApiOss::probeDeviceOpen: input/output channels must be equal for OSS duplex device (" << ainfo.name << ").";\r
+ errorText_ = errorStream_.str();\r
+ return FAILURE;\r
+ }\r
+ flags |= O_RDWR;\r
+ }\r
+ else\r
+ flags |= O_RDONLY;\r
+ }\r
+\r
+ // Set exclusive access if specified.\r
+ if ( options && options->flags & RTAUDIO_HOG_DEVICE ) flags |= O_EXCL;\r
+\r
+ // Try to open the device.\r
+ int fd;\r
+ fd = open( ainfo.devnode, flags, 0 );\r
+ if ( fd == -1 ) {\r
+ if ( errno == EBUSY )\r
+ errorStream_ << "RtApiOss::probeDeviceOpen: device (" << ainfo.name << ") is busy.";\r
+ else\r
+ errorStream_ << "RtApiOss::probeDeviceOpen: error opening device (" << ainfo.name << ").";\r
+ errorText_ = errorStream_.str();\r
+ return FAILURE;\r
+ }\r
+\r
+ // For duplex operation, specifically set this mode (this doesn't seem to work).\r
+ /*\r
+ if ( flags | O_RDWR ) {\r
+ result = ioctl( fd, SNDCTL_DSP_SETDUPLEX, NULL );\r
+ if ( result == -1) {\r
+ errorStream_ << "RtApiOss::probeDeviceOpen: error setting duplex mode for device (" << ainfo.name << ").";\r
+ errorText_ = errorStream_.str();\r
+ return FAILURE;\r
+ }\r
+ }\r
+ */\r
+\r
+ // Check the device channel support.\r
+ stream_.nUserChannels[mode] = channels;\r
+ if ( ainfo.max_channels < (int)(channels + firstChannel) ) {\r
+ close( fd );\r
+ errorStream_ << "RtApiOss::probeDeviceOpen: the device (" << ainfo.name << ") does not support requested channel parameters.";\r
+ errorText_ = errorStream_.str();\r
+ return FAILURE;\r
+ }\r
+\r
+ // Set the number of channels.\r
+ int deviceChannels = channels + firstChannel;\r
+ result = ioctl( fd, SNDCTL_DSP_CHANNELS, &deviceChannels );\r
+ if ( result == -1 || deviceChannels < (int)(channels + firstChannel) ) {\r
+ close( fd );\r
+ errorStream_ << "RtApiOss::probeDeviceOpen: error setting channel parameters on device (" << ainfo.name << ").";\r
+ errorText_ = errorStream_.str();\r
+ return FAILURE;\r
+ }\r
+ stream_.nDeviceChannels[mode] = deviceChannels;\r
+\r
+ // Get the data format mask\r
+ int mask;\r
+ result = ioctl( fd, SNDCTL_DSP_GETFMTS, &mask );\r
+ if ( result == -1 ) {\r
+ close( fd );\r
+ errorStream_ << "RtApiOss::probeDeviceOpen: error getting device (" << ainfo.name << ") data formats.";\r
+ errorText_ = errorStream_.str();\r
+ return FAILURE;\r
+ }\r
+\r
+ // Determine how to set the device format.\r
+ stream_.userFormat = format;\r
+ int deviceFormat = -1;\r
+ stream_.doByteSwap[mode] = false;\r
+ if ( format == RTAUDIO_SINT8 ) {\r
+ if ( mask & AFMT_S8 ) {\r
+ deviceFormat = AFMT_S8;\r
+ stream_.deviceFormat[mode] = RTAUDIO_SINT8;\r
+ }\r
+ }\r
+ else if ( format == RTAUDIO_SINT16 ) {\r
+ if ( mask & AFMT_S16_NE ) {\r
+ deviceFormat = AFMT_S16_NE;\r
+ stream_.deviceFormat[mode] = RTAUDIO_SINT16;\r
+ }\r
+ else if ( mask & AFMT_S16_OE ) {\r
+ deviceFormat = AFMT_S16_OE;\r
+ stream_.deviceFormat[mode] = RTAUDIO_SINT16;\r
+ stream_.doByteSwap[mode] = true;\r
+ }\r
+ }\r
+ else if ( format == RTAUDIO_SINT24 ) {\r
+ if ( mask & AFMT_S24_NE ) {\r
+ deviceFormat = AFMT_S24_NE;\r
+ stream_.deviceFormat[mode] = RTAUDIO_SINT24;\r
+ }\r
+ else if ( mask & AFMT_S24_OE ) {\r
+ deviceFormat = AFMT_S24_OE;\r
+ stream_.deviceFormat[mode] = RTAUDIO_SINT24;\r
+ stream_.doByteSwap[mode] = true;\r
+ }\r
+ }\r
+ else if ( format == RTAUDIO_SINT32 ) {\r
+ if ( mask & AFMT_S32_NE ) {\r
+ deviceFormat = AFMT_S32_NE;\r
+ stream_.deviceFormat[mode] = RTAUDIO_SINT32;\r
+ }\r
+ else if ( mask & AFMT_S32_OE ) {\r
+ deviceFormat = AFMT_S32_OE;\r
+ stream_.deviceFormat[mode] = RTAUDIO_SINT32;\r
+ stream_.doByteSwap[mode] = true;\r
+ }\r
+ }\r
+\r
+ if ( deviceFormat == -1 ) {\r
+ // The user requested format is not natively supported by the device.\r
+ if ( mask & AFMT_S16_NE ) {\r
+ deviceFormat = AFMT_S16_NE;\r
+ stream_.deviceFormat[mode] = RTAUDIO_SINT16;\r
+ }\r
+ else if ( mask & AFMT_S32_NE ) {\r
+ deviceFormat = AFMT_S32_NE;\r
+ stream_.deviceFormat[mode] = RTAUDIO_SINT32;\r
+ }\r
+ else if ( mask & AFMT_S24_NE ) {\r
+ deviceFormat = AFMT_S24_NE;\r
+ stream_.deviceFormat[mode] = RTAUDIO_SINT24;\r
+ }\r
+ else if ( mask & AFMT_S16_OE ) {\r
+ deviceFormat = AFMT_S16_OE;\r
+ stream_.deviceFormat[mode] = RTAUDIO_SINT16;\r
+ stream_.doByteSwap[mode] = true;\r
+ }\r
+ else if ( mask & AFMT_S32_OE ) {\r
+ deviceFormat = AFMT_S32_OE;\r
+ stream_.deviceFormat[mode] = RTAUDIO_SINT32;\r
+ stream_.doByteSwap[mode] = true;\r
+ }\r
+ else if ( mask & AFMT_S24_OE ) {\r
+ deviceFormat = AFMT_S24_OE;\r
+ stream_.deviceFormat[mode] = RTAUDIO_SINT24;\r
+ stream_.doByteSwap[mode] = true;\r
+ }\r
+ else if ( mask & AFMT_S8) {\r
+ deviceFormat = AFMT_S8;\r
+ stream_.deviceFormat[mode] = RTAUDIO_SINT8;\r
+ }\r
+ }\r
+\r
+ if ( stream_.deviceFormat[mode] == 0 ) {\r
+ // This really shouldn't happen ...\r
+ close( fd );\r
+ errorStream_ << "RtApiOss::probeDeviceOpen: device (" << ainfo.name << ") data format not supported by RtAudio.";\r
+ errorText_ = errorStream_.str();\r
+ return FAILURE;\r
+ }\r
+\r
+ // Set the data format.\r
+ int temp = deviceFormat;\r
+ result = ioctl( fd, SNDCTL_DSP_SETFMT, &deviceFormat );\r
+ if ( result == -1 || deviceFormat != temp ) {\r
+ close( fd );\r
+ errorStream_ << "RtApiOss::probeDeviceOpen: error setting data format on device (" << ainfo.name << ").";\r
+ errorText_ = errorStream_.str();\r
+ return FAILURE;\r
+ }\r
+\r
+ // Attempt to set the buffer size. According to OSS, the minimum\r
+ // number of buffers is two. The supposed minimum buffer size is 16\r
+ // bytes, so that will be our lower bound. The argument to this\r
+ // call is in the form 0xMMMMSSSS (hex), where the buffer size (in\r
+ // bytes) is given as 2^SSSS and the number of buffers as 2^MMMM.\r
+ // We'll check the actual value used near the end of the setup\r
+ // procedure.\r
+ int ossBufferBytes = *bufferSize * formatBytes( stream_.deviceFormat[mode] ) * deviceChannels;\r
+ if ( ossBufferBytes < 16 ) ossBufferBytes = 16;\r
+ int buffers = 0;\r
+ if ( options ) buffers = options->numberOfBuffers;\r
+ if ( options && options->flags & RTAUDIO_MINIMIZE_LATENCY ) buffers = 2;\r
+ if ( buffers < 2 ) buffers = 3;\r
+ temp = ((int) buffers << 16) + (int)( log10( (double)ossBufferBytes ) / log10( 2.0 ) );\r
+ result = ioctl( fd, SNDCTL_DSP_SETFRAGMENT, &temp );\r
+ if ( result == -1 ) {\r
+ close( fd );\r
+ errorStream_ << "RtApiOss::probeDeviceOpen: error setting buffer size on device (" << ainfo.name << ").";\r
+ errorText_ = errorStream_.str();\r
+ return FAILURE;\r
+ }\r
+ stream_.nBuffers = buffers;\r
+\r
+ // Save buffer size (in sample frames).\r
+ *bufferSize = ossBufferBytes / ( formatBytes(stream_.deviceFormat[mode]) * deviceChannels );\r
+ stream_.bufferSize = *bufferSize;\r
+\r
+ // Set the sample rate.\r
+ int srate = sampleRate;\r
+ result = ioctl( fd, SNDCTL_DSP_SPEED, &srate );\r
+ if ( result == -1 ) {\r
+ close( fd );\r
+ errorStream_ << "RtApiOss::probeDeviceOpen: error setting sample rate (" << sampleRate << ") on device (" << ainfo.name << ").";\r
+ errorText_ = errorStream_.str();\r
+ return FAILURE;\r
+ }\r
+\r
+ // Verify the sample rate setup worked.\r
+ if ( abs( srate - sampleRate ) > 100 ) {\r
+ close( fd );\r
+ errorStream_ << "RtApiOss::probeDeviceOpen: device (" << ainfo.name << ") does not support sample rate (" << sampleRate << ").";\r
+ errorText_ = errorStream_.str();\r
+ return FAILURE;\r
+ }\r
+ stream_.sampleRate = sampleRate;\r
+\r
+ if ( mode == INPUT && stream_.mode == OUTPUT && stream_.device[0] == device) {\r
+ // We're doing duplex setup here.\r
+ stream_.deviceFormat[0] = stream_.deviceFormat[1];\r
+ stream_.nDeviceChannels[0] = deviceChannels;\r
+ }\r
+\r
+ // Set interleaving parameters.\r
+ stream_.userInterleaved = true;\r
+ stream_.deviceInterleaved[mode] = true;\r
+ if ( options && options->flags & RTAUDIO_NONINTERLEAVED )\r
+ stream_.userInterleaved = false;\r
+\r
+ // Set flags for buffer conversion\r
+ stream_.doConvertBuffer[mode] = false;\r
+ if ( stream_.userFormat != stream_.deviceFormat[mode] )\r
+ stream_.doConvertBuffer[mode] = true;\r
+ if ( stream_.nUserChannels[mode] < stream_.nDeviceChannels[mode] )\r
+ stream_.doConvertBuffer[mode] = true;\r
+ if ( stream_.userInterleaved != stream_.deviceInterleaved[mode] &&\r
+ stream_.nUserChannels[mode] > 1 )\r
+ stream_.doConvertBuffer[mode] = true;\r
+\r
+ // Allocate the stream handles if necessary and then save.\r
+ if ( stream_.apiHandle == 0 ) {\r
+ try {\r
+ handle = new OssHandle;\r
+ }\r
+ catch ( std::bad_alloc& ) {\r
+ errorText_ = "RtApiOss::probeDeviceOpen: error allocating OssHandle memory.";\r
+ goto error;\r
+ }\r
+\r
+ if ( pthread_cond_init( &handle->runnable, NULL ) ) {\r
+ errorText_ = "RtApiOss::probeDeviceOpen: error initializing pthread condition variable.";\r
+ goto error;\r
+ }\r
+\r
+ stream_.apiHandle = (void *) handle;\r
+ }\r
+ else {\r
+ handle = (OssHandle *) stream_.apiHandle;\r
+ }\r
+ handle->id[mode] = fd;\r
+\r
+ // Allocate necessary internal buffers.\r
+ unsigned long bufferBytes;\r
+ bufferBytes = stream_.nUserChannels[mode] * *bufferSize * formatBytes( stream_.userFormat );\r
+ stream_.userBuffer[mode] = (char *) calloc( bufferBytes, 1 );\r
+ if ( stream_.userBuffer[mode] == NULL ) {\r
+ errorText_ = "RtApiOss::probeDeviceOpen: error allocating user buffer memory.";\r
+ goto error;\r
+ }\r
+\r
+ if ( stream_.doConvertBuffer[mode] ) {\r
+\r
+ bool makeBuffer = true;\r
+ bufferBytes = stream_.nDeviceChannels[mode] * formatBytes( stream_.deviceFormat[mode] );\r
+ if ( mode == INPUT ) {\r
+ if ( stream_.mode == OUTPUT && stream_.deviceBuffer ) {\r
+ unsigned long bytesOut = stream_.nDeviceChannels[0] * formatBytes( stream_.deviceFormat[0] );\r
+ if ( bufferBytes <= bytesOut ) makeBuffer = false;\r
+ }\r
+ }\r
+\r
+ if ( makeBuffer ) {\r
+ bufferBytes *= *bufferSize;\r
+ if ( stream_.deviceBuffer ) free( stream_.deviceBuffer );\r
+ stream_.deviceBuffer = (char *) calloc( bufferBytes, 1 );\r
+ if ( stream_.deviceBuffer == NULL ) {\r
+ errorText_ = "RtApiOss::probeDeviceOpen: error allocating device buffer memory.";\r
+ goto error;\r
+ }\r
+ }\r
+ }\r
+\r
+ stream_.device[mode] = device;\r
+ stream_.state = STREAM_STOPPED;\r
+\r
+ // Setup the buffer conversion information structure.\r
+ if ( stream_.doConvertBuffer[mode] ) setConvertInfo( mode, firstChannel );\r
+\r
+ // Setup thread if necessary.\r
+ if ( stream_.mode == OUTPUT && mode == INPUT ) {\r
+ // We had already set up an output stream.\r
+ stream_.mode = DUPLEX;\r
+ if ( stream_.device[0] == device ) handle->id[0] = fd;\r
+ }\r
+ else {\r
+ stream_.mode = mode;\r
+\r
+ // Setup callback thread.\r
+ stream_.callbackInfo.object = (void *) this;\r
+\r
+ // Set the thread attributes for joinable and realtime scheduling\r
+ // priority. The higher priority will only take affect if the\r
+ // program is run as root or suid.\r
+ pthread_attr_t attr;\r
+ pthread_attr_init( &attr );\r
+ pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_JOINABLE );\r
+#ifdef SCHED_RR // Undefined with some OSes (eg: NetBSD 1.6.x with GNU Pthread)\r
+ if ( options && options->flags & RTAUDIO_SCHEDULE_REALTIME ) {\r
+ struct sched_param param;\r
+ int priority = options->priority;\r
+ int min = sched_get_priority_min( SCHED_RR );\r
+ int max = sched_get_priority_max( SCHED_RR );\r
+ if ( priority < min ) priority = min;\r
+ else if ( priority > max ) priority = max;\r
+ param.sched_priority = priority;\r
+ pthread_attr_setschedparam( &attr, ¶m );\r
+ pthread_attr_setschedpolicy( &attr, SCHED_RR );\r
+ }\r
+ else\r
+ pthread_attr_setschedpolicy( &attr, SCHED_OTHER );\r
+#else\r
+ pthread_attr_setschedpolicy( &attr, SCHED_OTHER );\r
+#endif\r
+\r
+ stream_.callbackInfo.isRunning = true;\r
+ result = pthread_create( &stream_.callbackInfo.thread, &attr, ossCallbackHandler, &stream_.callbackInfo );\r
+ pthread_attr_destroy( &attr );\r
+ if ( result ) {\r
+ stream_.callbackInfo.isRunning = false;\r
+ errorText_ = "RtApiOss::error creating callback thread!";\r
+ goto error;\r
+ }\r
+ }\r
+\r
+ return SUCCESS;\r
+\r
+ error:\r
+ if ( handle ) {\r
+ pthread_cond_destroy( &handle->runnable );\r
+ if ( handle->id[0] ) close( handle->id[0] );\r
+ if ( handle->id[1] ) close( handle->id[1] );\r
+ delete handle;\r
+ stream_.apiHandle = 0;\r
+ }\r
+\r
+ for ( int i=0; i<2; i++ ) {\r
+ if ( stream_.userBuffer[i] ) {\r
+ free( stream_.userBuffer[i] );\r
+ stream_.userBuffer[i] = 0;\r
+ }\r
+ }\r
+\r
+ if ( stream_.deviceBuffer ) {\r
+ free( stream_.deviceBuffer );\r
+ stream_.deviceBuffer = 0;\r
+ }\r
+\r
+ return FAILURE;\r
+}\r
+\r
+void RtApiOss :: closeStream()\r
+{\r
+ if ( stream_.state == STREAM_CLOSED ) {\r
+ errorText_ = "RtApiOss::closeStream(): no open stream to close!";\r
+ error( RtAudioError::WARNING );\r
+ return;\r
+ }\r
+\r
+ OssHandle *handle = (OssHandle *) stream_.apiHandle;\r
+ stream_.callbackInfo.isRunning = false;\r
+ MUTEX_LOCK( &stream_.mutex );\r
+ if ( stream_.state == STREAM_STOPPED )\r
+ pthread_cond_signal( &handle->runnable );\r
+ MUTEX_UNLOCK( &stream_.mutex );\r
+ pthread_join( stream_.callbackInfo.thread, NULL );\r
+\r
+ if ( stream_.state == STREAM_RUNNING ) {\r
+ if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX )\r
+ ioctl( handle->id[0], SNDCTL_DSP_HALT, 0 );\r
+ else\r
+ ioctl( handle->id[1], SNDCTL_DSP_HALT, 0 );\r
+ stream_.state = STREAM_STOPPED;\r
+ }\r
+\r
+ if ( handle ) {\r
+ pthread_cond_destroy( &handle->runnable );\r
+ if ( handle->id[0] ) close( handle->id[0] );\r
+ if ( handle->id[1] ) close( handle->id[1] );\r
+ delete handle;\r
+ stream_.apiHandle = 0;\r
+ }\r
+\r
+ for ( int i=0; i<2; i++ ) {\r
+ if ( stream_.userBuffer[i] ) {\r
+ free( stream_.userBuffer[i] );\r
+ stream_.userBuffer[i] = 0;\r
+ }\r
+ }\r
+\r
+ if ( stream_.deviceBuffer ) {\r
+ free( stream_.deviceBuffer );\r
+ stream_.deviceBuffer = 0;\r
+ }\r
+\r
+ stream_.mode = UNINITIALIZED;\r
+ stream_.state = STREAM_CLOSED;\r
+}\r
+\r
+void RtApiOss :: startStream()\r
+{\r
+ verifyStream();\r
+ if ( stream_.state == STREAM_RUNNING ) {\r
+ errorText_ = "RtApiOss::startStream(): the stream is already running!";\r
+ error( RtAudioError::WARNING );\r
+ return;\r
+ }\r
+\r
+ MUTEX_LOCK( &stream_.mutex );\r
+\r
+ stream_.state = STREAM_RUNNING;\r
+\r
+ // No need to do anything else here ... OSS automatically starts\r
+ // when fed samples.\r
+\r
+ MUTEX_UNLOCK( &stream_.mutex );\r
+\r
+ OssHandle *handle = (OssHandle *) stream_.apiHandle;\r
+ pthread_cond_signal( &handle->runnable );\r
+}\r
+\r
+void RtApiOss :: stopStream()\r
+{\r
+ verifyStream();\r
+ if ( stream_.state == STREAM_STOPPED ) {\r
+ errorText_ = "RtApiOss::stopStream(): the stream is already stopped!";\r
+ error( RtAudioError::WARNING );\r
+ return;\r
+ }\r
+\r
+ MUTEX_LOCK( &stream_.mutex );\r
+\r
+ // The state might change while waiting on a mutex.\r
+ if ( stream_.state == STREAM_STOPPED ) {\r
+ MUTEX_UNLOCK( &stream_.mutex );\r
+ return;\r
+ }\r
+\r
+ int result = 0;\r
+ OssHandle *handle = (OssHandle *) stream_.apiHandle;\r
+ if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) {\r
+\r
+ // Flush the output with zeros a few times.\r
+ char *buffer;\r
+ int samples;\r
+ RtAudioFormat format;\r
+\r
+ if ( stream_.doConvertBuffer[0] ) {\r
+ buffer = stream_.deviceBuffer;\r
+ samples = stream_.bufferSize * stream_.nDeviceChannels[0];\r
+ format = stream_.deviceFormat[0];\r
+ }\r
+ else {\r
+ buffer = stream_.userBuffer[0];\r
+ samples = stream_.bufferSize * stream_.nUserChannels[0];\r
+ format = stream_.userFormat;\r
+ }\r
+\r
+ memset( buffer, 0, samples * formatBytes(format) );\r
+ for ( unsigned int i=0; i<stream_.nBuffers+1; i++ ) {\r
+ result = write( handle->id[0], buffer, samples * formatBytes(format) );\r
+ if ( result == -1 ) {\r
+ errorText_ = "RtApiOss::stopStream: audio write error.";\r
+ error( RtAudioError::WARNING );\r
+ }\r
+ }\r
+\r
+ result = ioctl( handle->id[0], SNDCTL_DSP_HALT, 0 );\r
+ if ( result == -1 ) {\r
+ errorStream_ << "RtApiOss::stopStream: system error stopping callback procedure on device (" << stream_.device[0] << ").";\r
+ errorText_ = errorStream_.str();\r
+ goto unlock;\r
+ }\r
+ handle->triggered = false;\r
+ }\r
+\r
+ if ( stream_.mode == INPUT || ( stream_.mode == DUPLEX && handle->id[0] != handle->id[1] ) ) {\r
+ result = ioctl( handle->id[1], SNDCTL_DSP_HALT, 0 );\r
+ if ( result == -1 ) {\r
+ errorStream_ << "RtApiOss::stopStream: system error stopping input callback procedure on device (" << stream_.device[0] << ").";\r
+ errorText_ = errorStream_.str();\r
+ goto unlock;\r
+ }\r
+ }\r
+\r
+ unlock:\r
+ stream_.state = STREAM_STOPPED;\r
+ MUTEX_UNLOCK( &stream_.mutex );\r
+\r
+ if ( result != -1 ) return;\r
+ error( RtAudioError::SYSTEM_ERROR );\r
+}\r
+\r
+void RtApiOss :: abortStream()\r
+{\r
+ verifyStream();\r
+ if ( stream_.state == STREAM_STOPPED ) {\r
+ errorText_ = "RtApiOss::abortStream(): the stream is already stopped!";\r
+ error( RtAudioError::WARNING );\r
+ return;\r
+ }\r
+\r
+ MUTEX_LOCK( &stream_.mutex );\r
+\r
+ // The state might change while waiting on a mutex.\r
+ if ( stream_.state == STREAM_STOPPED ) {\r
+ MUTEX_UNLOCK( &stream_.mutex );\r
+ return;\r
+ }\r
+\r
+ int result = 0;\r
+ OssHandle *handle = (OssHandle *) stream_.apiHandle;\r
+ if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) {\r
+ result = ioctl( handle->id[0], SNDCTL_DSP_HALT, 0 );\r
+ if ( result == -1 ) {\r
+ errorStream_ << "RtApiOss::abortStream: system error stopping callback procedure on device (" << stream_.device[0] << ").";\r
+ errorText_ = errorStream_.str();\r
+ goto unlock;\r
+ }\r
+ handle->triggered = false;\r
+ }\r
+\r
+ if ( stream_.mode == INPUT || ( stream_.mode == DUPLEX && handle->id[0] != handle->id[1] ) ) {\r
+ result = ioctl( handle->id[1], SNDCTL_DSP_HALT, 0 );\r
+ if ( result == -1 ) {\r
+ errorStream_ << "RtApiOss::abortStream: system error stopping input callback procedure on device (" << stream_.device[0] << ").";\r
+ errorText_ = errorStream_.str();\r
+ goto unlock;\r
+ }\r
+ }\r
+\r
+ unlock:\r
+ stream_.state = STREAM_STOPPED;\r
+ MUTEX_UNLOCK( &stream_.mutex );\r
+\r
+ if ( result != -1 ) return;\r
+ error( RtAudioError::SYSTEM_ERROR );\r
+}\r
+\r
+void RtApiOss :: callbackEvent()\r
+{\r
+ OssHandle *handle = (OssHandle *) stream_.apiHandle;\r
+ if ( stream_.state == STREAM_STOPPED ) {\r
+ MUTEX_LOCK( &stream_.mutex );\r
+ pthread_cond_wait( &handle->runnable, &stream_.mutex );\r
+ if ( stream_.state != STREAM_RUNNING ) {\r
+ MUTEX_UNLOCK( &stream_.mutex );\r
+ return;\r
+ }\r
+ MUTEX_UNLOCK( &stream_.mutex );\r
+ }\r
+\r
+ if ( stream_.state == STREAM_CLOSED ) {\r
+ errorText_ = "RtApiOss::callbackEvent(): the stream is closed ... this shouldn't happen!";\r
+ error( RtAudioError::WARNING );\r
+ return;\r
+ }\r
+\r
+ // Invoke user callback to get fresh output data.\r
+ int doStopStream = 0;\r
+ RtAudioCallback callback = (RtAudioCallback) stream_.callbackInfo.callback;\r
+ double streamTime = getStreamTime();\r
+ RtAudioStreamStatus status = 0;\r
+ if ( stream_.mode != INPUT && handle->xrun[0] == true ) {\r
+ status |= RTAUDIO_OUTPUT_UNDERFLOW;\r
+ handle->xrun[0] = false;\r
+ }\r
+ if ( stream_.mode != OUTPUT && handle->xrun[1] == true ) {\r
+ status |= RTAUDIO_INPUT_OVERFLOW;\r
+ handle->xrun[1] = false;\r
+ }\r
+ doStopStream = callback( stream_.userBuffer[0], stream_.userBuffer[1],\r
+ stream_.bufferSize, streamTime, status, stream_.callbackInfo.userData );\r
+ if ( doStopStream == 2 ) {\r
+ this->abortStream();\r
+ return;\r
+ }\r
+\r
+ MUTEX_LOCK( &stream_.mutex );\r
+\r
+ // The state might change while waiting on a mutex.\r
+ if ( stream_.state == STREAM_STOPPED ) goto unlock;\r
+\r
+ int result;\r
+ char *buffer;\r
+ int samples;\r
+ RtAudioFormat format;\r
+\r
+ if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) {\r
+\r
+ // Setup parameters and do buffer conversion if necessary.\r
+ if ( stream_.doConvertBuffer[0] ) {\r
+ buffer = stream_.deviceBuffer;\r
+ convertBuffer( buffer, stream_.userBuffer[0], stream_.convertInfo[0] );\r
+ samples = stream_.bufferSize * stream_.nDeviceChannels[0];\r
+ format = stream_.deviceFormat[0];\r
+ }\r
+ else {\r
+ buffer = stream_.userBuffer[0];\r
+ samples = stream_.bufferSize * stream_.nUserChannels[0];\r
+ format = stream_.userFormat;\r
+ }\r
+\r
+ // Do byte swapping if necessary.\r
+ if ( stream_.doByteSwap[0] )\r
+ byteSwapBuffer( buffer, samples, format );\r
+\r
+ if ( stream_.mode == DUPLEX && handle->triggered == false ) {\r
+ int trig = 0;\r
+ ioctl( handle->id[0], SNDCTL_DSP_SETTRIGGER, &trig );\r
+ result = write( handle->id[0], buffer, samples * formatBytes(format) );\r
+ trig = PCM_ENABLE_INPUT|PCM_ENABLE_OUTPUT;\r
+ ioctl( handle->id[0], SNDCTL_DSP_SETTRIGGER, &trig );\r
+ handle->triggered = true;\r
+ }\r
+ else\r
+ // Write samples to device.\r
+ result = write( handle->id[0], buffer, samples * formatBytes(format) );\r
+\r
+ if ( result == -1 ) {\r
+ // We'll assume this is an underrun, though there isn't a\r
+ // specific means for determining that.\r
+ handle->xrun[0] = true;\r
+ errorText_ = "RtApiOss::callbackEvent: audio write error.";\r
+ error( RtAudioError::WARNING );\r
+ // Continue on to input section.\r
+ }\r
+ }\r
+\r
+ if ( stream_.mode == INPUT || stream_.mode == DUPLEX ) {\r
+\r
+ // Setup parameters.\r
+ if ( stream_.doConvertBuffer[1] ) {\r
+ buffer = stream_.deviceBuffer;\r
+ samples = stream_.bufferSize * stream_.nDeviceChannels[1];\r
+ format = stream_.deviceFormat[1];\r
+ }\r
+ else {\r
+ buffer = stream_.userBuffer[1];\r
+ samples = stream_.bufferSize * stream_.nUserChannels[1];\r
+ format = stream_.userFormat;\r
+ }\r
+\r
+ // Read samples from device.\r
+ result = read( handle->id[1], buffer, samples * formatBytes(format) );\r
+\r
+ if ( result == -1 ) {\r
+ // We'll assume this is an overrun, though there isn't a\r
+ // specific means for determining that.\r
+ handle->xrun[1] = true;\r
+ errorText_ = "RtApiOss::callbackEvent: audio read error.";\r
+ error( RtAudioError::WARNING );\r
+ goto unlock;\r
+ }\r
+\r
+ // Do byte swapping if necessary.\r
+ if ( stream_.doByteSwap[1] )\r
+ byteSwapBuffer( buffer, samples, format );\r
+\r
+ // Do buffer conversion if necessary.\r
+ if ( stream_.doConvertBuffer[1] )\r
+ convertBuffer( stream_.userBuffer[1], stream_.deviceBuffer, stream_.convertInfo[1] );\r
+ }\r
+\r
+ unlock:\r
+ MUTEX_UNLOCK( &stream_.mutex );\r
+\r
+ RtApi::tickStreamTime();\r
+ if ( doStopStream == 1 ) this->stopStream();\r
+}\r
+\r
+static void *ossCallbackHandler( void *ptr )\r
+{\r
+ CallbackInfo *info = (CallbackInfo *) ptr;\r
+ RtApiOss *object = (RtApiOss *) info->object;\r
+ bool *isRunning = &info->isRunning;\r
+\r
+ while ( *isRunning == true ) {\r
+ pthread_testcancel();\r
+ object->callbackEvent();\r
+ }\r
+\r
+ pthread_exit( NULL );\r
+}\r
+\r
+//******************** End of __LINUX_OSS__ *********************//\r
+#endif\r
+\r
+\r
+// *************************************************** //\r
+//\r
+// Protected common (OS-independent) RtAudio methods.\r
+//\r
+// *************************************************** //\r
+\r
+// This method can be modified to control the behavior of error\r
+// message printing.\r
+void RtApi :: error( RtAudioError::Type type )\r
+{\r
+ errorStream_.str(""); // clear the ostringstream\r
+\r
+ RtAudioErrorCallback errorCallback = (RtAudioErrorCallback) stream_.callbackInfo.errorCallback;\r
+ if ( errorCallback ) {\r
+ // abortStream() can generate new error messages. Ignore them. Just keep original one.\r
+\r
+ if ( firstErrorOccurred_ )\r
+ return;\r
+\r
+ firstErrorOccurred_ = true;\r
+ const std::string errorMessage = errorText_;\r
+\r
+ if ( type != RtAudioError::WARNING && stream_.state != STREAM_STOPPED) {\r
+ stream_.callbackInfo.isRunning = false; // exit from the thread\r
+ abortStream();\r
+ }\r
+\r
+ errorCallback( type, errorMessage );\r
+ firstErrorOccurred_ = false;\r
+ return;\r
+ }\r
+\r
+ if ( type == RtAudioError::WARNING && showWarnings_ == true )\r
+ std::cerr << '\n' << errorText_ << "\n\n";\r
+ else if ( type != RtAudioError::WARNING )\r
+ throw( RtAudioError( errorText_, type ) );\r
+}\r
+\r
+void RtApi :: verifyStream()\r
+{\r
+ if ( stream_.state == STREAM_CLOSED ) {\r
+ errorText_ = "RtApi:: a stream is not open!";\r
+ error( RtAudioError::INVALID_USE );\r
+ }\r
+}\r
+\r
+void RtApi :: clearStreamInfo()\r
+{\r
+ stream_.mode = UNINITIALIZED;\r
+ stream_.state = STREAM_CLOSED;\r
+ stream_.sampleRate = 0;\r
+ stream_.bufferSize = 0;\r
+ stream_.nBuffers = 0;\r
+ stream_.userFormat = 0;\r
+ stream_.userInterleaved = true;\r
+ stream_.streamTime = 0.0;\r
+ stream_.apiHandle = 0;\r
+ stream_.deviceBuffer = 0;\r
+ stream_.callbackInfo.callback = 0;\r
+ stream_.callbackInfo.userData = 0;\r
+ stream_.callbackInfo.isRunning = false;\r
+ stream_.callbackInfo.errorCallback = 0;\r
+ for ( int i=0; i<2; i++ ) {\r
+ stream_.device[i] = 11111;\r
+ stream_.doConvertBuffer[i] = false;\r
+ stream_.deviceInterleaved[i] = true;\r
+ stream_.doByteSwap[i] = false;\r
+ stream_.nUserChannels[i] = 0;\r
+ stream_.nDeviceChannels[i] = 0;\r
+ stream_.channelOffset[i] = 0;\r
+ stream_.deviceFormat[i] = 0;\r
+ stream_.latency[i] = 0;\r
+ stream_.userBuffer[i] = 0;\r
+ stream_.convertInfo[i].channels = 0;\r
+ stream_.convertInfo[i].inJump = 0;\r
+ stream_.convertInfo[i].outJump = 0;\r
+ stream_.convertInfo[i].inFormat = 0;\r
+ stream_.convertInfo[i].outFormat = 0;\r
+ stream_.convertInfo[i].inOffset.clear();\r
+ stream_.convertInfo[i].outOffset.clear();\r
+ }\r
+}\r
+\r
+unsigned int RtApi :: formatBytes( RtAudioFormat format )\r
+{\r
+ if ( format == RTAUDIO_SINT16 )\r
+ return 2;\r
+ else if ( format == RTAUDIO_SINT32 || format == RTAUDIO_FLOAT32 )\r
+ return 4;\r
+ else if ( format == RTAUDIO_FLOAT64 )\r
+ return 8;\r
+ else if ( format == RTAUDIO_SINT24 )\r
+ return 3;\r
+ else if ( format == RTAUDIO_SINT8 )\r
+ return 1;\r
+\r
+ errorText_ = "RtApi::formatBytes: undefined format.";\r
+ error( RtAudioError::WARNING );\r
+\r
+ return 0;\r
+}\r
+\r
+void RtApi :: setConvertInfo( StreamMode mode, unsigned int firstChannel )\r
+{\r
+ if ( mode == INPUT ) { // convert device to user buffer\r
+ stream_.convertInfo[mode].inJump = stream_.nDeviceChannels[1];\r
+ stream_.convertInfo[mode].outJump = stream_.nUserChannels[1];\r
+ stream_.convertInfo[mode].inFormat = stream_.deviceFormat[1];\r
+ stream_.convertInfo[mode].outFormat = stream_.userFormat;\r
+ }\r
+ else { // convert user to device buffer\r
+ stream_.convertInfo[mode].inJump = stream_.nUserChannels[0];\r
+ stream_.convertInfo[mode].outJump = stream_.nDeviceChannels[0];\r
+ stream_.convertInfo[mode].inFormat = stream_.userFormat;\r
+ stream_.convertInfo[mode].outFormat = stream_.deviceFormat[0];\r
+ }\r
+\r
+ if ( stream_.convertInfo[mode].inJump < stream_.convertInfo[mode].outJump )\r
+ stream_.convertInfo[mode].channels = stream_.convertInfo[mode].inJump;\r
+ else\r
+ stream_.convertInfo[mode].channels = stream_.convertInfo[mode].outJump;\r
+\r
+ // Set up the interleave/deinterleave offsets.\r
+ if ( stream_.deviceInterleaved[mode] != stream_.userInterleaved ) {\r
+ if ( ( mode == OUTPUT && stream_.deviceInterleaved[mode] ) ||\r
+ ( mode == INPUT && stream_.userInterleaved ) ) {\r
+ for ( int k=0; k<stream_.convertInfo[mode].channels; k++ ) {\r
+ stream_.convertInfo[mode].inOffset.push_back( k * stream_.bufferSize );\r
+ stream_.convertInfo[mode].outOffset.push_back( k );\r
+ stream_.convertInfo[mode].inJump = 1;\r
+ }\r
+ }\r
+ else {\r
+ for ( int k=0; k<stream_.convertInfo[mode].channels; k++ ) {\r
+ stream_.convertInfo[mode].inOffset.push_back( k );\r
+ stream_.convertInfo[mode].outOffset.push_back( k * stream_.bufferSize );\r
+ stream_.convertInfo[mode].outJump = 1;\r
+ }\r
+ }\r
+ }\r
+ else { // no (de)interleaving\r
+ if ( stream_.userInterleaved ) {\r
+ for ( int k=0; k<stream_.convertInfo[mode].channels; k++ ) {\r
+ stream_.convertInfo[mode].inOffset.push_back( k );\r
+ stream_.convertInfo[mode].outOffset.push_back( k );\r
+ }\r
+ }\r
+ else {\r
+ for ( int k=0; k<stream_.convertInfo[mode].channels; k++ ) {\r
+ stream_.convertInfo[mode].inOffset.push_back( k * stream_.bufferSize );\r
+ stream_.convertInfo[mode].outOffset.push_back( k * stream_.bufferSize );\r
+ stream_.convertInfo[mode].inJump = 1;\r
+ stream_.convertInfo[mode].outJump = 1;\r
+ }\r
+ }\r
+ }\r
+\r
+ // Add channel offset.\r
+ if ( firstChannel > 0 ) {\r
+ if ( stream_.deviceInterleaved[mode] ) {\r
+ if ( mode == OUTPUT ) {\r
+ for ( int k=0; k<stream_.convertInfo[mode].channels; k++ )\r
+ stream_.convertInfo[mode].outOffset[k] += firstChannel;\r
+ }\r
+ else {\r
+ for ( int k=0; k<stream_.convertInfo[mode].channels; k++ )\r
+ stream_.convertInfo[mode].inOffset[k] += firstChannel;\r
+ }\r
+ }\r
+ else {\r
+ if ( mode == OUTPUT ) {\r
+ for ( int k=0; k<stream_.convertInfo[mode].channels; k++ )\r
+ stream_.convertInfo[mode].outOffset[k] += ( firstChannel * stream_.bufferSize );\r
+ }\r
+ else {\r
+ for ( int k=0; k<stream_.convertInfo[mode].channels; k++ )\r
+ stream_.convertInfo[mode].inOffset[k] += ( firstChannel * stream_.bufferSize );\r
+ }\r
+ }\r
+ }\r
+}\r
+\r
+void RtApi :: convertBuffer( char *outBuffer, char *inBuffer, ConvertInfo &info )\r
+{\r
+ // This function does format conversion, input/output channel compensation, and\r
+ // data interleaving/deinterleaving. 24-bit integers are assumed to occupy\r
+ // the lower three bytes of a 32-bit integer.\r
+\r
+ // Clear our device buffer when in/out duplex device channels are different\r
+ if ( outBuffer == stream_.deviceBuffer && stream_.mode == DUPLEX &&\r
+ ( stream_.nDeviceChannels[0] < stream_.nDeviceChannels[1] ) )\r
+ memset( outBuffer, 0, stream_.bufferSize * info.outJump * formatBytes( info.outFormat ) );\r
+\r
+ int j;\r
+ if (info.outFormat == RTAUDIO_FLOAT64) {\r
+ Float64 scale;\r
+ Float64 *out = (Float64 *)outBuffer;\r
+\r
+ if (info.inFormat == RTAUDIO_SINT8) {\r
+ signed char *in = (signed char *)inBuffer;\r
+ scale = 1.0 / 127.5;\r
+ for (unsigned int i=0; i<stream_.bufferSize; i++) {\r
+ for (j=0; j<info.channels; j++) {\r
+ out[info.outOffset[j]] = (Float64) in[info.inOffset[j]];\r
+ out[info.outOffset[j]] += 0.5;\r
+ out[info.outOffset[j]] *= scale;\r
+ }\r
+ in += info.inJump;\r
+ out += info.outJump;\r
+ }\r
+ }\r
+ else if (info.inFormat == RTAUDIO_SINT16) {\r
+ Int16 *in = (Int16 *)inBuffer;\r
+ scale = 1.0 / 32767.5;\r
+ for (unsigned int i=0; i<stream_.bufferSize; i++) {\r
+ for (j=0; j<info.channels; j++) {\r
+ out[info.outOffset[j]] = (Float64) in[info.inOffset[j]];\r
+ out[info.outOffset[j]] += 0.5;\r
+ out[info.outOffset[j]] *= scale;\r
+ }\r
+ in += info.inJump;\r
+ out += info.outJump;\r
+ }\r
+ }\r
+ else if (info.inFormat == RTAUDIO_SINT24) {\r
+ Int24 *in = (Int24 *)inBuffer;\r
+ scale = 1.0 / 8388607.5;\r
+ for (unsigned int i=0; i<stream_.bufferSize; i++) {\r
+ for (j=0; j<info.channels; j++) {\r
+ out[info.outOffset[j]] = (Float64) (in[info.inOffset[j]].asInt());\r
+ out[info.outOffset[j]] += 0.5;\r
+ out[info.outOffset[j]] *= scale;\r
+ }\r
+ in += info.inJump;\r
+ out += info.outJump;\r
+ }\r
+ }\r
+ else if (info.inFormat == RTAUDIO_SINT32) {\r
+ Int32 *in = (Int32 *)inBuffer;\r
+ scale = 1.0 / 2147483647.5;\r
+ for (unsigned int i=0; i<stream_.bufferSize; i++) {\r
+ for (j=0; j<info.channels; j++) {\r
+ out[info.outOffset[j]] = (Float64) in[info.inOffset[j]];\r
+ out[info.outOffset[j]] += 0.5;\r
+ out[info.outOffset[j]] *= scale;\r
+ }\r
+ in += info.inJump;\r
+ out += info.outJump;\r
+ }\r
+ }\r
+ else if (info.inFormat == RTAUDIO_FLOAT32) {\r
+ Float32 *in = (Float32 *)inBuffer;\r
+ for (unsigned int i=0; i<stream_.bufferSize; i++) {\r
+ for (j=0; j<info.channels; j++) {\r
+ out[info.outOffset[j]] = (Float64) in[info.inOffset[j]];\r
+ }\r
+ in += info.inJump;\r
+ out += info.outJump;\r
+ }\r
+ }\r
+ else if (info.inFormat == RTAUDIO_FLOAT64) {\r
+ // Channel compensation and/or (de)interleaving only.\r
+ Float64 *in = (Float64 *)inBuffer;\r
+ for (unsigned int i=0; i<stream_.bufferSize; i++) {\r
+ for (j=0; j<info.channels; j++) {\r
+ out[info.outOffset[j]] = in[info.inOffset[j]];\r
+ }\r
+ in += info.inJump;\r
+ out += info.outJump;\r
+ }\r
+ }\r
+ }\r
+ else if (info.outFormat == RTAUDIO_FLOAT32) {\r
+ Float32 scale;\r
+ Float32 *out = (Float32 *)outBuffer;\r
+\r
+ if (info.inFormat == RTAUDIO_SINT8) {\r
+ signed char *in = (signed char *)inBuffer;\r
+ scale = (Float32) ( 1.0 / 127.5 );\r
+ for (unsigned int i=0; i<stream_.bufferSize; i++) {\r
+ for (j=0; j<info.channels; j++) {\r
+ out[info.outOffset[j]] = (Float32) in[info.inOffset[j]];\r
+ out[info.outOffset[j]] += 0.5;\r
+ out[info.outOffset[j]] *= scale;\r
+ }\r
+ in += info.inJump;\r
+ out += info.outJump;\r
+ }\r
+ }\r
+ else if (info.inFormat == RTAUDIO_SINT16) {\r
+ Int16 *in = (Int16 *)inBuffer;\r
+ scale = (Float32) ( 1.0 / 32767.5 );\r
+ for (unsigned int i=0; i<stream_.bufferSize; i++) {\r
+ for (j=0; j<info.channels; j++) {\r
+ out[info.outOffset[j]] = (Float32) in[info.inOffset[j]];\r
+ out[info.outOffset[j]] += 0.5;\r
+ out[info.outOffset[j]] *= scale;\r
+ }\r
+ in += info.inJump;\r
+ out += info.outJump;\r
+ }\r
+ }\r
+ else if (info.inFormat == RTAUDIO_SINT24) {\r
+ Int24 *in = (Int24 *)inBuffer;\r
+ scale = (Float32) ( 1.0 / 8388607.5 );\r
+ for (unsigned int i=0; i<stream_.bufferSize; i++) {\r
+ for (j=0; j<info.channels; j++) {\r
+ out[info.outOffset[j]] = (Float32) (in[info.inOffset[j]].asInt());\r
+ out[info.outOffset[j]] += 0.5;\r
+ out[info.outOffset[j]] *= scale;\r
+ }\r
+ in += info.inJump;\r
+ out += info.outJump;\r
+ }\r
+ }\r
+ else if (info.inFormat == RTAUDIO_SINT32) {\r
+ Int32 *in = (Int32 *)inBuffer;\r
+ scale = (Float32) ( 1.0 / 2147483647.5 );\r
+ for (unsigned int i=0; i<stream_.bufferSize; i++) {\r
+ for (j=0; j<info.channels; j++) {\r
+ out[info.outOffset[j]] = (Float32) in[info.inOffset[j]];\r
+ out[info.outOffset[j]] += 0.5;\r
+ out[info.outOffset[j]] *= scale;\r
+ }\r
+ in += info.inJump;\r
+ out += info.outJump;\r
+ }\r
+ }\r
+ else if (info.inFormat == RTAUDIO_FLOAT32) {\r
+ // Channel compensation and/or (de)interleaving only.\r
+ Float32 *in = (Float32 *)inBuffer;\r
+ for (unsigned int i=0; i<stream_.bufferSize; i++) {\r
+ for (j=0; j<info.channels; j++) {\r
+ out[info.outOffset[j]] = in[info.inOffset[j]];\r
+ }\r
+ in += info.inJump;\r
+ out += info.outJump;\r
+ }\r
+ }\r
+ else if (info.inFormat == RTAUDIO_FLOAT64) {\r
+ Float64 *in = (Float64 *)inBuffer;\r
+ for (unsigned int i=0; i<stream_.bufferSize; i++) {\r
+ for (j=0; j<info.channels; j++) {\r
+ out[info.outOffset[j]] = (Float32) in[info.inOffset[j]];\r
+ }\r
+ in += info.inJump;\r
+ out += info.outJump;\r
+ }\r
+ }\r
+ }\r
+ else if (info.outFormat == RTAUDIO_SINT32) {\r
+ Int32 *out = (Int32 *)outBuffer;\r
+ if (info.inFormat == RTAUDIO_SINT8) {\r
+ signed char *in = (signed char *)inBuffer;\r
+ for (unsigned int i=0; i<stream_.bufferSize; i++) {\r
+ for (j=0; j<info.channels; j++) {\r
+ out[info.outOffset[j]] = (Int32) in[info.inOffset[j]];\r
+ out[info.outOffset[j]] <<= 24;\r
+ }\r
+ in += info.inJump;\r
+ out += info.outJump;\r
+ }\r
+ }\r
+ else if (info.inFormat == RTAUDIO_SINT16) {\r
+ Int16 *in = (Int16 *)inBuffer;\r
+ for (unsigned int i=0; i<stream_.bufferSize; i++) {\r
+ for (j=0; j<info.channels; j++) {\r
+ out[info.outOffset[j]] = (Int32) in[info.inOffset[j]];\r
+ out[info.outOffset[j]] <<= 16;\r
+ }\r
+ in += info.inJump;\r
+ out += info.outJump;\r
+ }\r
+ }\r
+ else if (info.inFormat == RTAUDIO_SINT24) {\r
+ Int24 *in = (Int24 *)inBuffer;\r
+ for (unsigned int i=0; i<stream_.bufferSize; i++) {\r
+ for (j=0; j<info.channels; j++) {\r
+ out[info.outOffset[j]] = (Int32) in[info.inOffset[j]].asInt();\r
+ out[info.outOffset[j]] <<= 8;\r
+ }\r
+ in += info.inJump;\r
+ out += info.outJump;\r
+ }\r
+ }\r
+ else if (info.inFormat == RTAUDIO_SINT32) {\r
+ // Channel compensation and/or (de)interleaving only.\r
+ Int32 *in = (Int32 *)inBuffer;\r
+ for (unsigned int i=0; i<stream_.bufferSize; i++) {\r
+ for (j=0; j<info.channels; j++) {\r
+ out[info.outOffset[j]] = in[info.inOffset[j]];\r
+ }\r
+ in += info.inJump;\r
+ out += info.outJump;\r
+ }\r
+ }\r
+ else if (info.inFormat == RTAUDIO_FLOAT32) {\r
+ Float32 *in = (Float32 *)inBuffer;\r
+ for (unsigned int i=0; i<stream_.bufferSize; i++) {\r
+ for (j=0; j<info.channels; j++) {\r
+ out[info.outOffset[j]] = (Int32) (in[info.inOffset[j]] * 2147483647.5 - 0.5);\r
+ }\r
+ in += info.inJump;\r
+ out += info.outJump;\r
+ }\r
+ }\r
+ else if (info.inFormat == RTAUDIO_FLOAT64) {\r
+ Float64 *in = (Float64 *)inBuffer;\r
+ for (unsigned int i=0; i<stream_.bufferSize; i++) {\r
+ for (j=0; j<info.channels; j++) {\r
+ out[info.outOffset[j]] = (Int32) (in[info.inOffset[j]] * 2147483647.5 - 0.5);\r
+ }\r
+ in += info.inJump;\r
+ out += info.outJump;\r
+ }\r
+ }\r
+ }\r
+ else if (info.outFormat == RTAUDIO_SINT24) {\r
+ Int24 *out = (Int24 *)outBuffer;\r
+ if (info.inFormat == RTAUDIO_SINT8) {\r
+ signed char *in = (signed char *)inBuffer;\r
+ for (unsigned int i=0; i<stream_.bufferSize; i++) {\r
+ for (j=0; j<info.channels; j++) {\r
+ out[info.outOffset[j]] = (Int32) (in[info.inOffset[j]] << 16);\r
+ //out[info.outOffset[j]] <<= 16;\r
+ }\r
+ in += info.inJump;\r
+ out += info.outJump;\r
+ }\r
+ }\r
+ else if (info.inFormat == RTAUDIO_SINT16) {\r
+ Int16 *in = (Int16 *)inBuffer;\r
+ for (unsigned int i=0; i<stream_.bufferSize; i++) {\r
+ for (j=0; j<info.channels; j++) {\r
+ out[info.outOffset[j]] = (Int32) (in[info.inOffset[j]] << 8);\r
+ //out[info.outOffset[j]] <<= 8;\r
+ }\r
+ in += info.inJump;\r
+ out += info.outJump;\r
+ }\r
+ }\r
+ else if (info.inFormat == RTAUDIO_SINT24) {\r
+ // Channel compensation and/or (de)interleaving only.\r
+ Int24 *in = (Int24 *)inBuffer;\r
+ for (unsigned int i=0; i<stream_.bufferSize; i++) {\r
+ for (j=0; j<info.channels; j++) {\r
+ out[info.outOffset[j]] = in[info.inOffset[j]];\r
+ }\r
+ in += info.inJump;\r
+ out += info.outJump;\r
+ }\r
+ }\r
+ else if (info.inFormat == RTAUDIO_SINT32) {\r
+ Int32 *in = (Int32 *)inBuffer;\r
+ for (unsigned int i=0; i<stream_.bufferSize; i++) {\r
+ for (j=0; j<info.channels; j++) {\r
+ out[info.outOffset[j]] = (Int32) (in[info.inOffset[j]] >> 8);\r
+ //out[info.outOffset[j]] >>= 8;\r
+ }\r
+ in += info.inJump;\r
+ out += info.outJump;\r
+ }\r
+ }\r
+ else if (info.inFormat == RTAUDIO_FLOAT32) {\r
+ Float32 *in = (Float32 *)inBuffer;\r
+ for (unsigned int i=0; i<stream_.bufferSize; i++) {\r
+ for (j=0; j<info.channels; j++) {\r
+ out[info.outOffset[j]] = (Int32) (in[info.inOffset[j]] * 8388607.5 - 0.5);\r
+ }\r
+ in += info.inJump;\r
+ out += info.outJump;\r
+ }\r
+ }\r
+ else if (info.inFormat == RTAUDIO_FLOAT64) {\r
+ Float64 *in = (Float64 *)inBuffer;\r
+ for (unsigned int i=0; i<stream_.bufferSize; i++) {\r
+ for (j=0; j<info.channels; j++) {\r
+ out[info.outOffset[j]] = (Int32) (in[info.inOffset[j]] * 8388607.5 - 0.5);\r
+ }\r
+ in += info.inJump;\r
+ out += info.outJump;\r
+ }\r
+ }\r
+ }\r
+ else if (info.outFormat == RTAUDIO_SINT16) {\r
+ Int16 *out = (Int16 *)outBuffer;\r
+ if (info.inFormat == RTAUDIO_SINT8) {\r
+ signed char *in = (signed char *)inBuffer;\r
+ for (unsigned int i=0; i<stream_.bufferSize; i++) {\r
+ for (j=0; j<info.channels; j++) {\r
+ out[info.outOffset[j]] = (Int16) in[info.inOffset[j]];\r
+ out[info.outOffset[j]] <<= 8;\r
+ }\r
+ in += info.inJump;\r
+ out += info.outJump;\r
+ }\r
+ }\r
+ else if (info.inFormat == RTAUDIO_SINT16) {\r
+ // Channel compensation and/or (de)interleaving only.\r
+ Int16 *in = (Int16 *)inBuffer;\r
+ for (unsigned int i=0; i<stream_.bufferSize; i++) {\r
+ for (j=0; j<info.channels; j++) {\r
+ out[info.outOffset[j]] = in[info.inOffset[j]];\r
+ }\r
+ in += info.inJump;\r
+ out += info.outJump;\r
+ }\r
+ }\r
+ else if (info.inFormat == RTAUDIO_SINT24) {\r
+ Int24 *in = (Int24 *)inBuffer;\r
+ for (unsigned int i=0; i<stream_.bufferSize; i++) {\r
+ for (j=0; j<info.channels; j++) {\r
+ out[info.outOffset[j]] = (Int16) (in[info.inOffset[j]].asInt() >> 8);\r
+ }\r
+ in += info.inJump;\r
+ out += info.outJump;\r
+ }\r
+ }\r
+ else if (info.inFormat == RTAUDIO_SINT32) {\r
+ Int32 *in = (Int32 *)inBuffer;\r
+ for (unsigned int i=0; i<stream_.bufferSize; i++) {\r
+ for (j=0; j<info.channels; j++) {\r
+ out[info.outOffset[j]] = (Int16) ((in[info.inOffset[j]] >> 16) & 0x0000ffff);\r
+ }\r
+ in += info.inJump;\r
+ out += info.outJump;\r
+ }\r
+ }\r
+ else if (info.inFormat == RTAUDIO_FLOAT32) {\r
+ Float32 *in = (Float32 *)inBuffer;\r
+ for (unsigned int i=0; i<stream_.bufferSize; i++) {\r
+ for (j=0; j<info.channels; j++) {\r
+ out[info.outOffset[j]] = (Int16) (in[info.inOffset[j]] * 32767.5 - 0.5);\r
+ }\r
+ in += info.inJump;\r
+ out += info.outJump;\r
+ }\r
+ }\r
+ else if (info.inFormat == RTAUDIO_FLOAT64) {\r
+ Float64 *in = (Float64 *)inBuffer;\r
+ for (unsigned int i=0; i<stream_.bufferSize; i++) {\r
+ for (j=0; j<info.channels; j++) {\r
+ out[info.outOffset[j]] = (Int16) (in[info.inOffset[j]] * 32767.5 - 0.5);\r
+ }\r
+ in += info.inJump;\r
+ out += info.outJump;\r
+ }\r
+ }\r
+ }\r
+ else if (info.outFormat == RTAUDIO_SINT8) {\r
+ signed char *out = (signed char *)outBuffer;\r
+ if (info.inFormat == RTAUDIO_SINT8) {\r
+ // Channel compensation and/or (de)interleaving only.\r
+ signed char *in = (signed char *)inBuffer;\r
+ for (unsigned int i=0; i<stream_.bufferSize; i++) {\r
+ for (j=0; j<info.channels; j++) {\r
+ out[info.outOffset[j]] = in[info.inOffset[j]];\r
+ }\r
+ in += info.inJump;\r
+ out += info.outJump;\r
+ }\r
+ }\r
+ if (info.inFormat == RTAUDIO_SINT16) {\r
+ Int16 *in = (Int16 *)inBuffer;\r
+ for (unsigned int i=0; i<stream_.bufferSize; i++) {\r
+ for (j=0; j<info.channels; j++) {\r
+ out[info.outOffset[j]] = (signed char) ((in[info.inOffset[j]] >> 8) & 0x00ff);\r
+ }\r
+ in += info.inJump;\r
+ out += info.outJump;\r
+ }\r
+ }\r
+ else if (info.inFormat == RTAUDIO_SINT24) {\r
+ Int24 *in = (Int24 *)inBuffer;\r
+ for (unsigned int i=0; i<stream_.bufferSize; i++) {\r
+ for (j=0; j<info.channels; j++) {\r
+ out[info.outOffset[j]] = (signed char) (in[info.inOffset[j]].asInt() >> 16);\r
+ }\r
+ in += info.inJump;\r
+ out += info.outJump;\r
+ }\r
+ }\r
+ else if (info.inFormat == RTAUDIO_SINT32) {\r
+ Int32 *in = (Int32 *)inBuffer;\r
+ for (unsigned int i=0; i<stream_.bufferSize; i++) {\r
+ for (j=0; j<info.channels; j++) {\r
+ out[info.outOffset[j]] = (signed char) ((in[info.inOffset[j]] >> 24) & 0x000000ff);\r
+ }\r
+ in += info.inJump;\r
+ out += info.outJump;\r
+ }\r
+ }\r
+ else if (info.inFormat == RTAUDIO_FLOAT32) {\r
+ Float32 *in = (Float32 *)inBuffer;\r
+ for (unsigned int i=0; i<stream_.bufferSize; i++) {\r
+ for (j=0; j<info.channels; j++) {\r
+ out[info.outOffset[j]] = (signed char) (in[info.inOffset[j]] * 127.5 - 0.5);\r
+ }\r
+ in += info.inJump;\r
+ out += info.outJump;\r
+ }\r
+ }\r
+ else if (info.inFormat == RTAUDIO_FLOAT64) {\r
+ Float64 *in = (Float64 *)inBuffer;\r
+ for (unsigned int i=0; i<stream_.bufferSize; i++) {\r
+ for (j=0; j<info.channels; j++) {\r
+ out[info.outOffset[j]] = (signed char) (in[info.inOffset[j]] * 127.5 - 0.5);\r
+ }\r
+ in += info.inJump;\r
+ out += info.outJump;\r
+ }\r
+ }\r
+ }\r
+}\r
+\r
+//static inline uint16_t bswap_16(uint16_t x) { return (x>>8) | (x<<8); }\r
+//static inline uint32_t bswap_32(uint32_t x) { return (bswap_16(x&0xffff)<<16) | (bswap_16(x>>16)); }\r
+//static inline uint64_t bswap_64(uint64_t x) { return (((unsigned long long)bswap_32(x&0xffffffffull))<<32) | (bswap_32(x>>32)); }\r
+\r
+void RtApi :: byteSwapBuffer( char *buffer, unsigned int samples, RtAudioFormat format )\r
+{\r
+ char val;\r
+ char *ptr;\r
+\r
+ ptr = buffer;\r
+ if ( format == RTAUDIO_SINT16 ) {\r
+ for ( unsigned int i=0; i<samples; i++ ) {\r
+ // Swap 1st and 2nd bytes.\r
+ val = *(ptr);\r
+ *(ptr) = *(ptr+1);\r
+ *(ptr+1) = val;\r
+\r
+ // Increment 2 bytes.\r
+ ptr += 2;\r
+ }\r
+ }\r
+ else if ( format == RTAUDIO_SINT32 ||\r
+ format == RTAUDIO_FLOAT32 ) {\r
+ for ( unsigned int i=0; i<samples; i++ ) {\r
+ // Swap 1st and 4th bytes.\r
+ val = *(ptr);\r
+ *(ptr) = *(ptr+3);\r
+ *(ptr+3) = val;\r
+\r
+ // Swap 2nd and 3rd bytes.\r
+ ptr += 1;\r
+ val = *(ptr);\r
+ *(ptr) = *(ptr+1);\r
+ *(ptr+1) = val;\r
+\r
+ // Increment 3 more bytes.\r
+ ptr += 3;\r
+ }\r
+ }\r
+ else if ( format == RTAUDIO_SINT24 ) {\r
+ for ( unsigned int i=0; i<samples; i++ ) {\r
+ // Swap 1st and 3rd bytes.\r
+ val = *(ptr);\r
+ *(ptr) = *(ptr+2);\r
+ *(ptr+2) = val;\r
+\r
+ // Increment 2 more bytes.\r
+ ptr += 2;\r
+ }\r
+ }\r
+ else if ( format == RTAUDIO_FLOAT64 ) {\r
+ for ( unsigned int i=0; i<samples; i++ ) {\r
+ // Swap 1st and 8th bytes\r
+ val = *(ptr);\r
+ *(ptr) = *(ptr+7);\r
+ *(ptr+7) = val;\r
+\r
+ // Swap 2nd and 7th bytes\r
+ ptr += 1;\r
+ val = *(ptr);\r
+ *(ptr) = *(ptr+5);\r
+ *(ptr+5) = val;\r
+\r
+ // Swap 3rd and 6th bytes\r
+ ptr += 1;\r
+ val = *(ptr);\r
+ *(ptr) = *(ptr+3);\r
+ *(ptr+3) = val;\r
+\r
+ // Swap 4th and 5th bytes\r
+ ptr += 1;\r
+ val = *(ptr);\r
+ *(ptr) = *(ptr+1);\r
+ *(ptr+1) = val;\r
+\r
+ // Increment 5 more bytes.\r
+ ptr += 5;\r
+ }\r
+ }\r
+}\r
+\r
+ // Indentation settings for Vim and Emacs\r
+ //\r
+ // Local Variables:\r
+ // c-basic-offset: 2\r
+ // indent-tabs-mode: nil\r
+ // End:\r
+ //\r
+ // vim: et sts=2 sw=2\r
--- /dev/null
+/************************************************************************/
+/*! \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.
+*/
+/************************************************************************/
+
+/*!
+ \file RtAudio.h
+ */
+
+#ifndef __RTAUDIO_H
+#define __RTAUDIO_H
+
+#define RTAUDIO_VERSION "4.1.2"
+
+#include <string>
+#include <vector>
+#include <exception>
+#include <iostream>
+
+/*! \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<unsigned int> 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<RtAudio::Api> &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 <windows.h>
+ #include <process.h>
+
+ 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 <pthread.h>
+
+ 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 <sys/time.h>
+#endif
+
+#include <sstream>
+
+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<int> inOffset;
+ std::vector<int> 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 <CoreAudio/AudioHardware.h>
+
+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<RtAudio::DeviceInfo> 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<struct DsDevice> 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<RtAudio::DeviceInfo> 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<RtAudio::DeviceInfo> 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
--- /dev/null
+#define IDI_ICON1 101
\ No newline at end of file
--- /dev/null
+#include "resource.h"
+IDI_ICON1 ICON DISCARDABLE "giada.ico"
\ No newline at end of file
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <cmath>
+#include <FL/Fl.H>
+#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;
+
+
+namespace giada {
+namespace c {
+namespace channel
+{
+int loadChannel(SampleChannel* ch, const string& fname)
+{
+ using namespace giada::m;
+
+ /* 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->isPlaying())
+ ch->kill(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("[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* addChannel(int column, ChannelType type, int size)
+{
+ Channel* ch = m::mh::addChannel(type);
+ geChannel* gch = G_MainWin->keyboard->addChannel(column, ch, size);
+ ch->guiChannel = gch;
+ return ch;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void deleteChannel(Channel* ch)
+{
+ using namespace giada::m;
+
+ 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, ch);
+#endif
+ Fl::lock();
+ G_MainWin->keyboard->deleteChannel(ch->guiChannel);
+ Fl::unlock();
+ mh::deleteChannel(ch);
+ gu_closeAllSubwindows();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void freeChannel(Channel* ch)
+{
+ if (ch->isPlaying()) {
+ 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);
+ m::recorder::clearChan(ch->index);
+ 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 toggleArm(Channel* ch, bool gui)
+{
+ ch->armed = !ch->armed;
+ if (!gui)
+ ch->guiChannel->arm->value(ch->armed);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void toggleInputMonitor(Channel* ch)
+{
+ SampleChannel* sch = static_cast<SampleChannel*>(ch);
+ sch->inputMonitor = !sch->inputMonitor;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+int cloneChannel(Channel* src)
+{
+ using namespace giada::m;
+
+ 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);
+
+ G_MainWin->keyboard->updateChannel(ch->guiChannel);
+ return true;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void 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 = static_cast<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 setPitch(SampleChannel* ch, float val)
+{
+ ch->setPitch(val);
+ gdSampleEditor* gdEditor = static_cast<gdSampleEditor*>(gu_getSubwindow(G_MainWin, WID_SAMPLE_EDITOR));
+ if (gdEditor) {
+ Fl::lock();
+ gdEditor->pitchTool->refresh();
+ Fl::unlock();
+ }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void setPanning(SampleChannel* ch, float val)
+{
+ ch->setPan(val);
+ gdSampleEditor* gdEditor = static_cast<gdSampleEditor*>(gu_getSubwindow(G_MainWin, WID_SAMPLE_EDITOR));
+ if (gdEditor) {
+ Fl::lock();
+ gdEditor->panTool->refresh();
+ Fl::unlock();
+ }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void toggleMute(Channel* ch, bool gui)
+{
+ ch->setMute(!ch->mute);
+ if (!gui) {
+ Fl::lock();
+ ch->guiChannel->mute->value(ch->mute);
+ Fl::unlock();
+ }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void toggleSolo(Channel* ch, bool gui)
+{
+ ch->solo = !ch->solo;
+ m::mh::updateSoloCount();
+ if (!gui) {
+ Fl::lock();
+ ch->guiChannel->solo->value(ch->solo);
+ Fl::unlock();
+ }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void kill(Channel* ch)
+{
+ ch->kill(0); // on frame 0: it's a user-generated event
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void setBoost(SampleChannel* ch, float val)
+{
+ ch->setBoost(val);
+ gdSampleEditor *gdEditor = static_cast<gdSampleEditor*>(gu_getSubwindow(G_MainWin, WID_SAMPLE_EDITOR));
+ if (gdEditor) {
+ Fl::lock();
+ gdEditor->boostTool->refresh();
+ Fl::unlock();
+ }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void setName(Channel* ch, const string& name)
+{
+ ch->name = name;
+ ch->guiChannel->update();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void toggleReadingActions(Channel* ch, bool gui)
+{
+
+ /* When you call 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 == ChannelStatus::WAIT))
+ stopReadingActions(ch, gui);
+ else
+ startReadingActions(ch, gui);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void startReadingActions(Channel* ch, bool gui)
+{
+ using namespace giada::m;
+
+ ch->startReadingActions(conf::treatRecsAsLoops, conf::recsStopOnChanHalt);
+
+ if (!gui) {
+ Fl::lock();
+ static_cast<geSampleChannel*>(ch->guiChannel)->readActions->value(1);
+ Fl::unlock();
+ }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void stopReadingActions(Channel* ch, bool gui)
+{
+ using namespace giada::m;
+
+ ch->stopReadingActions(clock::isRunning(), conf::treatRecsAsLoops,
+ conf::recsStopOnChanHalt);
+
+ if (!gui) {
+ Fl::lock();
+ static_cast<geSampleChannel*>(ch->guiChannel)->readActions->value(0);
+ Fl::unlock();
+ }
+}
+
+}}}; // giada::c::channel::
\ No newline at end of file
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef G_GLUE_CHANNEL_H
+#define G_GLUE_CHANNEL_H
+
+
+#include <string>
+#include "../core/types.h"
+
+
+class Channel;
+class SampleChannel;
+class gdSampleEditor;
+
+
+namespace giada {
+namespace c {
+namespace channel
+{
+/* addChannel
+Adds an empty new channel to the stack. Returns the new channel. */
+
+Channel* addChannel(int column, ChannelType type, int size);
+
+/* loadChannel
+Fills an existing channel with a wave. */
+
+int loadChannel(SampleChannel* ch, const std::string& fname);
+
+/* deleteChannel
+Removes a channel from Mixer. */
+
+void deleteChannel(Channel* ch);
+
+/* freeChannel
+Unloads the sample from a sample channel. */
+
+void freeChannel(Channel* ch);
+
+/* cloneChannel
+Makes an exact copy of Channel *ch. */
+
+int 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 toggleArm(Channel* ch, bool gui=true);
+void toggleInputMonitor(Channel* ch);
+void kill(Channel* ch);
+void toggleMute(Channel* ch, bool gui=true);
+void toggleSolo(Channel* ch, bool gui=true);
+void setVolume(Channel* ch, float v, bool gui=true, bool editor=false);
+void setName(Channel* ch, const std::string& name);
+void setPitch(SampleChannel* ch, float val);
+void setPanning(SampleChannel* ch, float val);
+void 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 toggleReadingActions(Channel* ch, bool gui=true);
+void startReadingActions(Channel* ch, bool gui=true);
+void stopReadingActions(Channel* ch, bool gui=true);
+
+}}}; // giada::c::channel::
+
+#endif
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <FL/Fl.H>
+#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 "../utils/math.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;
+
+
+namespace giada {
+namespace c {
+namespace io
+{
+void keyPress(Channel* ch, bool ctrl, bool shift, int velocity)
+{
+ /* Everything occurs on frame 0 here: they are all user-generated events. */
+ if (ctrl)
+ c::channel::toggleMute(ch);
+ else
+ if (shift) {
+ if (ch->recordKill())
+ ch->kill(0);
+ }
+ else {
+ if (ch->recordStart(m::clock::canQuantize()))
+ ch->start(0, m::clock::canQuantize(), 0);
+ }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void keyRelease(Channel* ch, bool ctrl, bool shift)
+{
+ if (!ctrl && !shift) {
+ ch->recordStop();
+ ch->stop();
+ }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void startStopActionRec(bool gui)
+{
+ m::recorder::active ? stopActionRec(gui) : startActionRec(gui);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void startActionRec(bool gui)
+{
+ using namespace giada::m;
+
+ if (kernelAudio::getStatus() == false)
+ return;
+
+ recorder::active = true;
+
+ if (!clock::isRunning())
+ glue_startSeq(false); // update gui
+
+ if (!gui) {
+ Fl::lock();
+ G_MainWin->mainTransport->updateRecAction(1);
+ Fl::unlock();
+ }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void stopActionRec(bool gui)
+{
+ /* Stop the recorder and sort newly recorder actions. */
+
+ m::recorder::active = false;
+ m::recorder::sortActions();
+
+ for (Channel* ch : m::mixer::channels)
+ {
+ if (ch->type == ChannelType::MIDI)
+ continue;
+ G_MainWin->keyboard->setChannelWithActions(static_cast<geSampleChannel*>(ch->guiChannel));
+ if (!ch->readActions && ch->hasActions)
+ c::channel::startReadingActions(ch, false);
+ }
+
+ if (!gui) {
+ Fl::lock();
+ G_MainWin->mainTransport->updateRecAction(0);
+ Fl::unlock();
+ }
+
+ gu_refreshActionEditor(); // in case it's open
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void startStopInputRec(bool gui)
+{
+ if (m::mixer::recording)
+ stopInputRec(gui);
+ else
+ if (!startInputRec(gui))
+ gdAlert("No channels armed/available for audio recording.");
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+int startInputRec(bool gui)
+{
+ using namespace giada::m;
+
+ 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 (Channel* ch : m::mixer::channels)
+ ch->guiChannel->update();
+
+ return true;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+int stopInputRec(bool gui)
+{
+ using namespace giada::m;
+
+ mh::stopInputRec();
+
+ Fl::lock();
+ if (!gui)
+ G_MainWin->mainTransport->updateRecInput(0);
+ G_MainWin->mainTimer->setLock(false);
+ Fl::unlock();
+
+ return 1;
+}
+
+}}} // giada::c::io::
\ No newline at end of file
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef G_GLUE_IO_H
+#define G_GLUE_IO_H
+
+
+class Channel;
+class SampleChannel;
+class MidiChannel;
+
+namespace giada {
+namespace c {
+namespace io
+{
+/* keyPress / keyRelease
+Handle the key pressure, either via mouse/keyboard or MIDI. If gui is true the
+event comes from the main window (mouse, keyboard or MIDI), otherwise the event
+comes from the action recorder. */
+
+void keyPress (Channel* ch, bool ctrl, bool shift, int velocity);
+void keyRelease(Channel* ch, bool ctrl, bool shift);
+
+/* 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 startStopActionRec(bool gui=true);
+void startActionRec(bool gui=true);
+void 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 startStopInputRec(bool gui=true);
+int startInputRec (bool gui=true);
+int stopInputRec (bool gui=true);
+
+}}} // giada::c::io::
+
+#endif
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <cmath>
+#include <FL/Fl.H>
+#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"
+#include "../core/const.h"
+#ifdef WITH_VST
+#include "../core/pluginHost.h"
+#endif
+#include "main.h"
+
+
+extern gdMainWindow *G_MainWin;
+
+
+using std::string;
+using namespace giada::m;
+
+
+namespace
+{
+void setBpm_(float f, string s)
+{
+ if (f < G_MIN_BPM) {
+ f = G_MIN_BPM;
+ s = G_MIN_BPM_STR;
+ }
+ else
+ if (f > G_MAX_BPM) {
+ f = G_MAX_BPM;
+ s = G_MAX_BPM_STR;
+ }
+
+ float vPre = clock::getBpm();
+ clock::setBpm(f);
+ recorder::updateBpm(vPre, f, clock::getQuanto());
+ mixer::allocVirtualInput(clock::getFramesInLoop());
+
+ gu_refreshActionEditor();
+ G_MainWin->mainTimer->setBpm(s.c_str());
+
+ gu_log("[glue::setBpm_] Bpm changed to %s (real=%f)\n", s.c_str(), clock::getBpm());
+}
+} // {anonymous}
+
+
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+
+
+void glue_setBpm(const char* v1, const char* v2)
+{
+ /* Never change this stuff while recording audio */
+
+ if (mixer::recording)
+ return;
+
+ /* 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 mixer and we show
+ the nice looking (but fake) one to the GUI.
+ On Linux, let Jack handle the bpm change if its on. */
+
+ float f = atof(v1) + (atof(v2)/10);
+ string s = string(v1) + "." + string(v2);
+
+#ifdef G_OS_LINUX
+ if (kernelAudio::getAPI() == G_SYS_API_JACK)
+ kernelAudio::jackSetBpm(f);
+ else
+#endif
+ setBpm_(f, s);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void glue_setBpm(float f)
+{
+ /* Never change this stuff while recording audio */
+
+ if (mixer::recording)
+ return;
+
+ float intpart;
+ float fracpart = std::round(std::modf(f, &intpart) * 10);
+ string s = std::to_string((int) intpart) + "." + std::to_string((int)fracpart);
+
+ setBpm_(f, s);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+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::getFramesInLoop();
+
+ clock::setBeats(beats);
+ clock::setBars(bars);
+ clock::updateFrameBars();
+ mixer::allocVirtualInput(clock::getFramesInLoop());
+
+ /* Update recorded actions, if 'expand' required and an expansion is taking
+ place. */
+
+ if (expand && clock::getBeats() > oldBeats)
+ recorder::expand(oldTotalFrames, clock::getFramesInLoop());
+
+ 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 (Channel* ch : mixer::channels) {
+ ch->empty();
+ ch->guiChannel->reset();
+ }
+ recorder::init();
+ return;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void glue_clearAllActions()
+{
+ recorder::init();
+ for (Channel* ch : mixer::channels)
+ ch->hasActions = false;
+ gu_updateControls();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void glue_resetToInitState(bool resetGui, bool createColumns)
+{
+ gu_closeAllSubwindows();
+ mixer::close();
+ clock::init(conf::samplerate, conf::midiTCfps);
+ mixer::init(clock::getFramesInLoop(), kernelAudio::getRealBufSize());
+ recorder::init();
+#ifdef WITH_VST
+ pluginHost::freeAllStacks(&mixer::channels, &mixer::mutex);
+#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);
+}
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef G_GLUE_MAIN_H
+#define G_GLUE_MAIN_H
+
+
+/* glue_setBpm (1)
+Sets bpm value from string to float. */
+
+void glue_setBpm(const char* v1, const char* v2);
+
+/* glue_setBpm (2)
+Sets bpm value. Usually called from the Jack callback or non-UI components. */
+
+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_clearAllActions();
+
+/* resetToInitState
+Resets 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
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifdef WITH_VST
+
+
+#include <FL/Fl.H>
+#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<gdPluginList*>(gu_getSubwindow(G_MainWin, WID_FX_LIST));
+ if (parent == nullptr)
+ return nullptr;
+ return static_cast<gdPluginWindow*>(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, ch);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void swapPlugins(Channel* ch, int index1, int index2, int stackType)
+{
+ pluginHost::swapPlugin(index1, index2, stackType, &mixer::mutex,
+ ch);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void freePlugin(Channel* ch, int index, int stackType)
+{
+ pluginHost::freePlugin(index, stackType, &mixer::mutex, 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<gdConfig*>(gu_getSubwindow(G_MainWin, WID_CONFIG));
+ configWin->refreshVstPath();
+}
+
+}}}; // giada::c::plugin::
+
+
+#endif
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#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
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <cassert>
+#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 "../core/mixer.h"
+#include "../core/sampleChannel.h"
+#include "../core/midiChannel.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, bool refreshActionEditor=true)
+{
+ gch->ch->hasActions = m::recorder::hasActions(gch->ch->index);
+ if (gch->ch->type == ChannelType::SAMPLE) {
+ geSampleChannel* gsch = static_cast<geSampleChannel*>(gch);
+ gsch->ch->hasActions ? gsch->showActionButton() : gsch->hideActionButton();
+ }
+ if (refreshActionEditor)
+ gu_refreshActionEditor(); // refresh a.editor window, it could be open
+}
+}; // {anonymous}
+
+
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+
+
+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);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+bool midiActionCanFit(int chan, int note, int frame_a, int frame_b)
+{
+ namespace mr = m::recorder;
+
+ /* TODO - This is insane, to say the least. Let's wait for recorder refactoring... */
+
+ vector<mr::Composite> comps = getMidiActions(chan);
+ for (mr::Composite c : comps)
+ if (frame_b >= c.a1.frame && c.a2.frame >= frame_a && m::MidiEvent(c.a1.iValue).getNote() == note)
+ return false;
+ return true;
+}
+
+
+bool sampleActionCanFit(const SampleChannel* ch, int frame_a, int frame_b)
+{
+ namespace mr = m::recorder;
+
+ /* TODO - Even more insanity... Let's wait for recorder refactoring... */
+
+ vector<mr::Composite> comps = getSampleActions(ch);
+ for (mr::Composite c : comps)
+ if (frame_b >= c.a1.frame && c.a2.frame >= frame_a)
+ return false;
+ return true;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void recordMidiAction(int chan, int note, int velocity, int frame_a, int frame_b)
+{
+ if (frame_b == 0)
+ frame_b = frame_a + G_DEFAULT_ACTION_SIZE;
+
+ /* Avoid frame overflow. */
+
+ int overflow = frame_b - (m::clock::getFramesInLoop());
+ if (overflow > 0) {
+ frame_b -= overflow;
+ frame_a -= overflow;
+ }
+
+ /* Prepare MIDI events. Due to some nasty restrictions on the ancient Recorder,
+ checks for overlapping are done by the caller. TODO ... */
+
+ m::MidiEvent event_a = m::MidiEvent(m::MidiEvent::NOTE_ON, note, velocity);
+ m::MidiEvent event_b = m::MidiEvent(m::MidiEvent::NOTE_OFF, note, velocity);
+
+ m::recorder::rec(chan, G_ACTION_MIDI, frame_a, event_a.getRaw());
+ m::recorder::rec(chan, G_ACTION_MIDI, frame_b, event_b.getRaw());
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void deleteMidiAction(MidiChannel* ch, m::recorder::action a1, m::recorder::action a2)
+{
+ m::recorder::deleteAction(ch->index, a1.frame, G_ACTION_MIDI, true,
+ &m::mixer::mutex, a1.iValue, 0.0);
+
+ /* If action 1 is not orphaned, send a note-off first in case we are deleting
+ it in a middle of a key_on/key_off sequence. Conversely, orphaned actions
+ should not play, so no need to fire the note-off. */
+
+ if (a2.frame != -1) {
+ ch->sendMidi(a2.iValue);
+ m::recorder::deleteAction(ch->index, a2.frame, G_ACTION_MIDI, true,
+ &m::mixer::mutex, a2.iValue, 0.0);
+ }
+
+ ch->hasActions = m::recorder::hasActions(ch->index);
+}
+
+/* -------------------------------------------------------------------------- */
+
+
+void recordSampleAction(SampleChannel* ch, int type, int frame_a, int frame_b)
+{
+ if (ch->mode == ChannelMode::SINGLE_PRESS) {
+ m::recorder::rec(ch->index, G_ACTION_KEYPRESS, frame_a);
+ m::recorder::rec(ch->index, G_ACTION_KEYREL, frame_b == 0 ? frame_a + G_DEFAULT_ACTION_SIZE : frame_b);
+ }
+ else
+ m::recorder::rec(ch->index, type, frame_a);
+
+ updateChannel(ch->guiChannel, /*refreshActionEditor=*/false);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void recordEnvelopeAction(Channel* ch, int type, int frame, float fValue)
+{
+ namespace mr = m::recorder;
+
+ if (!mr::hasActions(ch->index, type)) { // First action ever? Add actions at boundaries.
+ mr::rec(ch->index, type, 0, 0, 1.0);
+ mr::rec(ch->index, type, m::clock::getFramesInLoop() - 1, 0, 1.0);
+ }
+ mr::rec(ch->index, type, frame, 0, fValue);
+
+ updateChannel(ch->guiChannel, /*refreshActionEditor=*/false);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void deleteEnvelopeAction(Channel* ch, m::recorder::action a, bool moved)
+{
+ namespace mr = m::recorder;
+
+ /* Deleting first or last action: clear everything. Otherwise delete the
+ selected action only. */
+
+ if (!moved && (a.frame == 0 || a.frame == m::clock::getFramesInLoop() - 1))
+ mr::clearAction(ch->index, a.type);
+ else
+ mr::deleteAction(ch->index, a.frame, a.type, false, &m::mixer::mutex);
+
+ updateChannel(ch->guiChannel, /*refreshActionEditor=*/false);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void deleteSampleAction(SampleChannel* ch, m::recorder::action a1,
+ m::recorder::action a2)
+{
+ namespace mr = m::recorder;
+
+ /* if SINGLE_PRESS delete both the keypress and the keyrelease pair. */
+
+ if (ch->mode == ChannelMode::SINGLE_PRESS) {
+ mr::deleteAction(ch->index, a1.frame, G_ACTION_KEYPRESS, false, &m::mixer::mutex);
+ mr::deleteAction(ch->index, a2.frame, G_ACTION_KEYREL, false, &m::mixer::mutex);
+ }
+ else
+ mr::deleteAction(ch->index, a1.frame, a1.type, false, &m::mixer::mutex);
+
+ updateChannel(ch->guiChannel, /*refreshActionEditor=*/false);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+vector<m::recorder::Composite> getSampleActions(const SampleChannel* ch)
+{
+ namespace mr = m::recorder;
+
+ vector<mr::Composite> out;
+
+ mr::sortActions();
+ mr::forEachAction([&](const mr::action* a1)
+ {
+ /* Exclude:
+ - actions beyond clock::getFramesInLoop();
+ - actions that don't belong to channel ch;
+ - actions != G_ACTION_KEYPRESS, G_ACTION_KEYREL or G_ACTION_KILL;
+ - G_ACTION_KEYREL actions in a SINGLE_PRESS context. */
+
+ if (a1->frame > m::clock::getFramesInLoop() ||
+ a1->chan != ch->index ||
+ a1->type & ~(G_ACTION_KEYPRESS | G_ACTION_KEYREL | G_ACTION_KILL) ||
+ (ch->mode == ChannelMode::SINGLE_PRESS && a1->type == G_ACTION_KEYREL))
+ return;
+
+ mr::Composite cmp;
+ cmp.a1 = *a1;
+ cmp.a2.frame = -1;
+
+ /* If SINGLE_PRESS mode and the current action is G_ACTION_KEYPRESS, let's
+ fetch the corresponding G_ACTION_KEYREL. */
+
+ if (ch->mode == ChannelMode::SINGLE_PRESS && a1->type == G_ACTION_KEYPRESS) {
+ m::recorder::action* a2 = nullptr;
+ mr::getNextAction(ch->index, G_ACTION_KEYREL, a1->frame, &a2);
+ if (a2 != nullptr)
+ cmp.a2 = *a2;
+ }
+
+ out.push_back(cmp);
+ });
+
+ return out;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+vector<m::recorder::action> getEnvelopeActions(const Channel* ch, int type)
+{
+ namespace mr = m::recorder;
+
+ vector<mr::action> out;
+
+ mr::sortActions();
+ mr::forEachAction([&](const mr::action* a)
+ {
+ /* Exclude:
+ - actions beyond clock::getFramesInLoop();
+ - actions that don't belong to channel ch;
+ - actions with wrong type. */
+
+ if (a->frame > m::clock::getFramesInLoop() ||
+ a->chan != ch->index ||
+ a->type != type)
+ return;
+
+ out.push_back(*a);
+ });
+
+ return out;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void setVelocity(const Channel* ch, m::recorder::action a, int value)
+{
+ /* TODO - this is super ugly: delete the action and add a new one with the
+ modified values. This shit will go away as soon as we'll refactor m::recorder
+ for good. */
+
+ m::MidiEvent event = m::MidiEvent(a.iValue);
+ event.setVelocity(value);
+
+ m::recorder::deleteAction(ch->index, a.frame, G_ACTION_MIDI, true,
+ &m::mixer::mutex, a.iValue, 0.0);
+ m::recorder::rec(ch->index, G_ACTION_MIDI, a.frame, event.getRaw());
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+vector<m::recorder::Composite> getMidiActions(int chan)
+{
+ vector<m::recorder::Composite> out;
+
+ m::recorder::sortActions();
+
+ for (unsigned i=0; i<m::recorder::frames.size(); i++) {
+
+ if (m::recorder::frames.at(i) > m::clock::getFramesInLoop())
+ continue;
+
+ for (unsigned j=0; j<m::recorder::global.at(i).size(); j++) {
+
+ m::recorder::action* a1 = m::recorder::global.at(i).at(j);
+ m::recorder::action* a2 = nullptr;
+
+ m::MidiEvent a1midi(a1->iValue);
+
+ /* 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::recorder::getNextAction(chan, G_ACTION_MIDI, a1->frame, &a2,
+ m::MidiEvent(m::MidiEvent::NOTE_OFF, a1midi.getNote(), 0x0).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::
\ No newline at end of file
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef G_GLUE_RECORDER_H
+#define G_GLUE_RECORDER_H
+
+
+#include <vector>
+#include "../core/recorder.h"
+
+
+class SampleChannel;
+class MidiChannel;
+class geChannel;
+
+
+namespace giada {
+namespace c {
+namespace recorder
+{
+void clearAllActions(geChannel* gch);
+void clearVolumeActions(geChannel* gch);
+void clearStartStopActions(geChannel* gch);
+
+
+
+/* MOVE ALL THESE FUNCTIONS TO c::actionEditor*/
+
+
+bool midiActionCanFit(int chan, int note, int frame_a, int frame_b);
+bool sampleActionCanFit(const SampleChannel* ch, int frame_a, int frame_b);
+
+/* 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 velocity, int frame_a, int frame_b=0);
+
+void recordEnvelopeAction(Channel* ch, int type, int frame, float fValue);
+
+void recordSampleAction(SampleChannel* ch, int type, int frame_a, int frame_b=0);
+
+void setVelocity(const Channel* ch, m::recorder::action a, int value);
+
+/* getMidiActions
+Returns a list of Composite actions, ready to be displayed in a MIDI note
+editor as pairs of NoteOn+NoteOff. */
+
+std::vector<m::recorder::Composite> getMidiActions(int channel);
+
+std::vector<m::recorder::action> getEnvelopeActions(const Channel* ch, int type);
+
+/* getSampleActions
+Returns a list of Composite actions, ready to be displayed in a Sample Action
+Editor. If actions are not keypress+keyrelease combos, the second action in
+the Composite struct if left empty (with action2.frame = -1). */
+
+std::vector<m::recorder::Composite> getSampleActions(const SampleChannel* ch);
+
+void deleteMidiAction(MidiChannel* ch, m::recorder::action a1, m::recorder::action a2);
+
+void deleteSampleAction(SampleChannel* ch, m::recorder::action a1,
+ m::recorder::action a2);
+
+void deleteEnvelopeAction(Channel* ch, m::recorder::action a, bool moved);
+
+}}} // giada::c::recorder::
+
+#endif
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <cassert>
+#include <FL/Fl.H>
+#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;
+
+
+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<gdSampleEditor*>(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 (!m::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;
+ m::waveManager::createFromWave(ch->wave, a, b, &m_waveBuffer);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void paste(SampleChannel* ch, int a)
+{
+ if (!isWaveBufferFull()) {
+ gu_log("[sampleEditor::paste] Buffer is empty, nothing to paste\n");
+ return;
+ }
+
+ m::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)
+{
+ m::wfx::silence(*ch->wave, a, b);
+ gdSampleEditor* gdEditor = getSampleEditorWindow();
+ gdEditor->waveTools->waveform->refresh();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void fade(SampleChannel* ch, int a, int b, int type)
+{
+ m::wfx::fade(*ch->wave, a, b, type);
+ gdSampleEditor* gdEditor = getSampleEditorWindow();
+ gdEditor->waveTools->waveform->refresh();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void smoothEdges(SampleChannel* ch, int a, int b)
+{
+ m::wfx::smooth(*ch->wave, a, b);
+ gdSampleEditor* gdEditor = getSampleEditorWindow();
+ gdEditor->waveTools->waveform->refresh();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void reverse(SampleChannel* ch, int a, int b)
+{
+ m::wfx::reverse(*ch->wave, a, b);
+ gdSampleEditor* gdEditor = getSampleEditorWindow();
+ gdEditor->waveTools->waveform->refresh();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void normalizeHard(SampleChannel* ch, int a, int b)
+{
+ m::wfx::normalizeHard(*ch->wave, a, b);
+ gdSampleEditor* gdEditor = getSampleEditorWindow();
+ gdEditor->waveTools->waveform->refresh();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void trim(SampleChannel* ch, int a, int b)
+{
+ if (!m::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->trackerPreview = f;
+ gdSampleEditor* gdEditor = getSampleEditorWindow();
+ gdEditor->waveTools->waveform->redraw();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void setPreview(SampleChannel* ch, PreviewMode mode)
+{
+ ch->previewMode = mode;
+ gdSampleEditor* gdEditor = getSampleEditorWindow();
+ gdEditor->play->value(!gdEditor->play->value());
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void rewindPreview(SampleChannel* ch)
+{
+ geWaveform* waveform = getSampleEditorWindow()->waveTools->waveform;
+ if (waveform->isSelected() && ch->trackerPreview != waveform->getSelectionA())
+ setPlayHead(ch, waveform->getSelectionA());
+ else
+ setPlayHead(ch, 0);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void toNewChannel(SampleChannel* ch, int a, int b)
+{
+ SampleChannel* newCh = static_cast<SampleChannel*>(c::channel::addChannel(
+ ch->guiChannel->getColumnIndex(), ChannelType::SAMPLE, G_GUI_CHANNEL_H_1));
+
+ Wave* wave = nullptr;
+ m::waveManager::createFromWave(ch->wave, a, b, &wave);
+
+ newCh->pushWave(wave);
+ newCh->guiChannel->update();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+bool isWaveBufferFull()
+{
+ return m_waveBuffer != nullptr;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void shift(SampleChannel* ch, int offset)
+{
+ m::wfx::shift(*ch->wave, offset - ch->shift);
+ ch->shift = offset;
+ gdSampleEditor* gdEditor = getSampleEditorWindow();
+ gdEditor->shiftTool->refresh();
+ gdEditor->waveTools->waveform->refresh();
+}
+
+
+}}}; // giada::c::sampleEditor::
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef G_GLUE_SAMPLE_EDITOR_H
+#define G_GLUE_SAMPLE_EDITOR_H
+
+
+#include "../core/types.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, PreviewMode 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
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#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;
+
+
+#ifdef WITH_VST
+
+static void glue_fillPatchGlobalsPlugins__(vector <Plugin*>* host, vector<m::patch::plugin_t>* patch)
+{
+ using namespace giada::m;
+
+ for (unsigned i=0; i<host->size(); 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; k<numParams; k++)
+ ppl.params.push_back(pl->getParameter(k));
+ patch->push_back(ppl);
+ }
+}
+
+#endif
+
+
+/* -------------------------------------------------------------------------- */
+
+
+static void glue_fillPatchColumns__()
+{
+ using namespace giada::m;
+
+ for (unsigned i=0; i<G_MainWin->keyboard->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; k<gCol->countChannels(); k++) {
+ Channel *colChannel = gCol->getChannel(k);
+ for (unsigned j=0; j<mixer::channels.size(); j++) {
+ Channel *mixerChannel = mixer::channels.at(j);
+ if (colChannel == mixerChannel) {
+ pCol.channels.push_back(mixerChannel->index);
+ break;
+ }
+ }
+ }
+ patch::columns.push_back(pCol);
+ }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+static void glue_fillPatchChannels__(bool isProject)
+{
+ using namespace giada::m;
+
+ for (unsigned i=0; i<mixer::channels.size(); i++) {
+ mixer::channels.at(i)->writePatch(i, isProject);
+ }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+static void glue_fillPatchGlobals__(const string &name)
+{
+ using namespace giada::m;
+
+ 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)
+{
+ using namespace giada::m;
+
+ 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)
+{
+ using namespace giada::m;
+
+ 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
+ m::conf::patchPath = gu_dirname(fullPath);
+ browser->do_callback();
+ }
+ else
+ gdAlert("Unable to save the patch!");
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void glue_loadPatch(void* data)
+{
+ using namespace giada::m;
+
+ 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 = c::channel::addChannel(pch.column, static_cast<ChannelType>(pch.type), pch.size);
+ ch->readPatch(basePath, k);
+ }
+ browser->setStatusBar(steps);
+ k++;
+ }
+ }
+
+ /* Prepare Mixer. */
+
+ mh::updateSoloCount();
+ 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)
+{
+ using namespace giada::m;
+
+ 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 == ChannelType::MIDI)
+ continue;
+
+ const SampleChannel* sch = static_cast<const SampleChannel*>(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 = c::channel::loadChannel(static_cast<SampleChannel*>(browser->getChannel()),
+ fullPath);
+
+ if (res == G_RES_OK) {
+ m::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)
+{
+ using namespace giada::m;
+
+ 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<SampleChannel*>(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!");
+}
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#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
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <FL/Fl.H>
+#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();
+ }
+}
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#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
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <FL/Fl_Pixmap.H>
+#include <FL/fl_draw.H>
+#include <jansson.h>
+#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/ver.h"
+#include "../elems/basics/button.h"
+#include "../elems/basics/box.h"
+#include "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, 20, 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,
+ ver::getRtAudioVersion().c_str(),
+ ver::getRtMidiVersion().c_str(),
+ JANSSON_VERSION, ver::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();
+}
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#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
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <cassert>
+#include <string>
+#include <FL/Fl.H>
+#include <FL/fl_draw.H>
+#include "../../../utils/gui.h"
+#include "../../../utils/string.h"
+#include "../../../core/conf.h"
+#include "../../../core/const.h"
+#include "../../../core/clock.h"
+#include "../../../core/channel.h"
+#include "../../elems/actionEditor/gridTool.h"
+#include "../../elems/basics/scroll.h"
+#include "../../elems/basics/choice.h"
+#include "baseActionEditor.h"
+
+
+using std::string;
+
+
+namespace giada {
+namespace v
+{
+gdBaseActionEditor::gdBaseActionEditor(Channel* ch)
+: gdWindow (640, 284),
+ ch (ch),
+ ratio (G_DEFAULT_ZOOM_RATIO)
+{
+ using namespace giada::m;
+
+ if (conf::actionEditorW) {
+ resize(conf::actionEditorX, conf::actionEditorY, conf::actionEditorW, conf::actionEditorH);
+ ratio = conf::actionEditorZoom;
+ }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+gdBaseActionEditor::~gdBaseActionEditor()
+{
+ using namespace giada::m;
+
+ conf::actionEditorX = x();
+ conf::actionEditorY = y();
+ conf::actionEditorW = w();
+ conf::actionEditorH = h();
+ conf::actionEditorZoom = ratio;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void gdBaseActionEditor::cb_zoomIn(Fl_Widget *w, void *p) { ((gdBaseActionEditor*)p)->zoomIn(); }
+void gdBaseActionEditor::cb_zoomOut(Fl_Widget *w, void *p) { ((gdBaseActionEditor*)p)->zoomOut(); }
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void gdBaseActionEditor::computeWidth()
+{
+ fullWidth = frameToPixel(m::clock::getFramesInSeq());
+ loopWidth = frameToPixel(m::clock::getFramesInLoop());
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+Pixel gdBaseActionEditor::frameToPixel(Frame f) const
+{
+ return f / ratio;
+}
+
+
+Frame gdBaseActionEditor::pixelToFrame(Pixel p, bool snap) const
+{
+ return snap ? gridTool->getSnapFrame(p * ratio) : p * ratio;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void gdBaseActionEditor::zoomIn()
+{
+ if (ratio / 2 > MIN_RATIO) {
+ ratio /= 2;
+ rebuild();
+ centerViewportIn();
+ redraw();
+ }
+ else
+ ratio = MIN_RATIO;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void gdBaseActionEditor::zoomOut()
+{
+ if (ratio * 2 < MAX_RATIO) {
+ ratio *= 2;
+ rebuild();
+ centerViewportOut();
+ redraw();
+ }
+ else
+ ratio = MAX_RATIO;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void gdBaseActionEditor::centerViewportIn()
+{
+ Pixel sx = Fl::event_x() + (viewport->xposition() * 2);
+ viewport->scroll_to(sx, viewport->yposition());
+}
+
+
+void gdBaseActionEditor::centerViewportOut()
+{
+ Pixel sx = -((Fl::event_x() + viewport->xposition()) / 2) + viewport->xposition();
+ if (sx < 0) sx = 0;
+ viewport->scroll_to(sx, viewport->yposition());
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+int gdBaseActionEditor::getActionType() const
+{
+ 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;
+
+ assert(false);
+ return -1;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void gdBaseActionEditor::prepareWindow()
+{
+ gu_setFavicon(this);
+
+ string l = "Action Editor";
+ if (ch->name != "") l += " - " + ch->name;
+ copy_label(l.c_str());
+
+ set_non_modal();
+ size_range(640, 284);
+ resizable(viewport);
+
+ show();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+int gdBaseActionEditor::handle(int e)
+{
+ switch (e) {
+ case FL_MOUSEWHEEL:
+ Fl::event_dy() == -1 ? zoomIn() : zoomOut();
+ return 1;
+ default:
+ return Fl_Group::handle(e);
+ }
+}
+}} // giada::v::
\ No newline at end of file
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef GD_BASE_ACTION_EDITOR_H
+#define GD_BASE_ACTION_EDITOR_H
+
+
+#include "../../../core/types.h"
+#include "../window.h"
+
+
+class Channel;
+class geChoice;
+class geButton;
+class geScroll;
+
+
+namespace giada {
+namespace v
+{
+class geGridTool;
+
+
+class gdBaseActionEditor : public gdWindow
+{
+protected:
+
+ static constexpr Pixel RESIZER_BAR_H = 20;
+ static constexpr Pixel MIN_WIDGET_H = 10;
+ static constexpr float MIN_RATIO = 25.0f;
+ static constexpr float MAX_RATIO = 40000.0f;
+
+ gdBaseActionEditor(Channel* ch);
+
+ void zoomIn();
+ void zoomOut();
+ static void cb_zoomIn(Fl_Widget* w, void* p);
+ static void cb_zoomOut(Fl_Widget* w, void* p);
+
+ /* computeWidth
+ Computes total width, in pixel. */
+
+ void computeWidth();
+
+ void centerViewportIn();
+ void centerViewportOut();
+
+ void prepareWindow();
+
+public:
+
+ virtual ~gdBaseActionEditor();
+
+ /* rebuild
+ Forces all internal widgets to rebuild themselves. Used when refreshing the
+ whole Action Editor window. */
+
+ virtual void rebuild() = 0;
+
+ int handle(int e) override;
+
+ Pixel frameToPixel(Frame f) const;
+ Frame pixelToFrame(Pixel p, bool snap=true) const;
+ int getActionType() const;
+
+ geChoice* actionType;
+ geGridTool* gridTool;
+ geButton* zoomInBtn;
+ geButton* zoomOutBtn;
+ geScroll* viewport; // widget container
+
+ Channel* ch;
+
+ float ratio;
+ Pixel fullWidth; // Full widgets width, i.e. scaled-down full sequencer
+ Pixel loopWidth; // Loop width, i.e. scaled-down sequencer range
+};
+}} // giada::v::
+
+
+#endif
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <string>
+#include "../../../core/graphics.h"
+#include "../../../core/midiChannel.h"
+#include "../../elems/basics/scroll.h"
+#include "../../elems/basics/button.h"
+#include "../../elems/basics/resizerBar.h"
+#include "../../elems/basics/box.h"
+#include "../../elems/actionEditor/noteEditor.h"
+#include "../../elems/actionEditor/velocityEditor.h"
+#include "../../elems/actionEditor/pianoRoll.h"
+#include "../../elems/actionEditor/gridTool.h"
+#include "midiActionEditor.h"
+
+
+using std::string;
+
+
+namespace giada {
+namespace v
+{
+gdMidiActionEditor::gdMidiActionEditor(MidiChannel* ch)
+: gdBaseActionEditor(ch)
+{
+ computeWidth();
+
+ Fl_Group* upperArea = new Fl_Group(8, 8, w()-16, 20);
+
+ upperArea->begin();
+
+ gridTool = new geGridTool(8, 8);
+
+ geBox *b1 = new geBox(gridTool->x()+gridTool->w()+4, 8, 300, 20); // padding actionType - zoomButtons
+ zoomInBtn = new geButton(w()-8-40-4, 8, 20, 20, "", zoomInOff_xpm, zoomInOn_xpm);
+ zoomOutBtn = new geButton(w()-8-20, 8, 20, 20, "", zoomOutOff_xpm, zoomOutOn_xpm);
+
+ upperArea->end();
+ upperArea->resizable(b1);
+
+ zoomInBtn->callback(cb_zoomIn, (void*)this);
+ zoomOutBtn->callback(cb_zoomOut, (void*)this);
+
+ /* Main viewport: contains all widgets. */
+
+ viewport = new geScroll(8, 36, w()-16, h()-44);
+
+ ne = new geNoteEditor(viewport->x(), viewport->y(), this);
+ viewport->add(ne);
+ viewport->add(new geResizerBar(ne->x(), ne->y()+ne->h(), viewport->w(), RESIZER_BAR_H, MIN_WIDGET_H));
+
+ ve = new geVelocityEditor(viewport->x(), ne->y()+ne->h()+RESIZER_BAR_H, ch);
+ viewport->add(ve);
+ viewport->add(new geResizerBar(ve->x(), ve->y()+ve->h(), viewport->w(), RESIZER_BAR_H, MIN_WIDGET_H));
+
+ end();
+ prepareWindow();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void gdMidiActionEditor::rebuild()
+{
+ computeWidth();
+ ne->rebuild();
+ ve->rebuild();
+}
+}} // giada::v::
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef GD_MIDI_ACTION_EDITOR_H
+#define GD_MIDI_ACTION_EDITOR_H
+
+
+#include "baseActionEditor.h"
+
+
+class MidiChannel;
+
+
+namespace giada {
+namespace v
+{
+class geNoteEditor;
+class geVelocityEditor;
+
+
+class gdMidiActionEditor : public gdBaseActionEditor
+{
+private:
+
+ geNoteEditor* ne;
+ geVelocityEditor* ve;
+
+public:
+
+ gdMidiActionEditor(MidiChannel* ch);
+
+ void rebuild() override;
+};
+}} // giada::v::
+
+
+#endif
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <string>
+#include "../../../core/const.h"
+#include "../../../core/graphics.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/sampleActionEditor.h"
+#include "../../elems/actionEditor/envelopeEditor.h"
+#include "../../elems/actionEditor/gridTool.h"
+#include "sampleActionEditor.h"
+
+
+using std::string;
+
+
+namespace giada {
+namespace v
+{
+gdSampleActionEditor::gdSampleActionEditor(SampleChannel* ch)
+: gdBaseActionEditor(ch)
+{
+ computeWidth();
+
+ /* 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();
+
+ actionType = new geChoice(8, 8, 80, 20);
+ gridTool = new geGridTool(actionType->x()+actionType->w()+4, 8);
+ actionType->add("Key press");
+ actionType->add("Key release");
+ actionType->add("Kill chan");
+ actionType->value(0);
+
+ if (!canChangeActionType())
+ actionType->deactivate();
+
+ geBox* b1 = new geBox(gridTool->x()+gridTool->w()+4, 8, 300, 20); // padding actionType - zoomButtons
+ zoomInBtn = new geButton(w()-8-40-4, 8, 20, 20, "", zoomInOff_xpm, zoomInOn_xpm);
+ zoomOutBtn = new geButton(w()-8-20, 8, 20, 20, "", zoomOutOff_xpm, zoomOutOn_xpm);
+
+ upperArea->end();
+ upperArea->resizable(b1);
+
+ zoomInBtn->callback(cb_zoomIn, (void*)this);
+ zoomOutBtn->callback(cb_zoomOut, (void*)this);
+
+ /* Main viewport: contains all widgets. */
+
+ viewport = new geScroll(8, 36, w()-16, h()-44);
+
+ ac = new geSampleActionEditor(viewport->x(), viewport->y(), ch);
+ viewport->add(ac);
+ viewport->add(new geResizerBar(ac->x(), ac->y()+ac->h(), viewport->w(), RESIZER_BAR_H, MIN_WIDGET_H));
+
+ vc = new geEnvelopeEditor(viewport->x(), ac->y()+ac->h()+RESIZER_BAR_H, G_ACTION_VOLUME, "volume", ch);
+ viewport->add(vc);
+ viewport->add(new geResizerBar(vc->x(), vc->y()+vc->h(), viewport->w(), RESIZER_BAR_H, MIN_WIDGET_H));
+
+ end();
+ prepareWindow();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+bool gdSampleActionEditor::canChangeActionType()
+{
+ SampleChannel* sch = static_cast<SampleChannel*>(ch);
+ return sch->mode != ChannelMode::SINGLE_PRESS && !sch->isAnyLoopMode();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void gdSampleActionEditor::rebuild()
+{
+ canChangeActionType() ? actionType->activate() : actionType->deactivate();
+ computeWidth();
+ ac->rebuild();
+ vc->rebuild();
+}
+}} // giada::v::
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef GD_SAMPLE_ACTION_EDITOR_H
+#define GD_SAMPLE_ACTION_EDITOR_H
+
+
+#include "baseActionEditor.h"
+
+
+class SampleChannel;
+class geSampleActionEditor;
+class geEnvelopeEditor;
+
+
+namespace giada {
+namespace v
+{
+class gdSampleActionEditor : public gdBaseActionEditor
+{
+private:
+
+ geSampleActionEditor* ac;
+ geEnvelopeEditor* vc;
+
+ bool canChangeActionType();
+
+public:
+
+ gdSampleActionEditor(SampleChannel* ch);
+
+ void rebuild() override;
+};
+}} // giada::v::
+
+
+#endif
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <cstring>
+#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();
+}
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#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
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <cstring>
+#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<string> 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();
+}
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#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
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#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 = browser->getCurrentDir();
+ 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()
+{
+ browser->loadDir(gu_getUpDir(browser->getCurrentDir()));
+ 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);
+}
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#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
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#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());
+}
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#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
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#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());
+}
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#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
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#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);
+}
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#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
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#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;
+
+
+gdChannelNameInput::gdChannelNameInput(Channel* ch)
+: gdWindow(400, 64, "New channel name"),
+ m_ch (ch)
+{
+ using namespace giada::m;
+
+ 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->name.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()
+{
+ m::conf::nameX = x();
+ m::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()
+{
+ c::channel::setName(m_ch, m_name->value());
+ do_callback();
+}
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#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
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <FL/Fl_Tabs.H>
+#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();
+}
+
+#endif
\ No newline at end of file
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#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
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <FL/fl_draw.H>
+#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; i<totalFreq; i++) {
+ if (i % 6 == 0) {
+ body += "\n "; // add new line each 6 printed freqs AND on the first line (i % 0 != 0)
+ lines++;
+ }
+ body += gu_iToString( kernelAudio::getFreq(dev, i)) + " ";
+ }
+
+ text->copy_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() {}
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef GD_DEV_INFO_H
+#define GD_DEV_INFO_H
+
+
+#include <FL/Fl_Window.H>
+
+
+class geBox;
+class geButton;
+
+
+class gdDevInfo : public Fl_Window
+{
+private:
+
+ geBox *text;
+ geButton *close;
+
+public:
+
+ gdDevInfo(unsigned dev);
+ ~gdDevInfo();
+};
+
+#endif
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#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<char>(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);
+}
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef GD_KEYGRABBER_H
+#define GD_KEYGRABBER_H
+
+
+#include <FL/Fl.H>
+#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
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <FL/Fl.H>
+#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_MIN_GUI_WIDTH, G_MIN_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;
+}
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#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
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <FL/Fl.H>
+#include <FL/Fl_Window.H>
+#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;
+}
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef GD_WARNINGS_H
+#define GD_WARNINGS_H
+
+
+void gdAlert(const char *c);
+int gdConfirmWin(const char *title, const char *msg);
+
+
+#endif
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#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<gdMidiInputBase*>(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();
+}
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#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
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <FL/Fl_Pack.H>
+#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;
+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);
+
+ int extra = ch->type == ChannelType::SAMPLE ? 28 : 0;
+
+ Fl_Group* groupHeader = new Fl_Group(G_GUI_OUTER_MARGIN, G_GUI_OUTER_MARGIN, w(), 20 + extra);
+ 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);
+ veloAsVol = new geCheck(G_GUI_OUTER_MARGIN, enable->y()+enable->h()+G_GUI_OUTER_MARGIN , 120, G_GUI_UNIT,
+ "Velocity drives volume (one-shot only)");
+
+ groupHeader->resizable(nullptr);
+ groupHeader->end();
+
+ container = new geScroll(G_GUI_OUTER_MARGIN, groupHeader->y()+groupHeader->h()+G_GUI_OUTER_MARGIN,
+ w()-16, h()-72-extra);
+ 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);
+
+ if (ch->type == ChannelType::SAMPLE) {
+ veloAsVol->value(static_cast<SampleChannel*>(ch)->midiInVeloAsVol);
+ veloAsVol->callback(cb_veloAsVol, (void*)this);
+ }
+ else
+ veloAsVol->hide();
+
+ 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->midiInFilter == -1 ? 0 : ch->midiInFilter + 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 == ChannelType::SAMPLE) {
+ new geMidiLearner(0, 0, LEARNER_WIDTH, "pitch", cb_learn,
+ &(static_cast<SampleChannel*>(ch))->midiInPitch, ch);
+ new geMidiLearner(0, 0, LEARNER_WIDTH, "read actions", cb_learn,
+ &(static_cast<SampleChannel*>(ch))->midiInReadActions, ch);
+ }
+
+ pack->end();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+#ifdef WITH_VST
+
+void gdMidiInputChannel::addPluginLearners()
+{
+ vector<Plugin*>* plugins = pluginHost::getStack(pluginHost::CHANNEL, ch);
+ for (unsigned i=0; i<plugins->size(); 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; k<plugin->getNumParameters(); 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_veloAsVol(Fl_Widget* w, void* p) { ((gdMidiInputChannel*)p)->cb_veloAsVol(); }
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void gdMidiInputChannel::cb_enable()
+{
+ ch->midiIn = enable->value();
+ enable->value() ? channel->activate() : channel->deactivate();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void gdMidiInputChannel::cb_veloAsVol()
+{
+ static_cast<SampleChannel*>(ch)->midiInVeloAsVol = veloAsVol->value();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void gdMidiInputChannel::cb_setChannel()
+{
+ ch->midiInFilter = channel->value() == 0 ? -1 : channel->value() - 1;
+ gu_log("[gdMidiInputChannel] Set MIDI channel to %d\n", ch->midiInFilter);
+}
+
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#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;
+ geCheck* veloAsVol;
+ geChoice* channel;
+
+ static void cb_enable(Fl_Widget* w, void* p);
+ static void cb_setChannel(Fl_Widget* w, void* p);
+ static void cb_veloAsVol(Fl_Widget* w, void* p);
+ void cb_enable();
+ void cb_setChannel();
+ void cb_veloAsVol();
+
+ void addChannelLearners();
+
+#ifdef WITH_VST
+
+ void addPluginLearners();
+
+#endif
+
+public:
+
+ gdMidiInputChannel(Channel* ch);
+ ~gdMidiInputChannel();
+};
+
+
+#endif
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <FL/Fl_Pack.H>
+#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;
+}
+
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#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
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#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());
+}
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef GD_MIDI_OUTPUT_BASE_H
+#define GD_MIDI_OUTPUT_BASE_H
+
+
+#include <FL/Fl.H>
+#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
--- /dev/null
+
+/* -----------------------------------------------------------------------------
+ *
+, 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#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();
+}
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#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
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#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();
+}
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#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
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#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
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifdef WITH_VST
+
+#ifndef GD_PLUGIN_CHOOSER_H
+#define GD_PLUGIN_CHOOSER_H
+
+
+#include <FL/Fl.H>
+#include <FL/Fl_Scroll.H>
+#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
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifdef WITH_VST
+
+
+#include <string>
+#include <FL/Fl_Scroll.H>
+#include "../../utils/gui.h"
+#include "../../core/conf.h"
+#include "../../core/const.h"
+#include "../../core/pluginHost.h"
+#include "../../core/channel.h"
+#include "../../utils/string.h"
+#include "../elems/basics/boxtypes.h"
+#include "../elems/basics/button.h"
+#include "../elems/basics/statusButton.h"
+#include "../elems/mainWindow/mainIO.h"
+#include "../elems/mainWindow/keyboard/channel.h"
+#include "../elems/plugin/pluginElement.h"
+#include "pluginChooser.h"
+#include "gd_mainWindow.h"
+#include "pluginList.h"
+
+
+extern gdMainWindow* G_MainWin;
+
+
+using std::string;
+using namespace giada;
+
+
+gdPluginList::gdPluginList(int stackType, Channel* ch)
+ : gdWindow(468, 204), ch(ch), stackType(stackType)
+{
+ using namespace giada::m;
+
+ 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()
+{
+ m::conf::pluginListX = x();
+ m::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 = static_cast<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, it does
+ not matter. */
+
+ ((gdPluginList*)p)->refreshList();
+ ((gdPluginList*)p)->redraw();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void gdPluginList::cb_addPlugin()
+{
+ using namespace giada::m;
+
+ /* 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()
+{
+ using namespace giada::m;
+
+ /* 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 (i<numPlugins) {
+ Plugin* plugin = pluginHost::getPluginByIndex(i, stackType, ch);
+ gePluginElement* gdpe = new gePluginElement(this, plugin, list->x(),
+ list->y()-list->yposition()+(i*24), 800);
+ list->add(gdpe);
+ 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();
+ }
+}
+
+
+#endif // #ifdef WITH_VST
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifdef WITH_VST
+
+#ifndef GD_PLUGINLIST_H
+#define GD_PLUGINLIST_H
+
+
+#include "window.h"
+
+
+class Fl_Scroll;
+class Channel;
+class geButton;
+
+
+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();
+};
+
+
+#endif
+
+#endif // #ifdef WITH_VST
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifdef WITH_VST
+
+
+#include <FL/fl_draw.H>
+#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; i<numParams; i++) {
+ int py = m_list->y() + (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<gePluginParameter*>(m_list->child(index))->update(changeSlider);
+}
+
+
+void gdPluginWindow::updateParameters(bool changeSlider)
+{
+ for (int i=0; i<m_plugin->getNumParameters(); i++) {
+ static_cast<gePluginParameter*>(m_list->child(i))->update(changeSlider);
+ }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+int gdPluginWindow::getLabelWidth() const
+{
+ int width = 0;
+ int numParams = m_plugin->getNumParameters();
+ for (int i=0; i<numParams; i++) {
+ int wl = 0, hl = 0;
+ fl_measure(m_plugin->getParameterName(i).c_str(), wl, hl);
+ if (wl > width)
+ width = wl;
+ }
+ return width;
+}
+
+
+#endif // #ifdef WITH_VST
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#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
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifdef WITH_VST
+
+
+#include <FL/x.H>
+#include "../../utils/log.h"
+#include "../../utils/gui.h"
+#include "../../core/pluginHost.h"
+#include "../../core/plugin.h"
+#include "../../core/const.h"
+#include "pluginWindowGUI.h"
+
+#ifdef G_OS_MAC
+#import "../../utils/cocoa.h" // objective-c
+#endif
+
+
+using namespace giada::m;
+
+
+gdPluginWindowGUI::gdPluginWindowGUI(Plugin* plugin)
+#ifdef G_OS_MAC
+ : gdWindow(Fl::w(), Fl::h()), m_plugin(plugin)
+#else
+ : gdWindow(320, 200), m_plugin(plugin)
+#endif
+{
+ show();
+
+#if defined(G_OS_LINUX) || defined(G_OS_MAC)
+
+ /* Fl_Window::show() is not guaranteed to show and draw the window on all
+ platforms immediately. Instead this is done in the background; particularly on
+ X11 it will take a few messages (client server roundtrips) to display the
+ window. Usually this small delay doesn't matter, but in some cases you may
+ want to have the window instantiated and displayed synchronously. Currently
+ (as of FLTK 1.3.4) this method has an effect on X11 and Mac OS.
+
+ http://www.fltk.org/doc-1.3/classFl__Window.html#aafbec14ca8ff8abdaff77a35ebb23dd8 */
+
+ wait_for_expose();
+ Fl::flush();
+
+#endif
+
+ gu_log("[gdPluginWindowGUI] opening GUI, this=%p, xid=%p\n",
+ (void*) this, (void*) fl_xid(this));
+
+#ifdef G_OS_MAC
+
+ void* cocoaWindow = (void*) fl_xid(this);
+ m_plugin->showEditor(cocoa_getViewFromWindow(cocoaWindow));
+
+#else
+
+ m_plugin->showEditor((void*) fl_xid(this));
+
+ int pluginW = m_plugin->getEditorW();
+ int pluginH = m_plugin->getEditorH();
+
+ resize((Fl::w() - pluginW) / 2, (Fl::h() - pluginH) / 2, pluginW, pluginH);
+
+
+#endif
+
+ Fl::add_timeout(G_GUI_PLUGIN_RATE, cb_refresh, (void*) this);
+
+ copy_label(m_plugin->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);
+ m_plugin->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
--- /dev/null
+
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifdef WITH_VST
+
+
+#ifndef GD_PLUGIN_WINDOW_GUI_H
+#define GD_PLUGIN_WINDOW_GUI_H
+
+
+#include <FL/Fl.H>
+#include <FL/Fl_Window.H>
+#include "window.h"
+#if defined(__APPLE__)
+ #include <Carbon/Carbon.h>
+#endif
+
+
+class Plugin;
+
+
+class gdPluginWindowGUI : public gdWindow
+{
+private:
+
+ Plugin* m_plugin;
+
+ 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* p);
+ ~gdPluginWindowGUI();
+};
+
+
+#endif // include guard
+
+#endif // #ifdef WITH_VST
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <cmath>
+#include <FL/Fl.H>
+#include <FL/Fl_Group.H>
+//#include <FL/fl_draw.H>
+#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;
+
+
+gdSampleEditor::gdSampleEditor(SampleChannel* ch)
+ : gdWindow(640, 480),
+ ch(ch)
+{
+ using namespace giada::m;
+
+ 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->name.c_str());
+
+ size_range(720, 480);
+ if (conf::sampleEditorX)
+ resize(conf::sampleEditorX, conf::sampleEditorY, conf::sampleEditorW,
+ conf::sampleEditorH);
+
+ show();
+}
+
+
+
+/* -------------------------------------------------------------------------- */
+
+
+gdSampleEditor::~gdSampleEditor()
+{
+ m::conf::sampleEditorX = x();
+ m::conf::sampleEditorY = y();
+ m::conf::sampleEditorW = w();
+ m::conf::sampleEditorH = h();
+ m::conf::sampleEditorGridVal = atoi(grid->text());
+ m::conf::sampleEditorGridOn = snap->value();
+ c::sampleEditor::setPreview(ch, PreviewMode::NONE);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+Fl_Group* gdSampleEditor::createUpperBar()
+{
+ using namespace giada::m;
+
+ 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->onPreviewEnd = [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()
+{
+ using namespace giada::c;
+
+ if (play->value())
+ sampleEditor::setPreview(ch, PreviewMode::NONE);
+ else
+ sampleEditor::setPreview(ch, loop->value() ? PreviewMode::LOOP : PreviewMode::NORMAL);
+}
+
+
+void gdSampleEditor::cb_rewindPreview()
+{
+ c::sampleEditor::rewindPreview(ch);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void gdSampleEditor::cb_reload()
+{
+ using namespace giada::c;
+
+ /* TODO - move to glue::sampleEditor */
+ if (!gdConfirmWin("Warning", "Reload sample: are you sure?"))
+ return;
+
+ if (channel::loadChannel(ch, ch->wave->getPath()) != G_RES_OK)
+ return;
+
+ channel::setBoost(ch, G_DEFAULT_BOOST);
+ channel::setPitch(ch, G_DEFAULT_PITCH);
+ channel::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());
+}
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#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
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#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; i<subWindows.size(); i++)
+ delete subWindows.at(i);
+ subWindows.clear();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+/* this is the default callback of each window, fired when the user closes
+ * the window with the 'x'. Watch out: is the parent that calls delSubWIndow */
+
+void gdWindow::cb_closeChild(Fl_Widget *v, void *p)
+{
+ gdWindow *child = (gdWindow*) v;
+ if (child->getParent() != nullptr)
+ (child->getParent())->delSubWindow(child);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void gdWindow::addSubWindow(gdWindow *w)
+{
+ /** TODO - useless: delete ---------------------------------------- */
+ for (unsigned i=0; i<subWindows.size(); i++)
+ if (w->getId() == 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; i<subWindows.size(); i++)
+ if (w->getId() == 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; i<subWindows.size(); i++)
+ if (subWindows.at(i)->getId() == 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; i<subWindows.size(); i++)
+ gu_log("[gdWindow] %p (id=%d)\n", (void*)subWindows.at(i), subWindows.at(i)->getId());
+ gu_log("----\n");
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+gdWindow *gdWindow::getParent()
+{
+ return parent;
+}
+
+
+void gdWindow::setParent(gdWindow *w)
+{
+ parent = w;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+bool gdWindow::hasWindow(int id)
+{
+ for (unsigned i=0; i<subWindows.size(); i++)
+ if (id == subWindows.at(i)->getId())
+ return true;
+ return false;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+gdWindow *gdWindow::getChild(int id)
+{
+ for (unsigned i=0; i<subWindows.size(); i++)
+ if (id == subWindows.at(i)->getId())
+ return subWindows.at(i);
+ return nullptr;
+}
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef GD_WINDOW_H
+#define GD_WINDOW_H
+
+
+#include <vector>
+#include <FL/Fl_Double_Window.H>
+
+
+/* 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<gdWindow*> 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
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <FL/Fl.H>
+#include <FL/fl_draw.H>
+#include "baseAction.h"
+
+
+namespace giada {
+namespace v
+{
+geBaseAction::geBaseAction(Pixel X, Pixel Y, Pixel W, Pixel H, bool resizable,
+ giada::m::recorder::action a1, giada::m::recorder::action a2)
+: Fl_Box (X, Y, W, H),
+ m_resizable(resizable),
+ onRightEdge(false),
+ onLeftEdge (false),
+ hovered (false),
+ altered (false),
+ pick (0),
+ a1 (a1),
+ a2 (a2)
+{
+ if (w() < MIN_WIDTH)
+ size(MIN_WIDTH, h());
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+int geBaseAction::handle(int e)
+{
+ switch (e) {
+ case FL_ENTER: {
+ hovered = true;
+ redraw();
+ return 1;
+ }
+ case FL_LEAVE: {
+ fl_cursor(FL_CURSOR_DEFAULT, FL_WHITE, FL_BLACK);
+ hovered = false;
+ redraw();
+ return 1;
+ }
+ case FL_MOVE: {
+ if (m_resizable) {
+ 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);
+ }
+ return 1;
+ }
+ default:
+ return Fl_Widget::handle(e);
+ }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void geBaseAction::setLeftEdge(Pixel p)
+{
+ resize(p, y(), x() - p + w(), h());
+ if (w() < MIN_WIDTH)
+ size(MIN_WIDTH, h());
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void geBaseAction::setRightEdge(Pixel p)
+{
+ size(p, h());
+ if (w() < MIN_WIDTH)
+ size(MIN_WIDTH, h());
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void geBaseAction::setPosition(Pixel p)
+{
+ position(p, y());
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+bool geBaseAction::isOnEdges() const
+{
+ return onLeftEdge || onRightEdge;
+}
+}} // giada::v::
\ No newline at end of file
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef GE_BASE_ACTION_H
+#define GE_BASE_ACTION_H
+
+
+#include <FL/Fl_Box.H>
+#include "../../../core/recorder.h"
+#include "../../../core/types.h"
+
+
+namespace giada {
+namespace v
+{
+class geBaseAction : public Fl_Box
+{
+private:
+
+ bool m_resizable;
+
+public:
+
+ static const Pixel MIN_WIDTH = 12;
+ static const Pixel HANDLE_WIDTH = 6;
+
+ geBaseAction(Pixel x, Pixel y, Pixel w, Pixel h, bool resizable,
+ m::recorder::action a1, m::recorder::action a2);
+
+ int handle(int e) override;
+
+ bool isOnEdges() const;
+
+ /* setLeftEdge/setRightEdge
+ Set new left/right edges position, relative range. */
+
+ void setLeftEdge(Pixel p);
+ void setRightEdge(Pixel p);
+
+ void setPosition(Pixel p);
+
+ bool onRightEdge;
+ bool onLeftEdge;
+ bool hovered;
+ bool altered;
+ Pixel pick;
+
+ m::recorder::action a1;
+ m::recorder::action a2;
+};
+}} // giada::v::
+
+#endif
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <FL/Fl.H>
+#include <FL/fl_draw.H>
+#include "../../../core/const.h"
+#include "../../../core/clock.h"
+#include "../../dialogs/actionEditor/baseActionEditor.h"
+#include "gridTool.h"
+#include "baseAction.h"
+#include "baseActionEditor.h"
+
+
+namespace giada {
+namespace v
+{
+geBaseActionEditor::geBaseActionEditor(Pixel x, Pixel y, Pixel w, Pixel h, Channel* ch)
+: Fl_Group(x, y, w, h),
+ m_ch (ch),
+ m_base (static_cast<gdBaseActionEditor*>(window())),
+ m_action(nullptr)
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+geBaseAction* geBaseActionEditor::getActionAtCursor() const
+{
+ for (int i=0; i<children(); i++) {
+ geBaseAction* a = static_cast<geBaseAction*>(child(i));
+ if (a->hovered)
+ return a;
+ }
+ return nullptr;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void geBaseActionEditor::baseDraw(bool clear) const
+{
+ /* Clear the screen. */
+
+ if (clear)
+ fl_rectf(x(), y(), w(), h(), G_COLOR_GREY_1);
+
+ /* Draw the outer container. */
+
+ fl_color(G_COLOR_GREY_4);
+ fl_rect(x(), y(), w(), h());
+
+ /* Draw grid, beats and bars. A grid set to 1 has a cell size == beat, so
+ painting it is useless. */
+
+ if (m_base->gridTool->getValue() > 1) {
+ fl_color(G_COLOR_GREY_3);
+ drawVerticals(m_base->gridTool->getCellSize());
+ }
+
+ fl_color(G_COLOR_GREY_4);
+ drawVerticals(m::clock::getFramesInBeat());
+
+ fl_color(G_COLOR_LIGHT_1);
+ drawVerticals(m::clock::getFramesInBar());
+
+ /* Cover unused area. Avoid drawing cover if width == 0 (i.e. beats are 32). */
+
+ Pixel coverWidth = m_base->fullWidth - m_base->loopWidth;
+ if (coverWidth != 0)
+ fl_rectf(m_base->loopWidth+x(), y()+1, coverWidth, h()-2, G_COLOR_GREY_4);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void geBaseActionEditor::drawVerticals(int steps) const
+{
+ /* Start drawing from steps, not from 0. The zero-th element is always
+ graphically useless. */
+ for (Frame i=steps; i<m::clock::getFramesInLoop(); i+=steps) {
+ Pixel p = m_base->frameToPixel(i) + x();
+ fl_line(p, y()+1, p, y()+h()-2);
+ }
+}
+
+/* -------------------------------------------------------------------------- */
+
+
+int geBaseActionEditor::handle(int e)
+{
+ switch (e) {
+ case FL_PUSH:
+ return push();
+ case FL_DRAG:
+ return drag();
+ case FL_RELEASE:
+ fl_cursor(FL_CURSOR_DEFAULT, FL_WHITE, FL_BLACK); // Make sure cursor returns normal
+ return release();
+ default:
+ return Fl_Group::handle(e);
+ }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+int geBaseActionEditor::push()
+{
+ m_action = getActionAtCursor();
+
+ if (Fl::event_button1()) { // Left button
+ if (m_action == nullptr) { // No action under cursor: add a new one
+ if (Fl::event_x() < m_base->loopWidth) // Avoid click on grey area
+ onAddAction();
+ }
+ else // Prepare for dragging
+ m_action->pick = Fl::event_x() - m_action->x();
+ }
+ else
+ if (Fl::event_button3()) { // Right button
+ if (m_action != nullptr) {
+ onDeleteAction();
+ m_action = nullptr;
+ }
+ }
+ return 1;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+int geBaseActionEditor::drag()
+{
+ if (m_action == nullptr)
+ return 0;
+ if (m_action->isOnEdges())
+ onResizeAction();
+ else
+ onMoveAction();
+ m_action->altered = true;
+ redraw();
+ return 1;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+int geBaseActionEditor::release()
+{
+ int ret = 0;
+ if (m_action != nullptr && m_action->altered) {
+ onRefreshAction();
+ ret = 1;
+ }
+ m_action = nullptr;
+ return ret;
+}
+}} // giada::v::
\ No newline at end of file
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef GE_BASE_ACTION_EDITOR_H
+#define GE_BASE_ACTION_EDITOR_H
+
+
+#include <FL/Fl_Group.H>
+
+
+class Channel;
+
+
+namespace giada {
+namespace v
+{
+class gdBaseActionEditor;
+class geBaseAction;
+
+class geBaseActionEditor : public Fl_Group
+{
+private:
+
+ /* drawVerticals
+ Draws generic vertical lines (beats, bars, grid lines...). */
+
+ void drawVerticals(int steps) const;
+
+ int push();
+ int drag();
+ int release();
+
+protected:
+
+ Channel* m_ch;
+
+ gdBaseActionEditor* m_base;
+
+ /* m_action
+ Selected action. Used while dragging. */
+
+ geBaseAction* m_action;
+
+ /* baseDraw
+ Draws basic things like borders and grids. Optional background clear. */
+
+ void baseDraw(bool clear=true) const;
+
+ virtual void onAddAction() = 0;
+ virtual void onDeleteAction() = 0;
+ virtual void onMoveAction() = 0;
+ virtual void onResizeAction() = 0;
+ virtual void onRefreshAction() = 0;
+
+public:
+
+ geBaseActionEditor(Pixel x, Pixel y, Pixel w, Pixel h, Channel* ch);
+
+ /* updateActions
+ Rebuild the actions widgets from scratch. */
+
+ virtual void rebuild() = 0;
+
+ /* handle
+ Override base FL_Group events. */
+
+ int handle(int e) override;
+
+ /* getActionAtCursor
+ Returns the action under the mouse. nullptr if nothing found. Why not using
+ Fl::belowmouse? It would require a boring dynamic_cast. */
+
+ geBaseAction* getActionAtCursor() const;
+};
+}} // giada::v::
+
+
+#endif
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <FL/Fl.H>
+#include <FL/fl_draw.H>
+#include "../../../utils/log.h"
+#include "../../../utils/math.h"
+#include "../../../core/const.h"
+#include "../../../core/conf.h"
+#include "../../../core/sampleChannel.h"
+#include "../../../glue/recorder.h"
+#include "../../dialogs/actionEditor/baseActionEditor.h"
+#include "envelopePoint.h"
+#include "envelopeEditor.h"
+
+
+using std::vector;
+
+
+namespace giada {
+namespace v
+{
+geEnvelopeEditor::geEnvelopeEditor(Pixel x, Pixel y, int actionType, const char* l,
+ SampleChannel* ch)
+: geBaseActionEditor(x, y, 200, m::conf::envelopeEditorH, ch),
+ m_actionType (actionType)
+{
+ copy_label(l);
+ rebuild();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+geEnvelopeEditor::~geEnvelopeEditor()
+{
+ m::conf::envelopeEditorH = h();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void geEnvelopeEditor::draw()
+{
+ baseDraw();
+
+ /* Print label. */
+
+ fl_color(G_COLOR_GREY_4);
+ fl_font(FL_HELVETICA, G_GUI_FONT_SIZE_BASE);
+ fl_draw(label(), x()+4, y(), w(), h(), (Fl_Align) (FL_ALIGN_LEFT));
+
+ if (children() == 0)
+ return;
+
+ Pixel side = geEnvelopePoint::SIDE / 2;
+
+ Pixel x1 = child(0)->x() + side;
+ Pixel y1 = child(0)->y() + side;
+ Pixel x2 = 0;
+ Pixel y2 = 0;
+
+ /* For each point:
+ - paint the connecting line with the next one;
+ - reposition it on the y axis, only if there's no point selected (dragged
+ around). */
+
+ for (int i=0; i<children(); i++) {
+ geEnvelopePoint* p = static_cast<geEnvelopePoint*>(child(i));
+ if (m_action == nullptr)
+ p->position(p->x(), valueToY(p->a1.fValue));
+ if (i > 0) {
+ x2 = p->x() + side;
+ y2 = p->y() + side;
+ fl_line(x1, y1, x2, y2);
+ x1 = x2;
+ y1 = y2;
+ }
+ }
+
+ draw_children();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void geEnvelopeEditor::rebuild()
+{
+ namespace mr = m::recorder;
+ namespace cr = c::recorder;
+
+ /* Remove all existing actions and set a new width, according to the current
+ zoom level. */
+
+ clear();
+ size(m_base->fullWidth, h());
+
+ vector<mr::action> actions = cr::getEnvelopeActions(m_ch, m_actionType);
+
+ for (mr::action a : actions) {
+ gu_log("[geEnvelopeEditor::rebuild] Action %d\n", a.frame);
+ add(new geEnvelopePoint(frameToX(a.frame), valueToY(a.fValue), a));
+ }
+
+ resizable(nullptr);
+
+ redraw();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+bool geEnvelopeEditor::isFirstPoint() const
+{
+ return find(m_action) == 0;
+}
+
+
+bool geEnvelopeEditor::isLastPoint() const
+{
+ return find(m_action) == children() - 1;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+Pixel geEnvelopeEditor::frameToX(Frame frame) const
+{
+ return x() + m_base->frameToPixel(frame) - (geEnvelopePoint::SIDE / 2);
+}
+
+
+Pixel geEnvelopeEditor::valueToY(float value) const
+{
+ return u::math::map<float, Pixel>(value, 0.0, 1.0, y() + (h() - geEnvelopePoint::SIDE), y());
+}
+
+
+float geEnvelopeEditor::yToValue(Pixel pixel) const
+{
+ return u::math::map<Pixel, float>(pixel, h() - geEnvelopePoint::SIDE, 0, 0.0, 1.0);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void geEnvelopeEditor::onAddAction()
+{
+ Frame f = m_base->pixelToFrame(Fl::event_x() - x());
+ float v = yToValue(Fl::event_y() - y());
+ c::recorder::recordEnvelopeAction(m_ch, m_actionType, f, v);
+ rebuild();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void geEnvelopeEditor::onDeleteAction()
+{
+ c::recorder::deleteEnvelopeAction(m_ch, m_action->a1, /*moved=*/false);
+ rebuild();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void geEnvelopeEditor::onMoveAction()
+{
+ Pixel side = geEnvelopePoint::SIDE / 2;
+ Pixel ex = Fl::event_x() - side;
+ Pixel ey = Fl::event_y() - side;
+
+ Pixel x1 = x() - side;
+ Pixel x2 = m_base->loopWidth + x() - side;
+ Pixel y1 = y();
+ Pixel y2 = y() + h() - geEnvelopePoint::SIDE;
+
+ /* x-axis constraints. */
+ if (isFirstPoint() || ex < x1) ex = x1;
+ else if (isLastPoint() || ex > x2) ex = x2;
+
+ /* y-axis constraints. */
+ if (ey < y1) ey = y1; else if (ey > y2) ey = y2;
+
+ m_action->position(ex, ey);
+ redraw();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void geEnvelopeEditor::onRefreshAction()
+{
+ Frame f = m_base->pixelToFrame((m_action->x() - x()) + geEnvelopePoint::SIDE / 2);
+ float v = yToValue(m_action->y() - y());
+ c::recorder::deleteEnvelopeAction(m_ch, m_action->a1, /*moved=*/true);
+ c::recorder::recordEnvelopeAction(m_ch, m_actionType, f, v);
+ rebuild();
+}
+}} // giada::v::
\ No newline at end of file
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef GE_ENVELOPE_EDITOR_H
+#define GE_ENVELOPE_EDITOR_H
+
+
+#include "baseActionEditor.h"
+
+
+class SampleChannel;
+
+
+namespace giada {
+namespace v
+{
+class geEnvelopePoint;
+
+
+class geEnvelopeEditor : public geBaseActionEditor
+{
+private:
+
+ /* m_actionType
+ What type of action this envelope editor is dealing with. */
+
+ int m_actionType;
+
+ void onAddAction() override;
+ void onDeleteAction() override;
+ void onMoveAction() override;
+ void onResizeAction() override{}; // Nothing to do here
+ void onRefreshAction() override;
+
+ Pixel frameToX(Frame frame) const;
+ Pixel valueToY(float value) const;
+ float yToValue(Pixel pixel) const;
+
+ bool isFirstPoint() const;
+ bool isLastPoint() const;
+
+public:
+
+ geEnvelopeEditor(Pixel x, Pixel y, int actionType, const char* l, SampleChannel* ch);
+ ~geEnvelopeEditor();
+
+ void draw() override;
+
+ void rebuild() override;
+};
+}} // giada::v::
+
+#endif
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <FL/fl_draw.H>
+#include "../../../core/const.h"
+#include "envelopePoint.h"
+
+
+namespace giada {
+namespace v
+{
+geEnvelopePoint::geEnvelopePoint(Pixel X, Pixel Y, m::recorder::action a)
+ : geBaseAction(X, Y, SIDE, SIDE, /*resizable=*/false, a, {})
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void geEnvelopePoint::draw()
+{
+ fl_rectf(x(), y(), w(), h(), hovered ? G_COLOR_LIGHT_2 : G_COLOR_LIGHT_1);
+}
+}} // giada::v::
\ No newline at end of file
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef GE_ENVELOPE_POINT_H
+#define GE_ENVELOPE_POINT_H
+
+
+#include "../../../core/recorder.h"
+#include "baseAction.h"
+
+
+namespace giada {
+namespace v
+{
+class geEnvelopePoint : public geBaseAction
+{
+public:
+
+ static const Pixel SIDE = 12;
+
+ geEnvelopePoint(Pixel x, Pixel y, m::recorder::action a);
+
+ void draw() override;
+};
+}} // giada::v::
+
+
+#endif
--- /dev/null
+/* -----------------------------------------------------------------------------
+*
+* 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
+* <http://www.gnu.org/licenses/>.
+*
+* --------------------------------------------------------------------------- */
+
+
+#include <FL/Fl_Double_Window.H>
+#include "../../../core/conf.h"
+#include "../../../core/clock.h"
+#include "../../../utils/math.h"
+#include "../basics/choice.h"
+#include "../basics/check.h"
+#include "gridTool.h"
+
+
+namespace giada {
+namespace v
+{
+geGridTool::geGridTool(Pixel x, Pixel y)
+: Fl_Group(x, y, 80, 20)
+{
+ 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(gridType->x() + gridType->w() + 4, y+4, 12, 12);
+
+ gridType->value(m::conf::actionEditorGridVal);
+ active->value(m::conf::actionEditorGridOn);
+
+ end();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+geGridTool::~geGridTool()
+{
+ m::conf::actionEditorGridVal = gridType->value();
+ m::conf::actionEditorGridOn = active->value();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void geGridTool::cb_changeType(Fl_Widget *w, void *p) { ((geGridTool*)p)->cb_changeType(); }
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void geGridTool::cb_changeType()
+{
+ window()->redraw();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+bool geGridTool::isOn() const
+{
+ return active->value();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+int geGridTool::getValue() const
+{
+ 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;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+Frame geGridTool::getSnapFrame(Frame v) const
+{
+ if (!isOn())
+ return v;
+ return u::math::quantize(v, getCellSize());
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+Frame geGridTool::getCellSize() const
+{
+ return m::clock::getFramesInBeat() / getValue();
+}
+}} // giada::v::
\ No newline at end of file
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef GE_GRID_TOOL_H
+#define GE_GRID_TOOL_H
+
+
+#include <FL/Fl_Group.H>
+#include "../../../core/types.h"
+
+
+class geChoice;
+class geCheck;
+
+
+namespace giada {
+namespace v
+{
+class geGridTool : public Fl_Group
+{
+private:
+
+ geChoice* gridType;
+ geCheck* active;
+
+ static void cb_changeType(Fl_Widget* w, void* p);
+ inline void cb_changeType();
+
+public:
+
+ geGridTool(Pixel x, Pixel y);
+ ~geGridTool();
+
+ int getValue() const;
+ bool isOn() const;
+
+ Frame getSnapFrame(Frame f) const;
+
+ /* getCellSize
+ Returns the size in frames of a single cell of the grid. */
+
+ Frame getCellSize() const;
+
+};
+}} // giada::v::
+
+
+#endif
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <FL/Fl.H>
+#include "../../../core/const.h"
+#include "../../../core/conf.h"
+#include "../../../core/midiChannel.h"
+#include "../../dialogs/actionEditor/midiActionEditor.h"
+#include "pianoRoll.h"
+#include "noteEditor.h"
+
+
+namespace giada {
+namespace v
+{
+geNoteEditor::geNoteEditor(Pixel x, Pixel y, gdMidiActionEditor* base)
+: geScroll(x, y, 200, 422),
+ m_base (base)
+{
+ pianoRoll = new gePianoRoll(x, y, m_base->fullWidth, static_cast<MidiChannel*>(m_base->ch));
+
+ rebuild();
+
+ size(m_base->fullWidth, m::conf::pianoRollH);
+
+ type(Fl_Scroll::VERTICAL_ALWAYS);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+geNoteEditor::~geNoteEditor()
+{
+ m::conf::pianoRollH = h();
+ m::conf::pianoRollY = pianoRoll->y();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void geNoteEditor::scroll()
+{
+ Pixel ey = Fl::event_y() - pianoRoll->pick;
+
+ Pixel y1 = y();
+ Pixel y2 = (y() + h()) - pianoRoll->h();
+
+ if (ey > y1) ey = y1; else if (ey < y2) ey = y2;
+
+ pianoRoll->position(x(), ey);
+
+ redraw();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void geNoteEditor::rebuild()
+{
+ size(m_base->fullWidth, h());
+ pianoRoll->rebuild();
+}
+}} // giada::v::
\ No newline at end of file
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef GE_NOTE_EDITOR_H
+#define GE_NOTE_EDITOR_H
+
+
+#include "../basics/scroll.h"
+
+
+namespace giada {
+namespace v
+{
+class gdMidiActionEditor;
+class gePianoRoll;
+
+
+class geNoteEditor : public geScroll
+{
+private:
+
+ gdMidiActionEditor* m_base;
+
+public:
+
+ geNoteEditor(Pixel x, Pixel y, gdMidiActionEditor* base);
+ ~geNoteEditor();
+
+ void rebuild();
+ void scroll();
+
+ gePianoRoll* pianoRoll;
+};
+}} // giada::v::
+
+#endif
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <FL/fl_draw.H>
+#include "../../../core/const.h"
+#include "../../../core/midiEvent.h"
+#include "../../../utils/math.h"
+#include "pianoItem.h"
+
+
+namespace giada {
+namespace v
+{
+gePianoItem::gePianoItem(Pixel X, Pixel Y, Pixel W, Pixel H, m::recorder::action a1,
+ m::recorder::action a2)
+: geBaseAction(X, Y, W, H, /*resizable=*/true, a1, a2),
+ orphaned (a2.frame == -1)
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void gePianoItem::draw()
+{
+ Fl_Color color = hovered ? G_COLOR_LIGHT_2 : G_COLOR_LIGHT_1;
+
+ Pixel by = y() + 2;
+ Pixel bh = h() - 3;
+
+ if (orphaned) {
+ fl_rect(x(), by, MIN_WIDTH, bh, color);
+ fl_line(x(), by, x() + MIN_WIDTH, by + bh);
+ }
+ else {
+ Pixel vh = calcVelocityH();
+ fl_rectf(x(), by + (bh - vh), w(), vh, color);
+ fl_rect(x(), by, w(), bh, color);
+ }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+Pixel gePianoItem::calcVelocityH() const
+{
+ int v = m::MidiEvent(a1.iValue).getVelocity();
+ return u::math::map<int, Pixel>(v, 0, G_MAX_VELOCITY, 0, h() - 3);
+}
+}} // giada::v::
\ No newline at end of file
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef GE_PIANO_ITEM_H
+#define GE_PIANO_ITEM_H
+
+
+#include "../../../core/recorder.h"
+#include "baseAction.h"
+
+
+namespace giada {
+namespace v
+{
+class gdActionEditor;
+
+
+class gePianoItem : public geBaseAction
+{
+private:
+
+ Pixel calcVelocityH() const;
+
+public:
+
+ gePianoItem(int x, int y, int w, int h, m::recorder::action a1,
+ m::recorder::action a2);
+
+ void draw() override;
+
+ bool orphaned;
+};
+}} // giada::v::
+
+
+#endif
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <cassert>
+#include <FL/Fl.H>
+#include "../../../core/conf.h"
+#include "../../../core/const.h"
+#include "../../../core/clock.h"
+#include "../../../core/midiChannel.h"
+#include "../../../utils/log.h"
+#include "../../../utils/string.h"
+#include "../../../utils/math.h"
+#include "../../../glue/recorder.h"
+#include "../../dialogs/actionEditor/baseActionEditor.h"
+#include "pianoItem.h"
+#include "noteEditor.h"
+#include "pianoRoll.h"
+
+
+using std::string;
+using std::vector;
+
+
+namespace giada {
+namespace v
+{
+gePianoRoll::gePianoRoll(Pixel X, Pixel Y, Pixel W, MidiChannel* ch)
+ : geBaseActionEditor(X, Y, W, 40, ch),
+ pick (0)
+{
+ position(x(), m::conf::pianoRollY == -1 ? y()-(h()/2) : m::conf::pianoRollY);
+ rebuild();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+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__) // TODO - is this still useful?
+ for (Pixel i=36; i<m_base->fullWidth; i+=36) /// TODO: i < ae->coverX is faster
+ fl_copy_offscreen(x()+i, y(), CELL_W, h(), surface2, 1, 0);
+#else
+ for (Pixel i=CELL_W; i<m_base->loopWidth; i+=CELL_W)
+ fl_copy_offscreen(x()+i, y(), CELL_W, h(), surface2, 0, 0);
+#endif
+
+ baseDraw(false);
+ draw_children();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+int gePianoRoll::handle(int e)
+{
+ if (e == FL_PUSH && Fl::event_button3()) {
+ pick = Fl::event_y() - y();
+ return geBaseActionEditor::handle(e);
+ }
+ if (e == FL_DRAG && Fl::event_button3()) {
+ static_cast<geNoteEditor*>(parent())->scroll();
+ return 1;
+ }
+ return geBaseActionEditor::handle(e);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void gePianoRoll::onAddAction()
+{
+ Frame frame = m_base->pixelToFrame(Fl::event_x() - x());
+ int note = yToNote(Fl::event_y() - y());
+ c::recorder::recordMidiAction(m_ch->index, note, G_MAX_VELOCITY, frame);
+
+ m_base->rebuild(); // Rebuild velocityEditor as well
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void gePianoRoll::onDeleteAction()
+{
+ c::recorder::deleteMidiAction(static_cast<MidiChannel*>(m_ch), m_action->a1, m_action->a2);
+
+ m_base->rebuild(); // Rebuild velocityEditor as well
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void gePianoRoll::onMoveAction()
+{
+ /* Y computation: - (CELL_H/2) is wrong: we should need the y pick value as
+ done with x. Let's change this when vertical piano zoom will be available. */
+
+ Pixel ex = Fl::event_x() - m_action->pick;
+ Pixel ey = snapToY(Fl::event_y() - y() - (CELL_H/2)) + y();
+
+ Pixel x1 = x();
+ Pixel x2 = (m_base->loopWidth + x()) - m_action->w();
+ Pixel y1 = y();
+ Pixel y2 = y() + h();
+
+ if (ex < x1) ex = x1; else if (ex > x2) ex = x2;
+ if (ey < y1) ey = y1; else if (ey > y2) ey = y2;
+
+ m_action->position(ex, ey);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void gePianoRoll::onResizeAction()
+{
+ if (static_cast<gePianoItem*>(m_action)->orphaned)
+ return;
+
+ Pixel ex = Fl::event_x();
+
+ Pixel x1 = x();
+ Pixel x2 = m_base->loopWidth + x();
+
+ if (ex < x1) ex = x1; else if (ex > x2) ex = x2;
+
+ if (m_action->onRightEdge)
+ m_action->setRightEdge(ex - m_action->x());
+ else
+ m_action->setLeftEdge(ex);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void gePianoRoll::onRefreshAction()
+{
+ namespace cr = c::recorder;
+
+ if (static_cast<gePianoItem*>(m_action)->orphaned)
+ return;
+
+ Pixel p1 = m_action->x() - x();
+ Pixel p2 = m_action->x() + m_action->w() - x();
+
+ Frame f1 = 0;
+ Frame f2 = 0;
+
+ if (!m_action->isOnEdges()) {
+ f1 = m_base->pixelToFrame(p1);
+ f2 = m_base->pixelToFrame(p2, /*snap=*/false) - (m_base->pixelToFrame(p1, /*snap=*/false) - f1);
+ }
+ else if (m_action->onLeftEdge) {
+ f1 = m_base->pixelToFrame(p1);
+ f2 = m_action->a2.frame;
+ }
+ else if (m_action->onRightEdge) {
+ f1 = m_action->a1.frame;
+ f2 = m_base->pixelToFrame(p2);
+ }
+
+ assert(f1 != 0 && f2 != 0);
+
+ int note = yToNote(m_action->y() - y());
+ int velocity = m::MidiEvent(m_action->a1.iValue).getVelocity();
+
+ /* TODO - less then optimal. Let's wait for recorder refactoring... */
+
+ int oldNote = m::MidiEvent(m_action->a1.iValue).getNote();
+ cr::deleteMidiAction(static_cast<MidiChannel*>(m_ch), m_action->a1, m_action->a2);
+ if (cr::midiActionCanFit(m_ch->index, note, f1, f2))
+ cr::recordMidiAction(m_ch->index, note, velocity, f1, f2);
+ else
+ cr::recordMidiAction(m_ch->index, oldNote, velocity, m_action->a1.frame, m_action->a2.frame);
+
+ m_base->rebuild(); // Rebuild velocityEditor as well
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+int gePianoRoll::yToNote(Pixel p) const
+{
+ return gePianoRoll::MAX_KEYS - (p / gePianoRoll::CELL_H);
+}
+
+
+Pixel gePianoRoll::noteToY(int n) const
+{
+ return (MAX_KEYS * CELL_H) - (n * gePianoRoll::CELL_H);
+}
+
+
+Pixel gePianoRoll::snapToY(Pixel p) const
+{
+ return u::math::quantize(p, CELL_H);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void gePianoRoll::rebuild()
+{
+ namespace mr = m::recorder;
+ namespace cr = c::recorder;
+
+ /* Remove all existing actions and set a new width, according to the current
+ zoom level. */
+
+ clear();
+ size(m_base->fullWidth, (MAX_KEYS + 1) * CELL_H);
+
+ vector<mr::Composite> actions = cr::getMidiActions(m_ch->index);
+ for (mr::Composite comp : actions)
+ {
+ m::MidiEvent e1 = comp.a1.iValue;
+ m::MidiEvent e2 = comp.a2.iValue;
+
+ gu_log("[gePianoRoll::rebuild] ((0x%X, 0x%X, f=%d) - (0x%X, 0x%X, f=%d))\n",
+ e1.getStatus(), e1.getNote(), comp.a1.frame,
+ e2.getStatus(), e2.getNote(), comp.a2.frame
+ );
+
+ Pixel px = x() + m_base->frameToPixel(comp.a1.frame);
+ Pixel py = y() + noteToY(e1.getNote());
+ Pixel pw = m_base->frameToPixel(comp.a2.frame - comp.a1.frame);
+ Pixel ph = CELL_H;
+
+ add(new gePianoItem(px, py, pw, ph, comp.a1, comp.a2));
+ }
+
+ drawSurface1();
+ drawSurface2();
+
+ redraw();
+}
+}} // giada::v::
\ No newline at end of file
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef GE_PIANO_ROLL_H
+#define GE_PIANO_ROLL_H
+
+
+#include <FL/fl_draw.H>
+#include "baseActionEditor.h"
+
+
+class MidiChannel;
+
+
+namespace giada {
+namespace v
+{
+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
+ };
+
+ Fl_Offscreen surface1; // notes, no repeat
+ Fl_Offscreen surface2; // lines, x-repeat
+
+ void onAddAction() override;
+ void onDeleteAction() override;
+ void onMoveAction() override;
+ void onResizeAction() override;
+ void onRefreshAction() override;
+
+ /* 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();
+
+ Pixel snapToY(Pixel p) const;
+ int yToNote(Pixel y) const;
+ Pixel noteToY(int n) const;
+
+public:
+
+ static const int MAX_KEYS = 127;
+ static const int MAX_OCTAVES = 9;
+ static const int KEYS = 12;
+ static const Pixel CELL_H = 20;
+ static const Pixel CELL_W = 40;
+
+ gePianoRoll(Pixel x, Pixel y, Pixel w, MidiChannel* ch);
+
+ void draw() override;
+ int handle(int e) override;
+
+ void rebuild() override;
+
+ Pixel pick;
+};
+}} // giada::v::
+
+
+#endif
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <FL/fl_draw.H>
+#include "../../../core/const.h"
+#include "../../../core/sampleChannel.h"
+#include "sampleAction.h"
+
+
+namespace giada {
+namespace v
+{
+geSampleAction::geSampleAction(Pixel X, Pixel Y, Pixel W, Pixel H,
+ const SampleChannel* ch, m::recorder::action a1, m::recorder::action a2)
+: geBaseAction(X, Y, W, H, ch->mode == ChannelMode::SINGLE_PRESS, a1, a2),
+ m_ch (ch)
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void geSampleAction::draw()
+{
+ Fl_Color color = hovered ? G_COLOR_LIGHT_2 : G_COLOR_LIGHT_1;
+
+ if (m_ch->mode == ChannelMode::SINGLE_PRESS) {
+ fl_rectf(x(), y(), w(), h(), color);
+ }
+ else {
+ if (a1.type == G_ACTION_KILL)
+ fl_rect(x(), y(), MIN_WIDTH, h(), color);
+ else {
+ fl_rectf(x(), y(), MIN_WIDTH, h(), color);
+ if (a1.type == G_ACTION_KEYPRESS)
+ fl_rectf(x()+3, y()+h()-11, w()-6, 8, G_COLOR_GREY_4);
+ else
+ if (a1.type == G_ACTION_KEYREL)
+ fl_rectf(x()+3, y()+3, w()-6, 8, G_COLOR_GREY_4);
+ }
+ }
+}
+}} // giada::v::
\ No newline at end of file
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef GE_SAMPLE_ACTION_H
+#define GE_SAMPLE_ACTION_H
+
+
+#include "../../../core/recorder.h"
+#include "baseAction.h"
+
+
+class SampleChannel;
+
+
+namespace giada {
+namespace v
+{
+class geSampleAction : public geBaseAction
+{
+private:
+
+ const SampleChannel* m_ch;
+
+public:
+
+ geSampleAction(Pixel x, Pixel y, Pixel w, Pixel h, const SampleChannel* ch,
+ m::recorder::action a1, m::recorder::action a2);
+
+ void draw() override;
+};
+}} // giada::v::
+
+
+#endif
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <FL/Fl.H>
+#include <FL/fl_draw.H>
+#include "../../../core/const.h"
+#include "../../../core/conf.h"
+#include "../../../core/sampleChannel.h"
+#include "../../../utils/log.h"
+#include "../../../glue/recorder.h"
+#include "../../dialogs/actionEditor/baseActionEditor.h"
+#include "sampleAction.h"
+#include "sampleActionEditor.h"
+
+
+using std::vector;
+
+
+namespace giada {
+namespace v
+{
+geSampleActionEditor::geSampleActionEditor(Pixel x, Pixel y, SampleChannel* ch)
+: geBaseActionEditor(x, y, 200, m::conf::sampleActionEditorH, ch)
+{
+ rebuild();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+geSampleActionEditor::~geSampleActionEditor()
+{
+ m::conf::sampleActionEditorH = h();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void geSampleActionEditor::rebuild()
+{
+ namespace mr = m::recorder;
+ namespace cr = c::recorder;
+
+ const SampleChannel* ch = static_cast<const SampleChannel*>(m_ch);
+
+ /* Remove all existing actions and set a new width, according to the current
+ zoom level. */
+
+ clear();
+ size(m_base->fullWidth, h());
+
+ vector<mr::Composite> comps = cr::getSampleActions(ch);
+
+ for (mr::Composite comp : comps) {
+ gu_log("[geSampleActionEditor::rebuild] Action [%d, %d)\n",
+ comp.a1.frame, comp.a2.frame);
+ Pixel px = x() + m_base->frameToPixel(comp.a1.frame);
+ Pixel py = y() + 4;
+ Pixel pw = 0;
+ Pixel ph = h() - 8;
+ if (comp.a2.frame != -1)
+ pw = m_base->frameToPixel(comp.a2.frame - comp.a1.frame);
+
+ geSampleAction* a = new geSampleAction(px, py, pw, ph, ch, comp.a1, comp.a2);
+ add(a);
+ resizable(a);
+ }
+
+ /* If channel is LOOP_ANY, deactivate it: a loop mode channel cannot hold
+ keypress/keyrelease actions. */
+
+ ch->isAnyLoopMode() ? deactivate() : activate();
+
+ redraw();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void geSampleActionEditor::draw()
+{
+ /* Draw basic boundaries (+ beat bars) and hide the unused area. Then draw
+ children (the actions). */
+
+ baseDraw();
+
+ /* Print label. */
+
+ fl_color(G_COLOR_GREY_4);
+ fl_font(FL_HELVETICA, G_GUI_FONT_SIZE_BASE);
+ if (active())
+ fl_draw("start/stop", x()+4, y(), w(), h(), (Fl_Align) (FL_ALIGN_LEFT | FL_ALIGN_CENTER));
+ else
+ fl_draw("start/stop (disabled)", x()+4, y(), w(), h(), (Fl_Align) (FL_ALIGN_LEFT | FL_ALIGN_CENTER));
+
+ draw_children();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void geSampleActionEditor::onAddAction()
+{
+ Frame f = m_base->pixelToFrame(Fl::event_x() - x());
+ c::recorder::recordSampleAction(static_cast<SampleChannel*>(m_ch),
+ m_base->getActionType(), f);
+ rebuild();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void geSampleActionEditor::onDeleteAction()
+{
+ c::recorder::deleteSampleAction(static_cast<SampleChannel*>(m_ch), m_action->a1, m_action->a2);
+ rebuild();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void geSampleActionEditor::onMoveAction()
+{
+ Pixel ex = Fl::event_x() - m_action->pick;
+
+ Pixel x1 = x();
+ Pixel x2 = m_base->loopWidth + x() - m_action->w();
+
+ if (ex < x1) ex = x1; else if (ex > x2) ex = x2;
+
+ m_action->setPosition(ex);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void geSampleActionEditor::onResizeAction()
+{
+ Pixel ex = Fl::event_x();
+
+ Pixel x1 = x();
+ Pixel x2 = m_base->loopWidth + x();
+
+ if (ex < x1) ex = x1; else if (ex > x2) ex = x2;
+
+ if (m_action->onRightEdge)
+ m_action->setRightEdge(ex - m_action->x());
+ else
+ m_action->setLeftEdge(ex);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void geSampleActionEditor::onRefreshAction()
+{
+ namespace cr = c::recorder;
+
+ SampleChannel* ch = static_cast<SampleChannel*>(m_ch);
+
+ Pixel p1 = m_action->x() - x();
+ Pixel p2 = m_action->x() + m_action->w() - x();
+ Frame f1 = 0;
+ Frame f2 = 0;
+ int type = m_action->a1.type;
+
+ if (!m_action->isOnEdges()) {
+ f1 = m_base->pixelToFrame(p1);
+ f2 = m_base->pixelToFrame(p2, /*snap=*/false) - (m_base->pixelToFrame(p1, /*snap=*/false) - f1);
+ }
+ else if (m_action->onLeftEdge) {
+ f1 = m_base->pixelToFrame(p1);
+ f2 = m_action->a2.frame;
+ }
+ else if (m_action->onRightEdge) {
+ f1 = m_action->a1.frame;
+ f2 = m_base->pixelToFrame(p2);
+ }
+
+ /* TODO - less then optimal. Let's wait for recorder refactoring... */
+
+ cr::deleteSampleAction(ch, m_action->a1, m_action->a2);
+ if (cr::sampleActionCanFit(ch, f1, f2))
+ cr::recordSampleAction(ch, type, f1, f2);
+ else
+ cr::recordSampleAction(ch, type, m_action->a1.frame, m_action->a2.frame);
+
+ rebuild();
+}
+}} // giada::v::
\ No newline at end of file
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef GE_SAMPLE_ACTION_EDITOR_H
+#define GE_SAMPLE_ACTION_EDITOR_H
+
+
+#include "baseActionEditor.h"
+
+
+class SampleChannel;
+
+
+namespace giada {
+namespace v
+{
+class geSampleAction;
+
+
+class geSampleActionEditor : public geBaseActionEditor
+{
+private:
+
+ void onAddAction() override;
+ void onDeleteAction() override;
+ void onMoveAction() override;
+ void onResizeAction() override;
+ void onRefreshAction() override;
+
+public:
+
+ geSampleActionEditor(Pixel x, Pixel y, SampleChannel* ch);
+ ~geSampleActionEditor();
+
+ void draw() override;
+
+ void rebuild() override;
+};
+}} // giada::v::
+
+#endif
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <FL/Fl.H>
+#include <FL/fl_draw.H>
+#include "../../../utils/log.h"
+#include "../../../utils/math.h"
+#include "../../../core/const.h"
+#include "../../../core/conf.h"
+#include "../../../core/clock.h"
+#include "../../../core/midiChannel.h"
+#include "../../../glue/recorder.h"
+#include "../../dialogs/actionEditor/baseActionEditor.h"
+#include "envelopePoint.h"
+#include "velocityEditor.h"
+
+
+using std::vector;
+
+
+namespace giada {
+namespace v
+{
+geVelocityEditor::geVelocityEditor(Pixel x, Pixel y, MidiChannel* ch)
+: geBaseActionEditor(x, y, 200, m::conf::velocityEditorH, ch)
+{
+ rebuild();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+geVelocityEditor::~geVelocityEditor()
+{
+ m::conf::velocityEditorH = h();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void geVelocityEditor::draw()
+{
+ baseDraw();
+
+ /* Print label. */
+
+ fl_color(G_COLOR_GREY_4);
+ fl_font(FL_HELVETICA, G_GUI_FONT_SIZE_BASE);
+ fl_draw("Velocity", x()+4, y(), w(), h(), (Fl_Align) (FL_ALIGN_LEFT));
+
+ if (children() == 0)
+ return;
+
+ Pixel side = geEnvelopePoint::SIDE / 2;
+
+ for (int i=0; i<children(); i++) {
+ geEnvelopePoint* p = static_cast<geEnvelopePoint*>(child(i));
+ if (m_action == nullptr)
+ p->position(p->x(), valueToY(m::MidiEvent(p->a1.iValue).getVelocity()));
+ Pixel x1 = p->x() + side;
+ Pixel y1 = p->y();
+ Pixel y2 = y() + h();
+ fl_line(x1, y1, x1, y2);
+ }
+
+ draw_children();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+Pixel geVelocityEditor::valueToY(int v) const
+{
+ /* Cast the input type of 'v' to float, to make the mapping more precise. */
+ return u::math::map<float, Pixel>(v, 0, G_MAX_VELOCITY, y() + (h() - geEnvelopePoint::SIDE), y());
+}
+
+
+int geVelocityEditor::yToValue(Pixel px) const
+{
+ return u::math::map<Pixel, int>(px, h() - geEnvelopePoint::SIDE, 1, 0, G_MAX_VELOCITY);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void geVelocityEditor::rebuild()
+{
+ namespace mr = m::recorder;
+ namespace cr = c::recorder;
+
+ /* Remove all existing actions and set a new width, according to the current
+ zoom level. */
+
+ clear();
+ size(m_base->fullWidth, h());
+
+ vector<mr::Composite> actions = cr::getMidiActions(m_ch->index);
+ for (mr::Composite comp : actions)
+ {
+ gu_log("[geVelocityEditor::rebuild] f=%d\n", comp.a1.frame);
+
+ Pixel px = x() + m_base->frameToPixel(comp.a1.frame);
+ Pixel py = y() + valueToY(m::MidiEvent(comp.a1.iValue).getVelocity());
+
+ add(new geEnvelopePoint(px, py, comp.a1));
+ }
+
+ resizable(nullptr);
+ redraw();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void geVelocityEditor::onMoveAction()
+{
+ Pixel ey = Fl::event_y() - (geEnvelopePoint::SIDE / 2);
+
+ Pixel y1 = y();
+ Pixel y2 = y() + h() - geEnvelopePoint::SIDE;
+
+ if (ey < y1) ey = y1; else if (ey > y2) ey = y2;
+
+ m_action->position(m_action->x(), ey);
+ redraw();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void geVelocityEditor::onRefreshAction()
+{
+ c::recorder::setVelocity(m_ch, m_action->a1, yToValue(m_action->y() - y()));
+
+ m_base->rebuild(); // Rebuild pianoRoll as well
+}
+}} // giada::v::
\ No newline at end of file
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef GE_VELOCITY_EDITOR_H
+#define GE_VELOCITY_EDITOR_H
+
+
+#include "baseActionEditor.h"
+
+
+class MidiChannel;
+
+
+namespace giada {
+namespace v
+{
+class geEnvelopePoint;
+
+
+class geVelocityEditor : public geBaseActionEditor
+{
+private:
+
+ void onMoveAction() override;
+ void onRefreshAction() override;
+ void onAddAction() override{};
+ void onDeleteAction() override{};
+ void onResizeAction() override{};
+
+ Pixel valueToY(int v) const;
+ int yToValue(Pixel y) const;
+
+public:
+
+ geVelocityEditor(Pixel x, Pixel y, MidiChannel* ch);
+ ~geVelocityEditor();
+
+ void draw() override;
+
+ void rebuild() override;
+};
+}} // giada::v::
+
+
+#endif
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <FL/fl_draw.H>
+#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);
+}
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef GE_BASE_BUTTON_H
+#define GE_BASE_BUTTON_H
+
+
+#include <string>
+#include <FL/Fl_Button.H>
+
+
+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
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#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);
+}
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef GE_BOX_H
+#define GE_BOX_H
+
+
+#include <FL/Fl_Box.H>
+
+
+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
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <FL/fl_draw.H>
+#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);
+}
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef GE_BOXTYPES_H
+#define GE_BOXTYPES_H
+
+
+#include <FL/Fl.H>
+
+
+#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
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <FL/fl_draw.H>
+#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);
+}
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#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
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <FL/fl_draw.H>
+#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));
+}
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef GE_CHECK_H
+#define GE_CHECK_H
+
+
+#include <FL/Fl_Check_Button.H>
+
+
+class geCheck : public Fl_Check_Button
+{
+public:
+
+ geCheck(int x, int y, int w, int h, const char *l=0);
+
+ void draw();
+};
+
+
+#endif
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <string>
+#include <FL/fl_draw.H>
+#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));
+}
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef GE_CHOICE_H
+#define GE_CHOICE_H
+
+
+#include <FL/Fl_Choice.H>
+
+
+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
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <FL/fl_draw.H>
+#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());
+}
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef GE_DIAL_H
+#define GE_DIAL_H
+
+
+#include <FL/Fl_Dial.H>
+
+
+class geDial : public Fl_Dial
+{
+public:
+
+ geDial(int x, int y, int w, int h, const char *l=0);
+
+ void draw();
+};
+
+
+#endif
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#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)
+{
+}
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#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
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#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);
+}
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef GE_INPUT_H
+#define GE_INPUT_H
+
+
+#include <FL/Fl_Input.H>
+
+
+class geInput : public Fl_Input
+{
+public:
+
+ geInput(int x, int y, int w, int h, const char *l=0);
+};
+
+#endif
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#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; t<nc; t++) { // tell children to resize to our new width
+ Fl_Widget* c = child(t);
+ c->resize(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);
+}
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef GE_LIQUID_SCROLL_H
+#define GE_LIQUID_SCROLL_H
+
+
+#include <FL/Fl_Scroll.H>
+
+
+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
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#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);
+}
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef GE_PROGRESS_H
+#define GE_PROGRESS_H
+
+
+#include <FL/Fl_Progress.H>
+
+
+class geProgress : public Fl_Progress
+{
+public:
+
+ geProgress(int x, int y, int w, int h, const char *l=0);
+};
+
+
+#endif
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <FL/fl_draw.H>
+#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));
+}
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef GE_RADIO_H
+#define GE_RADIO_H
+
+
+#include <FL/Fl_Radio_Button.H>
+
+
+class geRadio : public Fl_Radio_Button
+{
+public:
+
+ geRadio(int x, int y, int w, int h, const char *l=0);
+
+ void draw();
+};
+
+#endif
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <FL/Fl.H>
+#include <FL/Fl_Scroll.H>
+#include <FL/fl_draw.H>
+#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<Fl_Scroll*>(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; t<grp->children(); 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; t<grp->children(); 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);
+}
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef GE_RESIZER_BAR_H
+#define GE_RESIZER_BAR_H
+
+
+#include <FL/Fl_Box.H>
+
+
+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
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#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);
+}
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef GE_SCROLL_H
+#define GE_SCROLL_H
+
+
+#include <FL/Fl_Scroll.H>
+
+
+class geScroll : public Fl_Scroll
+{
+public:
+
+ geScroll(int x, int y, int w, int h, int type=Fl_Scroll::BOTH);
+};
+
+
+#endif
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#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);
+}
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef GE_SLIDER_H
+#define GE_SLIDER_H
+
+
+#include <FL/Fl_Slider.H>
+
+
+class geSlider : public Fl_Slider
+{
+public:
+
+ geSlider(int x, int y, int w, int h, const char *l=0);
+
+ int id;
+};
+
+
+#endif
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <FL/fl_draw.H>
+#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);
+}
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#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
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include "../../core/const.h"
+#include "../../utils/string.h"
+#include "../../utils/fs.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),
+ m_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()
+{
+ m_showHiddenFiles = !m_showHiddenFiles;
+ loadDir(m_currentDir);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void geBrowser::loadDir(const string& dir)
+{
+ m_currentDir = dir;
+ load(m_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 || (!m_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))
+ static_cast<gdBrowserBase*>(parent())->fireCallback();
+ ret = 1;
+ break;
+ case FL_PUSH: // mouse
+ if (Fl::event_clicks() > 0) // double click
+ static_cast<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(m_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(m_currentDir);
+ else {
+#ifdef G_OS_WINDOWS
+ string sep = m_currentDir != "" ? G_SLASH_STR : "";
+#else
+ string sep = G_SLASH_STR;
+#endif
+ return normalize(gu_getRealPath(m_currentDir + sep + 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 is the root dir,
+ that is '/' on Unix or '[x]:\' on Windows. */
+
+ //if (out.back() == G_SLASH && out.length() > 1)
+ if (out.back() == G_SLASH && !gu_isRootDir(s))
+ out = out.substr(0, out.size()-1);
+ return out;
+}
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef GE_BROWSER_H
+#define GE_BROWSER_H
+
+
+#include <string>
+#include <FL/Fl_File_Browser.H>
+
+
+class geBrowser : public Fl_File_Browser
+{
+private:
+
+ std::string m_currentDir;
+ bool m_showHiddenFiles;
+
+ /* normalize
+ Makes 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
+ Initializes browser and show 'dir' as initial directory. */
+
+ void loadDir(const std::string &dir);
+
+ /* getSelectedItem
+ Returns 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
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <string>
+#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);
+ selection_color(G_COLOR_GREY_4);
+
+ 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; i<nfreq; i++) {
+ int freq = kernelAudio::getFreq(sounddevOut->value(), 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; i<chs; i+=2) {
+ string tmp = gu_iToString(i+1) + "-" + gu_iToString(i+2);
+ channelsIn->add(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; i<chs; i+=2) {
+ string tmp = gu_iToString(i+1) + "-" + gu_iToString(i+2);
+ channelsOut->add(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; i<m->size(); 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<kernelAudio::countDevices(); i++) {
+
+ /* escaping '/', very dangerous in FLTK (it creates a submenu) */
+
+ string tmp = kernelAudio::getDeviceName(i);
+ for (unsigned k=0; k<tmp.size(); k++)
+ if (tmp[k] == '/' || tmp[k] == '|' || tmp[k] == '&' || tmp[k] == '_')
+ tmp[k] = '-';
+
+ /* add to list devices with at least 1 channel available. In this
+ * way we can filter devices only for input or output, e.g. an input
+ * devices has 0 output channels. */
+
+ if (kernelAudio::getMaxOutChans(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());
+}
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef GE_TAB_AUDIO_H
+#define GE_TAB_AUDIO_H
+
+
+#include <FL/Fl_Group.H>
+
+
+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
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <FL/Fl_Pack.H>
+#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);
+ selection_color(G_COLOR_GREY_4);
+
+ 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;
+}
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef GE_TAB_BEHAVIORS_H
+#define GE_TAB_BEHAVIORS_H
+
+
+#include <FL/Fl_Group.H>
+
+
+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
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <string>
+#include "../../../core/const.h"
+#ifdef G_OS_MAC
+ #include <RtMidi.h>
+#else
+ #include <rtmidi/RtMidi.h>
+#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");
+ midiMap = new geChoice(x()+w()-250, portIn->y()+portIn->h()+8, 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);
+ selection_color(G_COLOR_GREY_4);
+
+ system->callback(cb_changeSystem, (void*)this);
+
+ fetchSystems();
+ fetchOutPorts();
+ fetchInPorts();
+ fetchMidiMaps();
+
+ 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; i<kernelMidi::countOutPorts(); i++)
+ portOut->add(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; i<kernelMidi::countInPorts(); i++)
+ portIn->add(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; i<midimap::maps.size(); i++) {
+ const char *imap = midimap::maps.at(i).c_str();
+ midiMap->add(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::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();
+ 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);
+ sync->deactivate();
+ }
+
+}
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef GE_TAB_MIDI_H
+#define GE_TAB_MIDI_H
+
+
+#include <FL/Fl_Group.H>
+
+
+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;
+ geChoice* midiMap;
+ geChoice* sync;
+
+ geTabMidi(int x, int y, int w, int h);
+
+ void save();
+};
+
+
+#endif
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#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);
+ selection_color(G_COLOR_GREY_4);
+
+ 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;
+ }
+}
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef GE_TAB_MISC_H
+#define GE_TAB_MISC_H
+
+
+#include <FL/Fl_Group.H>
+
+
+class geChoice;
+
+
+class geTabMisc : public Fl_Group
+{
+public:
+
+ geChoice *debugMsg;
+
+ geTabMisc(int x, int y, int w, int h);
+
+ void save();
+};
+
+#endif
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifdef WITH_VST
+
+
+#include <functional>
+#include <FL/Fl.H>
+#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);
+ selection_color(G_COLOR_GREY_4);
+
+ 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<gdWindow*>(top_window())->addSubWindow(browser);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void geTabPlugins::cb_scan()
+{
+ std::function<void(float)> 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
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef GE_TAB_PLUGINS_H
+#define GE_TAB_PLUGINS_H
+
+
+#ifdef WITH_VST
+
+
+#include <FL/Fl_Group.H>
+
+
+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
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <FL/fl_draw.H>
+#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<clock::getBars(); i++)
+ fl_line(x()+cursorW*(i*delta), y()+1, x()+cursorW*(i*delta), y()+h()-2);
+
+ /* unused grey area */
+
+ fl_rectf(x()+greyX+1, y()+1, w()-greyX-1, h()-2, G_COLOR_GREY_4);
+}
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef GE_BEAT_METER_H
+#define GE_BEAT_METER_H
+
+
+#include <FL/Fl_Box.H>
+
+
+class geBeatMeter : public Fl_Box
+{
+public:
+
+ geBeatMeter(int X,int Y,int W,int H,const char *L=0);
+ void draw();
+};
+
+
+#endif
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <FL/Fl.H>
+#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;
+
+
+geChannel::geChannel(int X, int Y, int W, int H, Channel* ch)
+ : Fl_Group(X, Y, W, H, nullptr),
+ ch (ch)
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+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()
+{
+ c::channel::toggleArm(ch, true);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void geChannel::cb_mute()
+{
+ c::channel::toggleMute(ch);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void geChannel::cb_solo()
+{
+ c::channel::toggleSolo(ch);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void geChannel::cb_changeVol()
+{
+ c::channel::setVolume(ch, vol->value());
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+#ifdef WITH_VST
+void geChannel::cb_openFxWindow()
+{
+ gu_openSubWindow(G_MainWin, new gdPluginList(m::pluginHost::CHANNEL, ch), WID_FX_LIST);
+}
+#endif
+
+
+/* -------------------------------------------------------------------------- */
+
+
+int geChannel::keyPress(int e)
+{
+ return handleKey(e, ch->key);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+
+int geChannel::getColumnIndex()
+{
+ return static_cast<geColumn*>(parent())->getIndex();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void geChannel::blink()
+{
+ if (gu_getBlinker() > 6)
+ mainButton->setPlayMode();
+ else
+ mainButton->setDefaultMode();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+void geChannel::setColorsByStatus(ChannelStatus playStatus, ChannelStatus recStatus)
+{
+ switch (playStatus) {
+ case ChannelStatus::OFF:
+ case ChannelStatus::EMPTY:
+ mainButton->setDefaultMode();
+ button->imgOn = channelPlay_xpm;
+ button->imgOff = channelStop_xpm;
+ button->redraw();
+ break;
+ case ChannelStatus::PLAY:
+ mainButton->setPlayMode();
+ button->imgOn = channelStop_xpm;
+ button->imgOff = channelPlay_xpm;
+ button->redraw();
+ break;
+ case ChannelStatus::WAIT:
+ blink();
+ break;
+ case ChannelStatus::ENDING:
+ mainButton->setEndingMode();
+ break;
+ default: break;
+ }
+
+ switch (recStatus) {
+ case ChannelStatus::WAIT:
+ blink();
+ break;
+ case ChannelStatus::ENDING:
+ mainButton->setEndingMode();
+ break;
+ default: break;
+ }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void geChannel::packWidgets()
+{
+ /* Count visible widgets and resize mainButton according to how many widgets
+ are visible. */
+
+ int visibles = 0;
+ for (int i=0; i<children(); i++) {
+ child(i)->size(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; i<children(); i++) {
+ if (!child(i)->visible())
+ 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();
+}
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef GE_CHANNEL_H
+#define GE_CHANNEL_H
+
+
+#include <FL/Fl_Group.H>
+#include "../../../../core/types.h"
+
+
+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(giada::ChannelStatus chan, giada::ChannelStatus rec);
+
+ /* 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, 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
+};
+
+
+#endif
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <FL/fl_draw.H>
+#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<char>(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;
+}
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#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
--- /dev/null
+ /* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <FL/fl_draw.H>
+#include "../../../../utils/gui.h"
+#include "../../../../core/graphics.h"
+#include "../../../../core/sampleChannel.h"
+#include "../../../../core/const.h"
+#include "../../basics/boxtypes.h"
+#include "channelMode.h"
+
+
+using namespace giada;
+
+
+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*) ChannelMode::LOOP_BASIC);
+ add("Loop . once", 0, cb_changeMode, (void*) ChannelMode::LOOP_ONCE);
+ add("Loop . once . bar", 0, cb_changeMode, (void*) ChannelMode::LOOP_ONCE_BAR);
+ add("Loop . repeat", 0, cb_changeMode, (void*) ChannelMode::LOOP_REPEAT);
+ add("Oneshot . basic", 0, cb_changeMode, (void*) ChannelMode::SINGLE_BASIC);
+ add("Oneshot . press", 0, cb_changeMode, (void*) ChannelMode::SINGLE_PRESS);
+ add("Oneshot . retrig", 0, cb_changeMode, (void*) ChannelMode::SINGLE_RETRIG);
+ add("Oneshot . endless", 0, cb_changeMode, (void*) ChannelMode::SINGLE_ENDLESS);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void geChannelMode::draw() {
+ fl_rect(x(), y(), w(), h(), G_COLOR_GREY_4); // border
+ switch (ch->mode) {
+ case ChannelMode::LOOP_BASIC:
+ fl_draw_pixmap(loopBasic_xpm, x()+1, y()+1);
+ break;
+ case ChannelMode::LOOP_ONCE:
+ fl_draw_pixmap(loopOnce_xpm, x()+1, y()+1);
+ break;
+ case ChannelMode::LOOP_ONCE_BAR:
+ fl_draw_pixmap(loopOnceBar_xpm, x()+1, y()+1);
+ break;
+ case ChannelMode::LOOP_REPEAT:
+ fl_draw_pixmap(loopRepeat_xpm, x()+1, y()+1);
+ break;
+ case ChannelMode::SINGLE_BASIC:
+ fl_draw_pixmap(oneshotBasic_xpm, x()+1, y()+1);
+ break;
+ case ChannelMode::SINGLE_PRESS:
+ fl_draw_pixmap(oneshotPress_xpm, x()+1, y()+1);
+ break;
+ case ChannelMode::SINGLE_RETRIG:
+ fl_draw_pixmap(oneshotRetrig_xpm, x()+1, y()+1);
+ break;
+ case ChannelMode::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 = static_cast<ChannelMode>(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();
+}
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef GE_CHANNEL_MODE_H
+#define GE_CHANNEL_MODE_H
+
+
+#include <FL/Fl_Menu_Button.H>
+
+
+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
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <FL/fl_draw.H>
+#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;
+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)
+ return;
+
+ if (ch->status == ChannelStatus::WAIT ||
+ ch->status == ChannelStatus::ENDING ||
+ ch->recStatus == ChannelStatus::WAIT ||
+ ch->recStatus == ChannelStatus::ENDING)
+ {
+ fl_rect(x(), y(), w(), h(), G_COLOR_LIGHT_1);
+ }
+ else
+ if (ch->status == ChannelStatus::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->armed)
+ 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);
+
+}
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef GE_CHANNEL_STATUS_H
+#define GE_CHANNEL_STATUS_H
+
+
+#include <FL/Fl_Box.H>
+
+
+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
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <cassert>
+#include <FL/fl_draw.H>
+#include <FL/Fl_Menu_Button.H>
+#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;
+using namespace giada;
+
+
+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<string> 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<SampleChannel*>(c::channel::addChannel(
+ m_index, ChannelType::SAMPLE, G_GUI_CHANNEL_H_1));
+ result = c::channel::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; i<children(); i++) {
+ Fl_Widget* wgCurr = child(i);
+ Fl_Widget* wgPrev = i == 0 ? nullptr : child(i - 1);
+ wgCurr->resize(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<children(); i++)
+ static_cast<geChannel*>(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; i<children(); i++) {
+ child(i)->draw();
+ 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; i<children(); i++)
+ totalH += child(i)->h() + 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 == ChannelType::SAMPLE)
+ gch = new geSampleChannel(x(), 0, w(), size, static_cast<SampleChannel*>(ch));
+ else
+ gch = new geMidiChannel(x(), 0, w(), size, static_cast<MidiChannel*>(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);
+
+ 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;
+
+ if (strcmp(m->label(), "Sample channel") == 0)
+ c::channel::addChannel(m_index, ChannelType::SAMPLE, G_GUI_CHANNEL_H_1);
+ else
+ c::channel::addChannel(m_index, ChannelType::MIDI, G_GUI_CHANNEL_H_1);
+}
+
+
+
+/* -------------------------------------------------------------------------- */
+
+
+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<geChannel*>(child(i)));
+ }
+ }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+Channel* geColumn::getChannel(int i)
+{
+ return static_cast<geChannel*>(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; }
\ No newline at end of file
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef GE_COLUMN_H
+#define GE_COLUMN_H
+
+
+#include <FL/Fl_Group.H>
+
+
+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();
+
+ 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
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#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; i<columns.size(); i++) {
+ int k = columns.at(i)->find(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; i<columns.size(); i++) {
+ int pos = i == 0 ? x() - xposition() : columns.at(i-1)->x() + 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; i<columns.size(); i++)
+ columns.at(i)->refreshChannels();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+geColumn* geKeyboard::getColumnByIndex(int index)
+{
+ for (unsigned i=0; i<columns.size(); i++)
+ if (columns.at(i)->getIndex() == 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)
+{
+ using namespace giada::c;
+
+ 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;
+ io::startStopInputRec(false); // not from GUI
+ ret = 1;
+ break;
+ }
+ else if (Fl::event_key() == FL_Enter && !enterPressed) {
+ enterPressed = true;
+ io::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; i<columns.size(); i++)
+ for (int k=1; k<columns.at(i)->children(); k++)
+ ret &= static_cast<geChannel*>(columns.at(i)->child(k))->keyPress(e);
+ break;
+ }
+ }
+ return ret;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void geKeyboard::clear()
+{
+ for (unsigned i=0; i<columns.size(); i++)
+ Fl::delete_widget(columns.at(i));
+ columns.clear();
+ indexColumn = 0; // new columns will start from index=0
+ addColumnBtn->position(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; i<columns.size(); i++)
+ columns.at(i)->setIndex(i);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+int geKeyboard::getColumnWidth(int i)
+{
+ return getColumnByIndex(i)->w();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+geColumn* geKeyboard::getColumn(int i)
+{
+ return columns.at(i);
+}
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef GE_KEYBOARD_H
+#define GE_KEYBOARD_H
+
+
+#include <vector>
+#include <FL/Fl_Scroll.H>
+#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<geColumn*> 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
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <FL/Fl_Menu_Button.H>
+#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_warnings.h"
+#include "../../../dialogs/gd_keyGrabber.h"
+#include "../../../dialogs/pluginList.h"
+#include "../../../dialogs/actionEditor/midiActionEditor.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;
+
+
+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)
+{
+ using namespace giada;
+
+ geMidiChannel* gch = static_cast<geMidiChannel*>(w);
+ MidiChannel* ch = static_cast<MidiChannel*>(gch->ch);
+
+ 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 v::gdMidiActionEditor(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(ch), 0);
+ break;
+ case Menu::RESIZE_H1:
+ gch->changeSize(G_GUI_CHANNEL_H_1);
+ static_cast<geColumn*>(gch->parent())->repositionChannels();
+ break;
+ case Menu::RESIZE_H2:
+ gch->changeSize(G_GUI_CHANNEL_H_2);
+ static_cast<geColumn*>(gch->parent())->repositionChannels();
+ break;
+ case Menu::RESIZE_H3:
+ gch->changeSize(G_GUI_CHANNEL_H_3);
+ static_cast<geColumn*>(gch->parent())->repositionChannels();
+ break;
+ case Menu::RESIZE_H4:
+ gch->changeSize(G_GUI_CHANNEL_H_4);
+ static_cast<geColumn*>(gch->parent())->repositionChannels();
+ break;
+ case Menu::CLONE_CHANNEL:
+ c::channel::cloneChannel(gch->ch);
+ break;
+ case Menu::RENAME_CHANNEL:
+ gu_openSubWindow(G_MainWin, new gdChannelNameInput(gch->ch), WID_SAMPLE_NAME);
+ break;
+ case Menu::DELETE_CHANNEL:
+ c::channel::deleteChannel(gch->ch);
+ break;
+ }
+}
+
+}; // {namespace}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+geMidiChannel::geMidiChannel(int X, int Y, int W, int H, MidiChannel* ch)
+ : geChannel(X, Y, W, H, 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()
+{
+ using namespace giada;
+
+ if (button->value())
+ c::io::keyPress(static_cast<MidiChannel*>(ch), Fl::event_ctrl(), Fl::event_shift(), 0);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+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<const MidiChannel*>(ch);
+
+ string label;
+ if (mch->name.empty())
+ label = "-- MIDI --";
+ else
+ label = mch->name.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->armed);
+
+#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();
+}
\ No newline at end of file
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#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
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#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);
+}
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#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);
+};
+
+
+#endif
\ No newline at end of file
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#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_warnings.h"
+#include "../../../dialogs/actionEditor/sampleActionEditor.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_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)
+{
+ using namespace giada;
+
+ geSampleChannel* gch = static_cast<geSampleChannel*>(w);
+ SampleChannel* ch = static_cast<SampleChannel*>(gch->ch);
+
+ Menu selectedItem = (Menu) (intptr_t) v;
+
+ switch (selectedItem) {
+ case Menu::INPUT_MONITOR: {
+ c::channel::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(ch), 0);
+ break;
+ }
+ case Menu::EDIT_SAMPLE: {
+ gu_openSubWindow(G_MainWin, new gdSampleEditor(ch), WID_SAMPLE_EDITOR);
+ break;
+ }
+ case Menu::EDIT_ACTIONS: {
+ gu_openSubWindow(G_MainWin, new v::gdSampleActionEditor(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_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<geColumn*>(gch->parent())->repositionChannels();
+ break;
+ }
+ case Menu::RESIZE_H2: {
+ gch->changeSize(G_GUI_CHANNEL_H_2);
+ static_cast<geColumn*>(gch->parent())->repositionChannels();
+ break;
+ }
+ case Menu::RESIZE_H3: {
+ gch->changeSize(G_GUI_CHANNEL_H_3);
+ static_cast<geColumn*>(gch->parent())->repositionChannels();
+ break;
+ }
+ case Menu::RESIZE_H4: {
+ gch->changeSize(G_GUI_CHANNEL_H_4);
+ static_cast<geColumn*>(gch->parent())->repositionChannels();
+ break;
+ }
+ case Menu::CLONE_CHANNEL: {
+ c::channel::cloneChannel(gch->ch);
+ break;
+ }
+ case Menu::RENAME_CHANNEL: {
+ gu_openSubWindow(G_MainWin, new gdChannelNameInput(gch->ch), WID_SAMPLE_NAME);
+ break;
+ }
+ case Menu::FREE_CHANNEL: {
+ c::channel::freeChannel(gch->ch);
+ break;
+ }
+ case Menu::DELETE_CHANNEL: {
+ c::channel::deleteChannel(gch->ch);
+ break;
+ }
+ }
+}
+
+}; // {namespace}
+
+
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+
+
+geSampleChannel::geSampleChannel(int X, int Y, int W, int H, SampleChannel* ch)
+ : geChannel(X, Y, W, H, 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()
+{
+ using namespace giada;
+
+ if (button->value()) // pushed, max velocity (127 i.e. 0x7f)
+ c::io::keyPress(ch, Fl::event_ctrl(), Fl::event_shift(), 0x7F);
+ else // released
+ c::io::keyRelease(ch, Fl::event_ctrl(), Fl::event_shift());
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void geSampleChannel::cb_openMenu()
+{
+ using namespace giada;
+
+ /* 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<SampleChannel*>(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},
+ {"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 == ChannelStatus::EMPTY ||
+ ch->status == ChannelStatus::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<SampleChannel*>(ch)->isAnyLoopMode())
+ 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()
+{
+ using namespace giada::c::channel;
+ toggleReadingActions(static_cast<SampleChannel*>(ch));
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void geSampleChannel::refresh()
+{
+ using namespace giada;
+
+ if (!mainButton->visible()) // mainButton invisible? status too (see below)
+ return;
+
+ setColorsByStatus(ch->status, ch->recStatus);
+
+ if (static_cast<SampleChannel*>(ch)->wave != nullptr) {
+ if (m::mixer::recording && ch->armed)
+ 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<const SampleChannel*>(ch);
+
+ switch (sch->status) {
+ case ChannelStatus::EMPTY:
+ mainButton->label("-- no sample --");
+ break;
+ case ChannelStatus::MISSING:
+ case ChannelStatus::WRONG:
+ mainButton->label("* file not found! *");
+ break;
+ default:
+ if (sch->name.empty())
+ mainButton->label(sch->wave->getBasename(false).c_str());
+ else
+ mainButton->label(sch->name.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(static_cast<int>(sch->mode));
+ modeBox->redraw();
+
+ vol->value(sch->volume);
+ mute->value(sch->mute);
+ solo->value(sch->solo);
+
+ mainButton->setKey(sch->key);
+
+ arm->value(sch->armed);
+
+#ifdef WITH_VST
+ fx->status = sch->plugins.size() > 0;
+ fx->redraw();
+#endif
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void geSampleChannel::showActionButton()
+{
+ readActions->value(static_cast<SampleChannel*>(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);
+}
\ No newline at end of file
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#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
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <FL/Fl.H>
+#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;
+
+
+using namespace giada;
+
+
+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<geSampleChannel*>(parent());
+ SampleChannel* ch = static_cast<SampleChannel*>(gch->ch);
+ int result = c::channel::loadChannel(ch, gu_trim(gu_stripFileUrl(Fl::event_text())));
+ if (result != G_RES_OK)
+ G_MainWin->keyboard->printChannelMessage(result);
+ ret = 1;
+ break;
+ }
+ }
+ return ret;
+}
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#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
--- /dev/null
+/* -----------------------------------------------------------------------------
+*
+* 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
+* <http://www.gnu.org/licenses/>.
+*
+* -------------------------------------------------------------------------- */
+
+
+#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();
+}
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef GE_MAIN_IO_H
+#define GE_MAIN_IO_H
+
+
+#include <FL/Fl_Group.H>
+
+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
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <FL/Fl_Menu_Button.H>
+#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/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 (const Channel* ch : mixer::channels)
+ if (ch->hasActions) {
+ menu[1].activate();
+ break;
+ }
+
+ for (const Channel* ch : mixer::channels)
+ if (ch->hasData()) {
+ 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_clearAllActions();
+ 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;
+ }
+}
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef GE_MAIN_MENU_H
+#define GE_MAIN_MENU_H
+
+
+#include <FL/Fl_Group.H>
+
+
+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
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#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("1\\/1", 0, cb_quantizer, (void*)this);
+ quantizer->add("1\\/2", 0, cb_quantizer, (void*)this);
+ quantizer->add("1\\/3", 0, cb_quantizer, (void*)this);
+ quantizer->add("1\\/4", 0, cb_quantizer, (void*)this);
+ quantizer->add("1\\/6", 0, cb_quantizer, (void*)this);
+ quantizer->add("1\\/8", 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::setQuantizer(int q)
+{
+ quantizer->value(q);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void geMainTimer::setMeter(int beats, int bars)
+{
+ string tmp = gu_iToString(beats) + "/" + gu_iToString(bars);
+ meter->copy_label(tmp.c_str());
+}
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef GE_MAIN_TIMER_H
+#define GE_MAIN_TIMER_H
+
+
+#include <FL/Fl_Group.H>
+
+
+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);
+ void setQuantizer(int q);
+
+ /* setLock
+ Locks bpm, beter and multipliers. Used during audio recordings. */
+
+ void setLock(bool v);
+};
+
+
+#endif
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#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()
+{
+ using namespace giada::c::io;
+ startStopActionRec(true);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void geMainTransport::__cb_recInput()
+{
+ using namespace giada::c::io;
+ 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();
+}
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef GE_MAIN_TRANSPORT_H
+#define GE_MAIN_TRANSPORT_H
+
+
+#include <FL/Fl_Group.H>
+
+
+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
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#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<gdMidiInputBase*>(parent()); // parent = gdMidiInput
+ cbData.learner = this;
+ cbData.channel = ch;
+ midiDispatcher::startMidiLearn(callback, (void*)&cbData);
+ }
+ else
+ midiDispatcher::stopMidiLearn();
+}
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef GE_MIDI_LEARNER_H
+#define GE_MIDI_LEARNER_H
+
+
+#include <FL/Fl_Group.H>
+#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
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifdef WITH_VST
+
+
+#include <FL/fl_draw.H>
+#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<pluginHost::countAvailablePlugins(); i++) {
+ pluginHost::PluginInfo pi = pluginHost::getAvailablePluginInfo(i);
+ string m = pluginHost::doesPluginExist(pi.uid) ? "" : "@-";
+ string s = m + pi.name + "\t" + m + pi.manufacturerName + "\t" + m +
+ pi.category + "\t" + m + pi.format + "\t" + m + pi.uid;
+ add(s.c_str());
+ }
+
+ for (unsigned i=0; i<pluginHost::countUnknownPlugins(); i++) {
+ string s = "?\t?\t?\t?\t? " + pluginHost::getUnknownPluginInfo(i) + " ?";
+ add(s.c_str());
+ }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void gePluginBrowser::computeWidths()
+{
+ int w0, w1, w3;
+ for (int i=0; i<pluginHost::countAvailablePlugins(); i++) {
+ pluginHost::PluginInfo pi = pluginHost::getAvailablePluginInfo(i);
+ w0 = (int) fl_width(pi.name.c_str());
+ w1 = (int) fl_width(pi.manufacturerName.c_str());
+ w3 = (int) fl_width(pi.format.c_str());
+ if (w0 > 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
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifdef WITH_VST
+
+#ifndef GE_PLUGIN_BROWSER_H
+#define GE_PLUGIN_BROWSER_H
+
+
+#include <FL/Fl_Browser.H>
+
+
+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
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifdef WITH_VST
+
+
+#include <string>
+#include "../../../core/graphics.h"
+#include "../../../core/pluginHost.h"
+#include "../../../core/plugin.h"
+#include "../../../utils/gui.h"
+#include "../../../utils/log.h"
+#include "../../../glue/plugin.h"
+#include "../../elems/basics/idButton.h"
+#include "../../elems/basics/choice.h"
+#include "../../dialogs/gd_mainWindow.h"
+#include "../../dialogs/pluginList.h"
+#include "../../dialogs/pluginWindowGUI.h"
+#include "../../dialogs/pluginWindow.h"
+#include "pluginElement.h"
+
+
+extern gdMainWindow* G_MainWin;
+
+
+using std::string;
+using namespace giada;
+
+
+gePluginElement::gePluginElement(gdPluginList* gdp, Plugin* p, int X, int Y, int W)
+ : Fl_Group (X, Y, W, 20),
+ m_parentWin(gdp),
+ m_plugin (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(m_plugin->getName().c_str());
+ button->callback(cb_openPluginWindow, (void*)this);
+
+ program->callback(cb_setProgram, (void*)this);
+
+ for (int i=0; i<m_plugin->getNumPrograms(); i++)
+ program->add(gu_removeFltkChars(m_plugin->getProgramName(i)).c_str());
+
+ if (program->size() == 0) {
+ program->add("-- no programs --\0");
+ program->deactivate();
+ }
+ else
+ program->value(m_plugin->getCurrentProgram());
+
+ bypass->callback(cb_setBypass, (void*)this);
+ bypass->type(FL_TOGGLE_BUTTON);
+ bypass->value(m_plugin->isBypassed() ? 0 : 1);
+
+ shiftUp->callback(cb_shiftUp, (void*)this);
+ shiftDown->callback(cb_shiftDown, (void*)this);
+ remove->callback(cb_removePlugin, (void*)this);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void gePluginElement::cb_removePlugin (Fl_Widget* v, void* p) { ((gePluginElement*)p)->cb_removePlugin(); }
+void gePluginElement::cb_openPluginWindow(Fl_Widget* v, void* p) { ((gePluginElement*)p)->cb_openPluginWindow(); }
+void gePluginElement::cb_setBypass (Fl_Widget* v, void* p) { ((gePluginElement*)p)->cb_setBypass(); }
+void gePluginElement::cb_shiftUp (Fl_Widget* v, void* p) { ((gePluginElement*)p)->cb_shiftUp(); }
+void gePluginElement::cb_shiftDown (Fl_Widget* v, void* p) { ((gePluginElement*)p)->cb_shiftDown(); }
+void gePluginElement::cb_setProgram (Fl_Widget* v, void* p) { ((gePluginElement*)p)->cb_setProgram(); }
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void gePluginElement::cb_shiftUp()
+{
+ /*nothing to do if there's only one plugin */
+
+ if (m::pluginHost::countPlugins(m_parentWin->stackType, m_parentWin->ch) == 1)
+ return;
+
+ int pluginIndex = m::pluginHost::getPluginIndex(m_plugin->getId(),
+ m_parentWin->stackType, m_parentWin->ch);
+
+ if (pluginIndex == 0) // first of the stack, do nothing
+ return;
+
+ c::plugin::swapPlugins(m_parentWin->ch, pluginIndex, pluginIndex-1, m_parentWin->stackType);
+ m_parentWin->refreshList();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void gePluginElement::cb_shiftDown()
+{
+ /*nothing to do if there's only one plugin */
+
+ if (m::pluginHost::countPlugins(m_parentWin->stackType, m_parentWin->ch) == 1)
+ return;
+
+ unsigned pluginIndex = m::pluginHost::getPluginIndex(m_plugin->getId(), m_parentWin->stackType, m_parentWin->ch);
+ unsigned stackSize = (m::pluginHost::getStack(m_parentWin->stackType, m_parentWin->ch))->size();
+
+ if (pluginIndex == stackSize-1) // last one in the stack, do nothing
+ return;
+
+ c::plugin::swapPlugins(m_parentWin->ch, pluginIndex, pluginIndex+1, m_parentWin->stackType);
+ m_parentWin->refreshList();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void gePluginElement::cb_removePlugin()
+{
+ /* Any subwindow linked to the plugin must be destroyed first. The
+ pluginWindow has id = id_plugin + 1, because id=0 is reserved for the parent
+ window 'add plugin' (i.e. this).*/
+
+ m_parentWin->delSubWindow(m_plugin->getId() + 1);
+ c::plugin::freePlugin(m_parentWin->ch, m_plugin->getId(), m_parentWin->stackType);
+ m_parentWin->refreshList();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void gePluginElement::cb_openPluginWindow()
+{
+ /* The new pluginWindow has id = id_plugin + 1, because id=0 is reserved for
+ the parent window 'add plugin' (i.e. this). */
+
+ int pwid = m_plugin->getId() + 1;
+
+ gdWindow* w;
+ if (m_plugin->hasEditor()) {
+ if (m_plugin->isEditorOpen()) {
+ gu_log("[gePluginElement::cb_openPluginWindow] Plug-in has editor but it's already visible\n");
+ m_parentWin->getChild(pwid)->show(); // Raise it to top
+ return;
+ }
+ gu_log("[gePluginElement::cb_openPluginWindow] Plug-in has editor, window id=%d\n", pwid);
+ w = new gdPluginWindowGUI(m_plugin);
+ }
+ else {
+ w = new gdPluginWindow(m_plugin);
+ gu_log("[gePluginElement::cb_openPluginWindow] Plug-in has no editor, window id=%d\n", pwid);
+ }
+
+ if (m_parentWin->hasWindow(pwid))
+ m_parentWin->delSubWindow(pwid);
+ w->setId(pwid);
+ m_parentWin->addSubWindow(w);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void gePluginElement::cb_setBypass()
+{
+ m_plugin->toggleBypass();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void gePluginElement::cb_setProgram()
+{
+ c::plugin::setProgram(m_plugin, program->value());
+}
+
+
+#endif // #ifdef WITH_VST
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifdef WITH_VST
+
+
+#ifndef GE_PLUGIN_ELEMENT_H
+#define GE_PLUGIN_ELEMENT_H
+
+
+#include <FL/Fl_Group.H>
+
+
+class gdPluginList;
+class Plugin;
+class geIdButton;
+class geChoice;
+
+
+class gePluginElement : public Fl_Group
+{
+private:
+
+ gdPluginList* m_parentWin;
+ Plugin* m_plugin;
+
+ 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;
+
+ gePluginElement(gdPluginList* gdp, Plugin* p, int x, int y, int w);
+};
+
+#endif
+
+#endif // #ifdef WITH_VST
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#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
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifdef WITH_VST
+
+
+#ifndef GE_PLUGIN_PARAMETER_H
+#define GE_PLUGIN_PARAMETER_H
+
+
+#include <FL/Fl_Group.H>
+
+
+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
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <FL/Fl.H>
+#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"
+
+
+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()
+{
+ using namespace giada::u;
+
+ input->value(gu_fToString(math::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()
+{
+ using namespace giada::c;
+
+ if (Fl::event() == FL_DRAG)
+ channel::setBoost(ch, dial->value());
+ else
+ if (Fl::event() == FL_RELEASE) {
+ channel::setBoost(ch, dial->value());
+ static_cast<gdSampleEditor*>(window())->waveTools->updateWaveform();
+ }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void geBoostTool::cb_setBoostNum()
+{
+ using namespace giada;
+
+ c::channel::setBoost(ch, u::math::dBtoLinear(atof(input->value())));
+ static_cast<gdSampleEditor*>(window())->waveTools->updateWaveform();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void geBoostTool::cb_normalize()
+{
+ using namespace giada;
+
+ float val = m::wfx::normalizeSoft(*ch->wave);
+ c::channel::setBoost(ch, val); // it's like a fake user moving the dial
+ static_cast<gdSampleEditor*>(window())->waveTools->updateWaveform();
+}
+
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef GE_BOOST_TOOL_H
+#define GE_BOOST_TOOL_H
+
+
+#include <FL/Fl_Group.H>
+
+
+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
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <FL/Fl.H>
+#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;
+using namespace giada;
+
+
+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()
+{
+ c::channel::setPanning(ch, dial->value());
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void gePanTool::__cb_panReset()
+{
+ c::channel::setPanning(ch, 0.5f);
+}
\ No newline at end of file
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef GE_PAN_TOOL_H
+#define GE_PAN_TOOL_H
+
+
+#include <FL/Fl_Group.H>
+
+
+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
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <FL/Fl.H>
+#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;
+
+
+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()
+{
+ c::channel::setPitch(ch, dial->value());
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void gePitchTool::__cb_setPitchNum()
+{
+ c::channel::setPitch(ch, atof(input->value()));
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void gePitchTool::__cb_setPitchHalf()
+{
+ c::channel::setPitch(ch, dial->value()/2);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void gePitchTool::__cb_setPitchDouble()
+{
+ c::channel::setPitch(ch, dial->value()*2);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void gePitchTool::__cb_setPitchToBar()
+{
+ // TODO - opaque channel's count
+ c::channel::setPitch(ch, (ch->getEnd()) / (float) m::clock::getFramesInBar());
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void gePitchTool::__cb_setPitchToSong()
+{
+ // TODO - opaque channel's count
+ c::channel::setPitch(ch, ch->getEnd() / (float) m::clock::getFramesInLoop());
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void gePitchTool::__cb_resetPitch()
+{
+ c::channel::setPitch(ch, G_DEFAULT_PITCH);
+}
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef GE_PITCH_TOOL_H
+#define GE_PITCH_TOOL_H
+
+
+#include <FL/Fl_Group.H>
+
+
+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
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <FL/Fl.H>
+#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<gdSampleEditor*>(window())->waveTools->updateWaveform(); // TODO - glue's business!
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void geRangeTool::__cb_resetStartEnd()
+{
+ sampleEditor::setBeginEnd(m_ch, 0, m_ch->wave->getSize() - 1);
+ static_cast<gdSampleEditor*>(window())->waveTools->updateWaveform(); // TODO - glue's business!
+}
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef GE_RANGE_TOOL_H
+#define GE_RANGE_TOOL_H
+
+
+#include <FL/Fl_Group.H>
+
+
+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
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <cstdlib>
+#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->shift).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->shift).c_str());
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void geShiftTool::shift(int f)
+{
+ if (m_ch->isPlaying())
+ gdAlert("Can't shift sample while playing.");
+ else
+ sampleEditor::shift(m_ch, f);
+}
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef GE_SHIFT_TOOL_H
+#define GE_SHIFT_TOOL_H
+
+
+#include <FL/Fl_Group.H>
+
+
+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
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <cmath>
+#include <cstdlib>
+#include <FL/Fl_Pack.H>
+#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()
+{
+ using namespace giada::u;
+
+ string tmp;
+ float dB = math::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()
+{
+ using namespace giada;
+
+ c::channel::setVolume(ch, dial->value(), false, true);
+ refresh();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void geVolumeTool::__cb_setVolumeNum()
+{
+ using namespace giada;
+
+ float value = pow(10, (atof(input->value()) / 20)); // linear = 10^(dB/20)
+ c::channel::setVolume(ch, value, false, true);
+ dial->value(ch->guiChannel->vol->value());
+}
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef GE_VOLUME_TOOL_H
+#define GE_VOLUME_TOOL_H
+
+
+#include <FL/Fl_Group.H>
+
+
+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
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <cstdint>
+#include <FL/Fl_Menu_Item.H>
+#include <FL/Fl_Menu_Button.H>
+#include "../../../core/sampleChannel.h"
+#include "../../../core/waveFx.h"
+#include "../../../glue/sampleEditor.h"
+#include "../basics/boxtypes.h"
+#include "../../../core/const.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<geWaveTools*>(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 == ChannelStatus::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;
+}
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef GE_WAVE_TOOLS_H
+#define GE_WAVE_TOOLS_H
+
+
+#include <FL/Fl_Scroll.H>
+
+
+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
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <cassert>
+#include <cmath>
+#include <FL/fl_draw.H>
+#include <FL/Fl_Menu_Button.H>
+#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<m_data.size; i++) {
+
+ /* Scan the original waveform in chunks [pc, pn]. */
+
+ int pc = i * m_ratio; // current point TODO - int until we switch to uint32_t for Wave size...
+ int pn = (i+1) * m_ratio; // next point TODO - int until we switch to uint32_t for Wave size...
+
+ float peaksup = 0.0f;
+ float peakinf = 0.0f;
+
+ for (int k=pc; k<pn; k++) { // TODO - int until we switch to uint32_t for Wave size...
+
+ if (k >= wave->getSize())
+ continue;
+
+ /* Compute average of stereo signal. */
+
+ float avg = 0.0f;
+ float* frame = wave->getFrame(k);
+ for (int j=0; j<wave->getChannels(); 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<to; i++) {
+ if (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->trackerPreview) + 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<gdSampleEditor*>(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 * G_GUI_ZOOM_FACTOR : m_data.size / G_GUI_ZOOM_FACTOR))
+ 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();
+}
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef GE_WAVEFORM_H
+#define GE_WAVEFORM_H
+
+
+#include <vector>
+#include <FL/Fl_Widget.H>
+
+
+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<int> 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
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <cmath>
+#include <FL/fl_draw.H>
+#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);
+}
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef GE_SOUND_METER_H
+#define GE_SOUND_METER_H
+
+
+#include <FL/Fl_Box.H>
+
+
+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
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <pthread.h>
+#if defined(__linux__) || defined(__APPLE__)
+ #include <unistd.h>
+#endif
+#include <FL/Fl.H>
+#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 "utils/time.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)
+{
+ using namespace giada;
+
+ if (m::kernelAudio::getStatus())
+ while (!G_quit) {
+ gu_refreshUI();
+ u::time::sleep(G_GUI_REFRESH_RATE);
+ }
+ pthread_exit(nullptr);
+ return 0;
+}
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#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.
+TODO temporarily disabled: it does not work. */
+
+//void cocoa_setWindowSize(void *p, int w, int h);
+
+
+#endif
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+ #import <Foundation/Foundation.h>
+ #import <Cocoa/Cocoa.h>
+ #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)];
+}
+*/
\ No newline at end of file
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#if defined(_WIN32) // getcwd (unix) or __getcwd (win)
+ #include <direct.h>
+ #include <windows.h>
+#else
+ #include <unistd.h>
+#endif
+
+#include <cstdarg>
+#include <sys/stat.h> // stat (gu_dirExists)
+#include <errno.h>
+#include <cstdlib>
+#ifdef __APPLE__ // our Clang still doesn't know about cstdint (c++11 stuff)
+ #include <stdint.h>
+#else
+ #include <cstdint>
+#endif
+#include <string>
+#include <cstring>
+#include <climits>
+#ifdef __APPLE__
+ #include <libgen.h> // basename unix
+ #include <pwd.h> // 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);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+bool gu_isRootDir(const std::string& s)
+{
+ if (s == "")
+ return false;
+
+#ifdef G_OS_WINDOWS
+
+ return s.length() <= 3 && s[1] == ':'; /* X: or X:\ */
+
+#else
+
+ return s == G_SLASH_STR;
+
+#endif
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+std::string gu_getUpDir(const std::string& s)
+{
+#ifdef G_OS_WINDOWS
+
+ /* If root, let the user browse the drives list by returning "". */
+ if (gu_isRootDir(s))
+ return "";
+
+#endif
+
+ return s.substr(0, s.find_last_of(G_SLASH_STR)) + G_SLASH_STR;
+}
\ No newline at end of file
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef G_UTILS_FS_H
+#define G_UTILS_FS_H
+
+
+#include <string>
+
+
+bool gu_fileExists(const std::string& path);
+bool gu_dirExists(const std::string& path);
+bool gu_isDir(const std::string& path);
+
+/* isRootDir
+Tells whether 's' is '/' on Unix or '[X]:\' on Windows. */
+
+bool gu_isRootDir(const std::string& s);
+
+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);
+
+/* gu_getUpDir
+Returns the upper directory:
+/path/to/my/directory -> /path/to/my/ */
+
+std::string gu_getUpDir(const std::string& s);
+
+
+#endif
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <string>
+#include <FL/Fl.H>
+#include <FL/fl_draw.H>
+#if defined(_WIN32)
+ #include "../ext/resource.h"
+#elif defined(__linux__)
+ #include <X11/xpm.h>
+#endif
+#include "../core/mixer.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/actionEditor/baseActionEditor.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;
+using namespace giada::m;
+using namespace giada::v;
+
+
+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<gdSampleEditor*>(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 (const Channel* ch : mixer::channels)
+ ch->guiChannel->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->mainTimer->setQuantizer(clock::getQuantize());
+
+ 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()
+{
+ gdBaseActionEditor* ae = static_cast<gdBaseActionEditor*>(G_MainWin->getChild(WID_ACTION_EDITOR));
+ if (ae != nullptr)
+ ae->rebuild();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+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;
+}
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef G_UTILS_GUI_H
+#define G_UTILS_GUI_H
+
+
+#include <string>
+
+
+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
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <cstdio>
+#include <cstdarg>
+#include <string>
+#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);
+}
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#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
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <cmath>
+#include "math.h"
+
+
+namespace giada {
+namespace u {
+namespace math
+{
+float linearToDB(float f)
+{
+ return 20 * std::log10(f);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+int quantize(int x, int step)
+{
+ /* Source:
+ https://en.wikipedia.org/wiki/Quantization_(signal_processing)#Rounding_example */
+ return step * floor((x / (float) step) + 0.5f);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+float dBtoLinear(float f)
+{
+ return std::pow(10, f/20.0f);
+}
+
+}}} // giada::u::math::
\ No newline at end of file
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef G_UTILS_MATH_H
+#define G_UTILS_MATH_H
+
+
+namespace giada {
+namespace u {
+namespace math
+{
+float linearToDB(float f);
+float dBtoLinear(float f);
+int quantize(int x, int step);
+
+/* map (template)
+Maps 'x' in range [a, b] to a new range [w, z]. Source:
+ https://en.wikipedia.org/wiki/Linear_equation#Two-point_form*/
+
+template <typename Tin, typename Tout>
+Tout map(Tin x, Tin a, Tin b, Tout w, Tout z)
+{
+ return (((x - a) / (float) (b - a)) * (z - w)) + w;
+}
+
+}}} // giada::u::math::
+
+
+#endif
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <iomanip>
+#include <cstdarg>
+#include <climits>
+#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). */
+
+ va_start(args, format);
+ size_t size = vsnprintf(nullptr, 0, format, args) + 1;
+ va_end(args);
+
+ /* Create a new temporary char array to hold the new expanded string. */
+
+ std::unique_ptr<char[]> 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<string>* 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);
+}
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef G_UTILS_STRING_H
+#define G_UTILS_STRING_H
+
+
+#include <string>
+#include <vector>
+#include <sstream>
+#include "../core/const.h"
+
+
+template <typename T>
+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<std::string>* v);
+
+std::string gu_fToString(float f, int precision);
+
+std::string gu_format(const char* format, ...);
+
+
+#endif
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <chrono>
+#include <thread>
+#include "time.h"
+
+
+namespace giada {
+namespace u {
+namespace time
+{
+void sleep(int millisecs)
+{
+ std::this_thread::sleep_for(std::chrono::milliseconds(millisecs));
+}
+}}}; // giada::u::time::
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef G_UTILS_TIME_H
+#define G_UTILS_TIME_H
+
+
+namespace giada {
+namespace u {
+namespace time
+{
+void sleep(int millisecs);
+}}};
+
+
+#endif
\ No newline at end of file
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include "../core/const.h"
+#ifdef G_OS_MAC
+ #include <RtMidi.h>
+#else
+ #include <rtmidi/RtMidi.h>
+#endif
+#include <sndfile.h>
+#include "../deps/rtaudio-mod/RtAudio.h"
+#include "ver.h"
+
+
+using std::string;
+
+
+namespace giada {
+namespace u {
+namespace ver
+{
+string getLibsndfileVersion()
+{
+ char buffer[128];
+ sf_command(NULL, SFC_GET_LIB_VERSION, buffer, sizeof(buffer));
+ return string(buffer);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+string getRtAudioVersion()
+{
+#ifdef TESTS
+ return "";
+#else
+ return RtAudio::getVersion();
+#endif
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+string getRtMidiVersion()
+{
+#ifdef TESTS
+ return "";
+#else
+ return RtMidi::getVersion();
+#endif
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+bool isLess(int a1, int b1, int c1, int a2, int b2, int c2)
+{
+ const int s = 3;
+ int v1[s] = {a1, b1, c1};
+ int v2[s] = {a2, b2, c2};
+
+ for (int i=0; i<s; i++) {
+ if (v1[i] == v2[i])
+ continue;
+ return v1[i] < v2[i];
+ }
+ return false;
+}
+
+}}}; // giada::u::ver::
\ No newline at end of file
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef G_UTILS_VER_H
+#define G_UTILS_VER_H
+
+
+#include <string>
+
+
+namespace giada {
+namespace u {
+namespace ver
+{
+std::string getLibsndfileVersion();
+std::string getRtAudioVersion();
+std::string getRtMidiVersion();
+
+bool isLess(int a1, int b1, int c1, int a2, int b2, int c2);
+}}}; // giada::u::ver::
+
+#endif
\ No newline at end of file
--- /dev/null
+#include <memory>
+#include "../src/core/audioBuffer.h"
+#include <catch.hpp>
+
+
+TEST_CASE("AudioBuffer")
+{
+ using namespace giada::m;
+
+ static const int BUFFER_SIZE = 4096;
+
+ /* 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. */
+
+ AudioBuffer buffer;
+ buffer.alloc(BUFFER_SIZE, 2);
+
+ SECTION("test allocation")
+ {
+ SECTION("test mono")
+ {
+ buffer.alloc(BUFFER_SIZE, 1);
+ REQUIRE(buffer.countFrames() == BUFFER_SIZE);
+ REQUIRE(buffer.countSamples() == BUFFER_SIZE);
+ REQUIRE(buffer.countChannels() == 1);
+ }
+
+ SECTION("test stereo")
+ {
+ REQUIRE(buffer.countFrames() == BUFFER_SIZE);
+ REQUIRE(buffer.countSamples() == BUFFER_SIZE * 2);
+ REQUIRE(buffer.countChannels() == 2);
+ }
+
+ SECTION("test odd channels count")
+ {
+ buffer.alloc(BUFFER_SIZE, 7);
+ REQUIRE(buffer.countFrames() == BUFFER_SIZE);
+ REQUIRE(buffer.countSamples() == BUFFER_SIZE * 7);
+ REQUIRE(buffer.countChannels() == 7);
+ }
+
+ buffer.free();
+
+ REQUIRE(buffer[0] == nullptr);
+ REQUIRE(buffer.countFrames() == 0);
+ REQUIRE(buffer.countSamples() == 0);
+ REQUIRE(buffer.countChannels() == 0);
+ }
+
+ SECTION("test clear all")
+ {
+ buffer.clear();
+ for (int i=0; i<buffer.countFrames(); i++)
+ for (int k=0; k<buffer.countChannels(); k++)
+ REQUIRE(buffer[i][k] == 0.0f);
+ buffer.free();
+ }
+
+ SECTION("test clear range")
+ {
+ for (int i=0; i<buffer.countFrames(); i++)
+ for (int k=0; k<buffer.countChannels(); k++)
+ buffer[i][k] = 1.0f;
+
+ buffer.clear(5, 6);
+
+ for (int k=0; k<buffer.countChannels(); k++)
+ REQUIRE(buffer[5][k] == 0.0f);
+
+ buffer.free();
+ }
+
+ SECTION("test copy")
+ {
+ int size = BUFFER_SIZE * 2;
+ float* data = new float[size];
+ for (int i=0; i<size; i++)
+ data[i] = (float) i;
+
+ SECTION("test full copy")
+ {
+ buffer.copyData(data, BUFFER_SIZE);
+
+ REQUIRE(buffer[0][0] == 0.0f);
+ REQUIRE(buffer[16][0] == 32.0f);
+ REQUIRE(buffer[32][0] == 64.0f);
+ REQUIRE(buffer[1024][0] == 2048.0f);
+ }
+
+ SECTION("test copy frame")
+ {
+ buffer.copyFrame(16, &data[32]);
+ REQUIRE(buffer[16][0] == 32.0f);
+ REQUIRE(buffer[16][1] == 33.0f);
+ }
+
+ delete[] data;
+ }
+}
--- /dev/null
+#include "../src/core/const.h"
+#include "../src/core/conf.h"
+#include <catch.hpp>
+
+
+using std::string;
+using namespace giada::m;
+
+
+TEST_CASE("conf")
+{
+ 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::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::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);
+ }
+}
--- /dev/null
+#define CATCH_CONFIG_MAIN
+#define CATCH_CONFIG_FAST_COMPILE
+#include <catch.hpp>
+
+/* There's no main.cpp in the test suite and the following global vars are
+unfortunately defined there. Let's fake them. */
+
+class gdMainWindow* G_MainWin;
+bool G_quit;
\ No newline at end of file
--- /dev/null
+#include "../src/core/const.h"
+#include "../src/core/midiMapConf.h"
+#include <catch.hpp>
+
+
+using std::string;
+using namespace giada::m;
+
+
+TEST_CASE("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
+}
--- /dev/null
+#include "../src/core/patch.h"
+#include "../src/core/const.h"
+#include "../src/core/types.h"
+#include <catch.hpp>
+
+
+using std::string;
+using std::vector;
+using namespace giada;
+using namespace giada::m;
+
+
+TEST_CASE("patch")
+{
+ string filename = "./test-patch.json";
+
+ SECTION("test write")
+ {
+ patch::action_t action0;
+ patch::action_t action1;
+ patch::channel_t channel1;
+ patch::column_t column;
+#ifdef WITH_VST
+ patch::plugin_t plugin1;
+ patch::plugin_t plugin2;
+ patch::plugin_t plugin3;
+#endif
+
+ action0.type = 0;
+ action0.frame = 50000;
+ action0.fValue = 0.3f;
+ action0.iValue = 1000;
+ action1.type = 2;
+ action1.frame = 589;
+ action1.fValue = 1.0f;
+ action1.iValue = 130;
+ channel1.actions.push_back(action0);
+ channel1.actions.push_back(action1);
+
+#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 = static_cast<int>(ChannelType::SAMPLE);
+ channel1.index = 666;
+ channel1.size = G_GUI_CHANNEL_H_1;
+ channel1.column = 0;
+ channel1.mute = 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 == static_cast<int>(ChannelType::SAMPLE));
+ REQUIRE(channel0.index == 666);
+ REQUIRE(channel0.size == G_GUI_CHANNEL_H_1);
+ REQUIRE(channel0.column == 0);
+ REQUIRE(channel0.mute == 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
+ }
+}
--- /dev/null
+#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.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
--- /dev/null
+#include "../src/core/recorder.h"
+#include "../src/core/const.h"
+#include <catch.hpp>
+
+
+using std::string;
+using namespace giada::m;
+
+
+TEST_CASE("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_KEYPRESS, 0, 1, 0.5f);
+ recorder::rec(0, G_ACTION_KEYREL, 80, 1, 0.5f);
+ recorder::rec(0, G_ACTION_KEYPRESS, 200, 1, 0.5f);
+ recorder::rec(0, G_ACTION_KEYREL, 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_KEYPRESS | G_ACTION_KEYREL, 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_KEYPRESS);
+ REQUIRE(recorder::global.at(1).at(0)->frame == 500);
+ REQUIRE(recorder::global.at(1).at(0)->type == G_ACTION_KEYREL);
+ }
+
+ SECTION("Test overdub, left overlap")
+ {
+ recorder::rec(0, G_ACTION_KEYPRESS, 100, 1, 0.5f);
+ recorder::rec(0, G_ACTION_KEYREL, 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_KEYPRESS | G_ACTION_KEYREL, 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_KEYPRESS);
+ REQUIRE(recorder::global.at(1).at(0)->frame == 300);
+ REQUIRE(recorder::global.at(1).at(0)->type == G_ACTION_KEYREL);
+ }
+
+ SECTION("Test overdub, right overlap")
+ {
+ recorder::rec(0, G_ACTION_KEYPRESS, 000, 1, 0.5f);
+ recorder::rec(0, G_ACTION_KEYREL, 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_KEYPRESS | G_ACTION_KEYREL, 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_KEYPRESS);
+ REQUIRE(recorder::global.at(1).at(0)->frame == 84);
+ REQUIRE(recorder::global.at(1).at(0)->type == G_ACTION_KEYREL);
+
+ REQUIRE(recorder::global.at(2).at(0)->frame == 100);
+ REQUIRE(recorder::global.at(2).at(0)->type == G_ACTION_KEYPRESS);
+ REQUIRE(recorder::global.at(3).at(0)->frame == 500);
+ REQUIRE(recorder::global.at(3).at(0)->type == G_ACTION_KEYREL);
+ }
+
+ SECTION("Test overdub, hole diggin'")
+ {
+ recorder::rec(0, G_ACTION_KEYPRESS, 0, 1, 0.5f);
+ recorder::rec(0, G_ACTION_KEYREL, 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_KEYPRESS | G_ACTION_KEYREL, 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_KEYPRESS);
+ REQUIRE(recorder::global.at(1).at(0)->frame == 84);
+ REQUIRE(recorder::global.at(1).at(0)->type == G_ACTION_KEYREL);
+
+ REQUIRE(recorder::global.at(2).at(0)->frame == 100);
+ REQUIRE(recorder::global.at(2).at(0)->type == G_ACTION_KEYPRESS);
+ REQUIRE(recorder::global.at(3).at(0)->frame == 300);
+ REQUIRE(recorder::global.at(3).at(0)->type == G_ACTION_KEYREL);
+ }
+
+ SECTION("Test overdub, cover all")
+ {
+ recorder::rec(0, G_ACTION_KEYPRESS, 0, 1, 0.5f);
+ recorder::rec(0, G_ACTION_KEYREL, 100, 1, 0.5f);
+ recorder::rec(0, G_ACTION_KEYPRESS, 120, 1, 0.5f);
+ recorder::rec(0, G_ACTION_KEYREL, 200, 1, 0.5f);
+ recorder::rec(0, G_ACTION_KEYPRESS, 220, 1, 0.5f);
+ recorder::rec(0, G_ACTION_KEYREL, 300, 1, 0.5f);
+
+ /* Overdub all existing actions. Expected result: a single composite one. */
+ recorder::startOverdub(0, G_ACTION_KEYPRESS | G_ACTION_KEYREL, 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_KEYPRESS);
+ REQUIRE(recorder::global.at(1).at(0)->frame == 500);
+ REQUIRE(recorder::global.at(1).at(0)->type == G_ACTION_KEYREL);
+ }
+
+ SECTION("Test overdub, null loop")
+ {
+ recorder::rec(0, G_ACTION_KEYPRESS, 0, 1, 0.5f);
+ recorder::rec(0, G_ACTION_KEYREL, 500, 1, 0.5f);
+
+ /* A null loop is a loop that begins and ends on the very same frame. */
+ recorder::startOverdub(0, G_ACTION_KEYPRESS | G_ACTION_KEYREL, 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_KEYPRESS);
+ REQUIRE(recorder::global.at(1).at(0)->frame == 284);
+ REQUIRE(recorder::global.at(1).at(0)->type == G_ACTION_KEYREL);
+ }
+
+ 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_KEYPRESS, 200, 1, 0.5f);
+ recorder::rec(0, G_ACTION_KEYREL, 300, 1, 0.5f);
+
+ recorder::startOverdub(0, G_ACTION_KEYPRESS | G_ACTION_KEYREL, 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);
+ }
+}
--- /dev/null
+#include "../src/core/sampleChannel.h"
+#include "../src/core/wave.h"
+#include "../src/core/waveManager.h"
+#include <catch.hpp>
+
+
+using namespace giada;
+using namespace giada::m;
+
+
+TEST_CASE("sampleChannel")
+{
+ const int BUFFER_SIZE = 1024;
+
+ std::vector<ChannelMode> modes = { ChannelMode::LOOP_BASIC,
+ ChannelMode::LOOP_ONCE, ChannelMode::LOOP_REPEAT,
+ ChannelMode::LOOP_ONCE_BAR, ChannelMode::SINGLE_BASIC,
+ ChannelMode::SINGLE_PRESS, ChannelMode::SINGLE_RETRIG,
+ ChannelMode::SINGLE_ENDLESS };
+
+ SampleChannel ch(false, BUFFER_SIZE);
+ Wave* w;
+ waveManager::create("tests/resources/test.wav", &w);
+ ch.pushWave(w);
+
+ SECTION("push wave")
+ {
+ REQUIRE(ch.status == ChannelStatus::OFF);
+ REQUIRE(ch.wave == w);
+ REQUIRE(ch.begin == 0);
+ REQUIRE(ch.end == w->getSize() - 1);
+ REQUIRE(ch.name == w->getBasename());
+ }
+
+ SECTION("begin/end")
+ {
+ ch.setBegin(-100);
+
+ REQUIRE(ch.getBegin() == 0);
+ REQUIRE(ch.tracker == 0);
+ REQUIRE(ch.trackerPreview == 0);
+
+ ch.setBegin(100000);
+
+ REQUIRE(ch.getBegin() == w->getSize());
+ REQUIRE(ch.tracker == w->getSize());
+ REQUIRE(ch.trackerPreview == w->getSize());
+
+ ch.setBegin(16);
+
+ REQUIRE(ch.getBegin() == 16);
+ REQUIRE(ch.tracker == 16);
+ REQUIRE(ch.trackerPreview == 16);
+
+ ch.setEnd(0);
+
+ REQUIRE(ch.getEnd() == 17);
+
+ ch.setEnd(100000);
+
+ REQUIRE(ch.getEnd() == w->getSize() - 1);
+
+ ch.setEnd(32);
+
+ REQUIRE(ch.getEnd() == 32);
+
+ ch.setBegin(64);
+
+ REQUIRE(ch.getBegin() == 31);
+ }
+
+ SECTION("pitch")
+ {
+ ch.setPitch(40.0f);
+
+ REQUIRE(ch.getPitch() == G_MAX_PITCH);
+
+ ch.setPitch(-2.0f);
+
+ REQUIRE(ch.getPitch() == G_MIN_PITCH);
+
+ ch.setPitch(0.8f);
+
+ REQUIRE(ch.getPitch() == 0.8f);
+ }
+
+ SECTION("position")
+ {
+ REQUIRE(ch.getPosition() == -1); // Initially OFF
+
+ ch.status = ChannelStatus::PLAY;
+ ch.tracker = 1000;
+
+ REQUIRE(ch.getPosition() == 1000);
+
+ ch.begin = 700;
+
+ REQUIRE(ch.getPosition() == 300);
+ }
+
+ SECTION("empty")
+ {
+ ch.empty();
+
+ REQUIRE(ch.status == ChannelStatus::EMPTY);
+ REQUIRE(ch.begin == 0);
+ REQUIRE(ch.end == 0);
+ REQUIRE(ch.tracker == 0);
+ REQUIRE(ch.volume == G_DEFAULT_VOL);
+ REQUIRE(ch.boost == G_DEFAULT_BOOST);
+ REQUIRE(ch.hasActions == false);
+ REQUIRE(ch.wave == nullptr);
+ }
+
+ SECTION("can record audio")
+ {
+ REQUIRE(ch.canInputRec() == false); // Can't record if not armed
+
+ ch.armed = true;
+
+ REQUIRE(ch.canInputRec() == false); // Can't record with Wave in it
+
+ ch.empty();
+
+ REQUIRE(ch.canInputRec() == true);
+ }
+
+ /* TODO - fillBuffer, isAnyLoopMode, isAnySingleMode, isOnLastFrame */
+}
--- /dev/null
+#include "../src/core/sampleChannel.h"
+#include "../src/core/sampleChannelProc.h"
+#include "../src/core/wave.h"
+#include "../src/core/waveManager.h"
+#include <catch.hpp>
+
+
+using namespace giada;
+using namespace giada::m;
+
+
+TEST_CASE("sampleChannelProc")
+{
+ const int BUFFER_SIZE = 1024;
+
+ std::vector<ChannelMode> modes = { ChannelMode::LOOP_BASIC,
+ ChannelMode::LOOP_ONCE, ChannelMode::LOOP_REPEAT,
+ ChannelMode::LOOP_ONCE_BAR, ChannelMode::SINGLE_BASIC,
+ ChannelMode::SINGLE_PRESS, ChannelMode::SINGLE_RETRIG,
+ ChannelMode::SINGLE_ENDLESS };
+
+ Wave* w;
+ SampleChannel ch(false, BUFFER_SIZE);
+ waveManager::create("tests/resources/test.wav", &w);
+
+ REQUIRE(ch.status == ChannelStatus::EMPTY);
+ REQUIRE(ch.mode == ChannelMode::SINGLE_BASIC);
+
+ SECTION("buffer")
+ {
+ SECTION("prepare")
+ {
+ /* With no wave data in it. */
+ sampleChannelProc::prepareBuffer(&ch, /*running=*/false);
+
+ REQUIRE(ch.tracker == 0);
+
+ /* With data, stopped. */
+ ch.pushWave(w);
+ sampleChannelProc::prepareBuffer(&ch, /*running=*/false);
+
+ REQUIRE(ch.tracker == 0);
+
+ /* With data, playing. */
+ ch.status = ChannelStatus::PLAY;
+ sampleChannelProc::prepareBuffer(&ch, /*running=*/false);
+
+ REQUIRE(ch.tracker == BUFFER_SIZE);
+ }
+
+ SECTION("fill")
+ {
+ ch.pushWave(w);
+
+ /* Zero offset. */
+ REQUIRE(ch.fillBuffer(ch.buffer, 0, 0) == BUFFER_SIZE);
+
+ /* Non-zero offset. */
+ REQUIRE(ch.fillBuffer(ch.buffer, 0, 666) == BUFFER_SIZE - 666);
+
+ /* At the end of the waveform. */
+ REQUIRE(ch.fillBuffer(ch.buffer, ch.end - 666, 0) == (ch.end - (ch.end - 666)) + 1);
+ }
+ }
+
+ SECTION("statuses")
+ {
+ ch.pushWave(w);
+
+ SECTION("start from OFF")
+ {
+ for (ChannelMode mode : modes) {
+ ch.mode = mode;
+ ch.status = ChannelStatus::OFF;
+ sampleChannelProc::start(&ch, 0, /*doQuantize=*/false, /*velocity=*/0);
+
+ if (ch.isAnyLoopMode())
+ REQUIRE(ch.status == ChannelStatus::WAIT);
+ else
+ REQUIRE(ch.status == ChannelStatus::PLAY);
+ }
+ }
+
+ SECTION("start from PLAY")
+ {
+ for (ChannelMode mode : modes) {
+ ch.mode = mode;
+ ch.status = ChannelStatus::PLAY;
+ ch.tracker = 16; // simulate processing
+ sampleChannelProc::start(&ch, 0, /*doQuantize=*/false, /*velocity=*/0);
+
+ if (ch.mode == ChannelMode::SINGLE_RETRIG) {
+ REQUIRE(ch.status == ChannelStatus::PLAY);
+ REQUIRE(ch.tracker == 0);
+ }
+ else
+ if (ch.isAnyLoopMode() || ch.mode == ChannelMode::SINGLE_ENDLESS)
+ REQUIRE(ch.status == ChannelStatus::ENDING);
+ else
+ if (ch.mode == ChannelMode::SINGLE_BASIC) {
+ REQUIRE(ch.status == ChannelStatus::OFF);
+ REQUIRE(ch.tracker == 0);
+ }
+ }
+ }
+
+ SECTION("start from WAIT")
+ {
+ for (ChannelMode mode : modes) {
+ ch.mode = mode;
+ ch.status = ChannelStatus::WAIT;
+ sampleChannelProc::start(&ch, 0, /*doQuantize=*/false, /*velocity=*/0);
+
+ REQUIRE(ch.status == ChannelStatus::OFF);
+ }
+ }
+
+ SECTION("start from ENDING")
+ {
+ for (ChannelMode mode : modes) {
+ ch.mode = mode;
+ ch.status = ChannelStatus::ENDING;
+ sampleChannelProc::start(&ch, 0, /*doQuantize=*/false, /*velocity=*/0);
+
+ REQUIRE(ch.status == ChannelStatus::PLAY);
+ }
+ }
+
+ SECTION("stop from PLAY")
+ {
+ for (ChannelMode mode : modes) {
+ ch.mode = mode;
+ ch.status = ChannelStatus::PLAY;
+ ch.tracker = 16; // simulate processing
+ sampleChannelProc::stop(&ch);
+
+ if (ch.mode == ChannelMode::SINGLE_PRESS) {
+ REQUIRE(ch.status == ChannelStatus::OFF);
+ REQUIRE(ch.tracker == 0);
+ }
+ else {
+ /* Nothing should change for other modes. */
+ REQUIRE(ch.status == ChannelStatus::PLAY);
+ REQUIRE(ch.tracker == 16);
+ }
+ }
+ }
+
+ SECTION("kill")
+ {
+ std::vector<ChannelStatus> statuses = { ChannelStatus::ENDING,
+ ChannelStatus::WAIT, ChannelStatus::PLAY, ChannelStatus::OFF,
+ ChannelStatus::EMPTY, ChannelStatus::MISSING, ChannelStatus::WRONG };
+
+ for (ChannelMode mode : modes) {
+ for (ChannelStatus status : statuses) {
+ ch.mode = mode;
+ ch.status = status;
+ ch.tracker = 16; // simulate processing
+ sampleChannelProc::kill(&ch, 0);
+
+ if (ch.status == ChannelStatus::WAIT ||
+ ch.status == ChannelStatus::PLAY ||
+ ch.status == ChannelStatus::ENDING) {
+ REQUIRE(ch.status == ChannelStatus::OFF);
+ REQUIRE(ch.tracker == 0);
+ }
+ }
+ }
+ }
+
+ SECTION("quantized start")
+ {
+ for (ChannelMode mode : modes) {
+ ch.mode = mode;
+ ch.status = ChannelStatus::OFF;
+ sampleChannelProc::start(&ch, 0, /*doQuantize=*/true, /*velocity=*/0);
+
+ if (ch.isAnyLoopMode())
+ REQUIRE(ch.status == ChannelStatus::WAIT);
+ else {
+ REQUIRE(ch.status == ChannelStatus::OFF);
+ REQUIRE(ch.qWait == true);
+ }
+ }
+ }
+ }
+
+ SECTION("stop input recordings")
+ {
+ /* Start all sample channels in any loop mode that were armed. */
+ for (ChannelMode mode : modes) {
+ ch.mode = mode;
+ ch.status = ChannelStatus::OFF;
+ ch.armed = true;
+ ch.tracker = 16;
+
+ sampleChannelProc::stopInputRec(&ch, /*globalFrame=*/666);
+
+ if (ch.isAnyLoopMode()) {
+ REQUIRE(ch.status == ChannelStatus::PLAY);
+ REQUIRE(ch.tracker == 666);
+ }
+ else {
+ REQUIRE(ch.status == ChannelStatus::OFF);
+ REQUIRE(ch.tracker == 16);
+ }
+ }
+ }
+
+ SECTION("rewind by sequencer")
+ {
+ ch.pushWave(w);
+
+ /* Test loop modes. */
+
+ for (ChannelMode mode : modes) {
+ ch.mode = mode;
+ ch.status = ChannelStatus::PLAY;
+ ch.tracker = 16; // simulate processing
+
+ sampleChannelProc::rewindBySeq(&ch);
+
+ if (ch.isAnyLoopMode()) {
+ REQUIRE(ch.status == ChannelStatus::PLAY);
+ REQUIRE(ch.tracker == 0);
+ }
+ else {
+ REQUIRE(ch.status == ChannelStatus::PLAY);
+ REQUIRE(ch.tracker == 16);
+ }
+ }
+
+ /* Test single modes that are reading actions. */
+
+ for (ChannelMode mode : modes) {
+ ch.mode = mode;
+ ch.status = ChannelStatus::PLAY;
+ ch.tracker = 16; // simulate processing
+ ch.recStatus = ChannelStatus::PLAY;
+
+ sampleChannelProc::rewindBySeq(&ch);
+
+ if (ch.isAnySingleMode()) {
+ REQUIRE(ch.status == ChannelStatus::PLAY);
+ REQUIRE(ch.tracker == 0);
+ }
+ }
+ }
+}
--- /dev/null
+#include "../src/core/sampleChannel.h"
+#include "../src/core/sampleChannelRec.h"
+#include <catch.hpp>
+
+
+using namespace giada;
+using namespace giada::m;
+
+
+TEST_CASE("sampleChannelRec")
+{
+ const int BUFFER_SIZE = 1024;
+
+ SampleChannel ch(false, BUFFER_SIZE);
+
+ SECTION("start reading actions, don't treat recs as loop")
+ {
+ sampleChannelRec::startReadingActions(&ch, /*treatRecsAsLoops=*/false,
+ /*recsStopOnChanHalt=*/false);
+
+ REQUIRE(ch.recStatus == ChannelStatus::OFF);
+ REQUIRE(ch.readActions == true);
+ }
+
+ SECTION("start reading actions, do treat recs as loop")
+ {
+ sampleChannelRec::startReadingActions(&ch, /*treatRecsAsLoops=*/true,
+ /*recsStopOnChanHalt=*/false);
+
+ REQUIRE(ch.recStatus == ChannelStatus::WAIT);
+ REQUIRE(ch.readActions == false);
+ }
+
+ SECTION("stop reading actions")
+ {
+ /* First state: PLAY */
+ ch.recStatus = ChannelStatus::PLAY;
+
+ sampleChannelRec::stopReadingActions(&ch, /*clockRunning=*/true,
+ /*treatRecsAsLoops=*/false, /*recsStopOnChanHalt=*/false);
+
+ REQUIRE(ch.readActions == false);
+ REQUIRE(ch.recStatus == ChannelStatus::PLAY);
+
+ /* Second state: WAIT */
+ ch.recStatus = ChannelStatus::WAIT;
+
+ sampleChannelRec::stopReadingActions(&ch, /*clockRunning=*/true,
+ /*treatRecsAsLoops=*/false, /*recsStopOnChanHalt=*/false);
+
+ REQUIRE(ch.readActions == false);
+ REQUIRE(ch.recStatus == ChannelStatus::OFF);
+
+ /* Third state: WAIT */
+ ch.recStatus = ChannelStatus::ENDING;
+
+ sampleChannelRec::stopReadingActions(&ch, /*clockRunning=*/true,
+ /*treatRecsAsLoops=*/false, /*recsStopOnChanHalt=*/false);
+
+ REQUIRE(ch.readActions == false);
+ REQUIRE(ch.recStatus == ChannelStatus::PLAY);
+
+ /* Fourth state: any, but with clockRunning == false. */
+
+ sampleChannelRec::stopReadingActions(&ch, /*clockRunning=*/false,
+ /*treatRecsAsLoops=*/false, /*recsStopOnChanHalt=*/false);
+
+ REQUIRE(ch.readActions == false);
+ REQUIRE(ch.recStatus == ChannelStatus::OFF);
+ }
+
+
+ SECTION("set read actions status to false with recsStopOnChanHalt")
+ {
+ ch.status = ChannelStatus::PLAY;
+ ch.tracker = 1024;
+
+ sampleChannelRec::setReadActions(&ch, false, /*recsStopOnChanHalt=*/true);
+
+ REQUIRE(ch.readActions == false);
+ REQUIRE(ch.status == ChannelStatus::OFF);
+ REQUIRE(ch.tracker == 0);
+
+ }
+}
--- /dev/null
+#include "../src/utils/fs.h"
+#include "../src/utils/string.h"
+#include "../src/utils/math.h"
+#include "../src/utils/ver.h"
+#include <catch.hpp>
+
+
+TEST_CASE("u::fs")
+{
+ REQUIRE(gu_fileExists("README.md") == 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");
+#if defined(_WIN32)
+ REQUIRE(gu_isRootDir("C:\\") == true);
+ REQUIRE(gu_isRootDir("C:\\path\\to\\something") == false);
+ REQUIRE(gu_getUpDir("C:\\path\\to\\something") == "C:\\path\\to\\");
+ REQUIRE(gu_getUpDir("C:\\path") == "C:\\");
+ REQUIRE(gu_getUpDir("C:\\") == "");
+#else
+ REQUIRE(gu_isRootDir("/") == true);
+ REQUIRE(gu_isRootDir("/path/to/something") == false);
+ REQUIRE(gu_getUpDir("/path/to/something") == "/path/to/");
+ REQUIRE(gu_getUpDir("/path") == "/");
+ REQUIRE(gu_getUpDir("/") == "/");
+#endif
+}
+
+
+TEST_CASE("u::string")
+{
+ using std::vector;
+
+ 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");
+ REQUIRE(gu_format("I see %d men with %s hats", 5, "strange") == "I see 5 men with strange hats");
+
+ vector<std::string> 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");
+}
+
+
+TEST_CASE("::math")
+{
+ using namespace giada::u::math;
+
+ REQUIRE(map( 0.0f, 0.0f, 30.0f, 0.0f, 1.0f) == 0.0f);
+ REQUIRE(map(30.0f, 0.0f, 30.0f, 0.0f, 1.0f) == 1.0f);
+ REQUIRE(map(15.0f, 0.0f, 30.0f, 0.0f, 1.0f) == Approx(0.5f));
+}
+
+
+TEST_CASE("u::ver")
+{
+ using namespace giada::u::ver;
+
+ REQUIRE(isLess(6, 6, 6, 0, 15, 0) == false);
+ REQUIRE(isLess(0, 15, 0, 6, 6, 6) == true);
+ REQUIRE(isLess(6, 6, 6, 6, 6, 6) == false);
+ REQUIRE(isLess(6, 6, 5, 6, 6, 6) == true);
+}
\ No newline at end of file
--- /dev/null
+#include <memory>
+#include "../src/core/wave.h"
+#include <catch.hpp>
+
+
+using std::string;
+
+
+TEST_CASE("Wave")
+{
+ 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. */
+
+
+ SECTION("test allocation")
+ {
+ Wave wave;
+ wave.alloc(BUFFER_SIZE, CHANNELS, SAMPLE_RATE, BIT_DEPTH, "path/to/sample.wav");
+
+ SECTION("test basename")
+ {
+ REQUIRE(wave.getPath() == "path/to/sample.wav");
+ REQUIRE(wave.getBasename() == "sample");
+ REQUIRE(wave.getBasename(true) == "sample.wav");
+ }
+
+ SECTION("test path")
+ {
+ 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")
+ {
+ REQUIRE(wave.getPath() == "path/to/sample.wav");
+ REQUIRE(wave.getBasename() == "sample");
+ REQUIRE(wave.getBasename(true) == "sample.wav");
+ }
+ }
+}
--- /dev/null
+#include <memory>
+#include "../src/core/const.h"
+#include "../src/core/wave.h"
+#include "../src/core/waveFx.h"
+#include <catch.hpp>
+
+
+using std::string;
+using namespace giada::m;
+
+
+TEST_CASE("waveFx")
+{
+ static const int SAMPLE_RATE = 44100;
+ static const int BUFFER_SIZE = 4000;
+ static const int BIT_DEPTH = 32;
+
+ Wave waveMono;
+ Wave waveStereo;
+ waveMono.alloc(BUFFER_SIZE, 1, SAMPLE_RATE, BIT_DEPTH, "path/to/sample.wav");
+ waveStereo.alloc(BUFFER_SIZE, 2, SAMPLE_RATE, BIT_DEPTH, "path/to/sample.wav");
+
+ SECTION("test mono->stereo conversion")
+ {
+ int prevSize = waveMono.getSize();
+
+ REQUIRE(wfx::monoToStereo(waveMono) == 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) == G_RES_OK);
+ REQUIRE(waveStereo.getSize() == prevSize);
+ REQUIRE(waveStereo.getChannels() == 2);
+ }
+ }
+
+ SECTION("test silence")
+ {
+ int a = 20;
+ int b = 57;
+ wfx::silence(waveStereo, a, b);
+
+ for (int i=a; i<b; i++)
+ for (int k=0; k<waveStereo.getChannels(); k++)
+ REQUIRE(waveStereo[i][k] == 0.0f);
+
+ SECTION("test silence (mono)")
+ {
+ wfx::silence(waveMono, a, b);
+
+ for (int i=a; i<b; i++)
+ for (int k=0; k<waveMono.getChannels(); k++)
+ REQUIRE(waveMono[i][k] == 0.0f);
+ }
+ }
+
+ SECTION("test cut")
+ {
+ int a = 47;
+ int b = 210;
+ int range = b - a;
+ int prevSize = waveStereo.getSize();
+
+ REQUIRE(wfx::cut(waveStereo, a, b) == G_RES_OK);
+ REQUIRE(waveStereo.getSize() == prevSize - range);
+
+ SECTION("test cut (mono)")
+ {
+ prevSize = waveMono.getSize();
+ REQUIRE(wfx::cut(waveMono, 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, a, b) == G_RES_OK);
+ REQUIRE(waveStereo.getSize() == area);
+
+ SECTION("test trim (mono)")
+ {
+ REQUIRE(wfx::trim(waveMono, a, b) == G_RES_OK);
+ REQUIRE(waveMono.getSize() == area);
+ }
+ }
+
+ SECTION("test fade")
+ {
+ int a = 47;
+ int b = 500;
+
+ wfx::fade(waveStereo, a, b, wfx::FADE_IN);
+ wfx::fade(waveStereo, 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, a, b, wfx::FADE_IN);
+ wfx::fade(waveMono, 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, 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, a, b);
+ REQUIRE(waveMono.getFrame(a)[0] == 0.0f);
+ REQUIRE(waveMono.getFrame(b)[0] == 0.0f);
+ }
+ }
+}
--- /dev/null
+#include <memory>
+#include "../src/core/waveManager.h"
+#include "../src/core/wave.h"
+#include "../src/core/const.h"
+#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("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> 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")
+ {
+ waveManager::createEmpty(G_BUFFER_SIZE, G_MAX_IO_CHANS, G_SAMPLE_RATE,
+ "test.wav", &w);
+ std::unique_ptr<Wave> wave(w);
+
+ REQUIRE(wave->getRate() == G_SAMPLE_RATE);
+ REQUIRE(wave->getSize() == G_BUFFER_SIZE);
+ REQUIRE(wave->getChannels() == G_CHANNELS);
+ REQUIRE(wave->isLogical() == true);
+ REQUIRE(wave->isEdited() == false);
+ }
+
+ SECTION("test resampling")
+ {
+ waveManager::create("tests/resources/test.wav", &w);
+ std::unique_ptr<Wave> wave(w);
+
+ int oldSize = wave->getSize();
+ waveManager::resample(wave.get(), 1, G_SAMPLE_RATE * 2);
+
+ 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);
+ }
+}