From 65dbd0180f29d0199f45a77886ec4907537f7865 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Jarom=C3=ADr=20Mike=C5=A1?= Date: Tue, 9 Jun 2015 20:43:28 +0200 Subject: [PATCH] Imported Upstream version 0.9.6~dfsg1 --- COPYING | 674 ++ ChangeLog | 813 +++ Makefile.am | 1 + README.md | 85 + autogen.sh | 1579 ++++ configure.ac | 179 + src/Makefile.am | 115 + src/channel.cpp | 141 + src/channel.h | 195 + src/conf.cpp | 473 ++ src/conf.h | 122 + src/const.h | 291 + src/dataStorage.cpp | 72 + src/dataStorage.h | 49 + src/gd_about.cpp | 126 + src/gd_about.h | 58 + src/gd_actionEditor.cpp | 472 ++ src/gd_actionEditor.h | 131 + src/gd_beatsInput.cpp | 102 + src/gd_beatsInput.h | 56 + src/gd_bpmInput.cpp | 105 + src/gd_bpmInput.h | 51 + src/gd_browser.cpp | 395 + src/gd_browser.h | 98 + src/gd_config.cpp | 841 +++ src/gd_config.h | 167 + src/gd_devInfo.cpp | 107 + src/gd_devInfo.h | 47 + src/gd_editor.cpp | 491 ++ src/gd_editor.h | 116 + src/gd_keyGrabber.cpp | 144 + src/gd_keyGrabber.h | 63 + src/gd_mainWindow.cpp | 672 ++ src/gd_mainWindow.h | 210 + src/gd_midiGrabber.cpp | 261 + src/gd_midiGrabber.h | 150 + src/gd_midiOutputSetup.cpp | 127 + src/gd_midiOutputSetup.h | 63 + src/gd_pluginList.cpp | 393 + src/gd_pluginList.h | 107 + src/gd_pluginWindow.cpp | 137 + src/gd_pluginWindow.h | 76 + src/gd_pluginWindowGUI.cpp | 196 + src/gd_pluginWindowGUI.h | 84 + src/gd_warnings.cpp | 78 + src/gd_warnings.h | 43 + src/ge_actionChannel.cpp | 676 ++ src/ge_actionChannel.h | 135 + src/ge_actionWidget.cpp | 101 + src/ge_actionWidget.h | 53 + src/ge_browser.cpp | 307 + src/ge_browser.h | 68 + src/ge_channel.cpp | 287 + src/ge_channel.h | 162 + src/ge_column.cpp | 304 + src/ge_column.h | 98 + src/ge_envelopeChannel.cpp | 399 + src/ge_envelopeChannel.h | 115 + src/ge_midiChannel.cpp | 342 + src/ge_midiChannel.h | 89 + src/ge_mixed.cpp | 666 ++ src/ge_mixed.h | 355 + src/ge_muteChannel.cpp | 411 ++ src/ge_muteChannel.h | 103 + src/ge_pianoRoll.cpp | 730 ++ src/ge_pianoRoll.h | 183 + src/ge_sampleChannel.cpp | 606 ++ src/ge_sampleChannel.h | 103 + src/ge_waveform.cpp | 838 +++ src/ge_waveform.h | 193 + src/ge_window.cpp | 183 + src/ge_window.h | 73 + src/gg_keyboard.cpp | 366 + src/gg_keyboard.h | 145 + src/gg_waveTools.cpp | 103 + src/gg_waveTools.h | 49 + src/giada.ico | Bin 0 -> 9662 bytes src/glue.cpp | 1187 +++ src/glue.h | 169 + src/graphics.cpp | 1631 +++++ src/graphics.h | 102 + src/gui_utils.cpp | 210 + src/gui_utils.h | 93 + src/init.cpp | 188 + src/init.h | 49 + src/kernelAudio.cpp | 467 ++ src/kernelAudio.h | 96 + src/kernelMidi.cpp | 385 + src/kernelMidi.h | 104 + src/log.cpp | 80 + src/log.h | 45 + src/main.cpp | 98 + src/midiChannel.cpp | 323 + src/midiChannel.h | 136 + src/mixer.cpp | 684 ++ src/mixer.h | 209 + src/mixerHandler.cpp | 238 + src/mixerHandler.h | 76 + src/patch.cpp | 744 ++ src/patch.h | 95 + src/plugin.cpp | 520 ++ src/plugin.h | 168 + src/pluginHost.cpp | 679 ++ src/pluginHost.h | 132 + src/recorder.cpp | 698 ++ src/recorder.h | 192 + src/resource.h | 1 + src/resource.rc | 2 + src/rtaudio-mod/Makefile.in | 75 + src/rtaudio-mod/RtAudio.cpp | 10145 ++++++++++++++++++++++++++ src/rtaudio-mod/RtAudio.h | 1177 +++ src/rtaudio-mod/config/config.guess | 1371 ++++ src/rtaudio-mod/config/config.sub | 1366 ++++ src/rtaudio-mod/config/install.sh | 0 src/rtaudio-mod/configure | 5888 +++++++++++++++ src/rtaudio-mod/configure.ac | 197 + src/rtaudio-mod/librtaudio.pc.in | 12 + src/rtaudio-mod/rtaudio-config.in | 19 + src/sampleChannel.cpp | 993 +++ src/sampleChannel.h | 210 + src/utils.cpp | 379 + src/utils.h | 190 + src/wave.cpp | 245 + src/wave.h | 88 + src/waveFx.cpp | 224 + src/waveFx.h | 59 + 126 files changed, 51638 insertions(+) create mode 100644 COPYING create mode 100644 ChangeLog create mode 100644 Makefile.am create mode 100644 README.md create mode 100755 autogen.sh create mode 100644 configure.ac create mode 100644 src/Makefile.am create mode 100644 src/channel.cpp create mode 100644 src/channel.h create mode 100644 src/conf.cpp create mode 100644 src/conf.h create mode 100644 src/const.h create mode 100644 src/dataStorage.cpp create mode 100644 src/dataStorage.h create mode 100644 src/gd_about.cpp create mode 100644 src/gd_about.h create mode 100644 src/gd_actionEditor.cpp create mode 100644 src/gd_actionEditor.h create mode 100644 src/gd_beatsInput.cpp create mode 100644 src/gd_beatsInput.h create mode 100644 src/gd_bpmInput.cpp create mode 100644 src/gd_bpmInput.h create mode 100644 src/gd_browser.cpp create mode 100644 src/gd_browser.h create mode 100644 src/gd_config.cpp create mode 100644 src/gd_config.h create mode 100644 src/gd_devInfo.cpp create mode 100644 src/gd_devInfo.h create mode 100644 src/gd_editor.cpp create mode 100644 src/gd_editor.h create mode 100644 src/gd_keyGrabber.cpp create mode 100644 src/gd_keyGrabber.h create mode 100644 src/gd_mainWindow.cpp create mode 100644 src/gd_mainWindow.h create mode 100644 src/gd_midiGrabber.cpp create mode 100644 src/gd_midiGrabber.h create mode 100644 src/gd_midiOutputSetup.cpp create mode 100644 src/gd_midiOutputSetup.h create mode 100644 src/gd_pluginList.cpp create mode 100644 src/gd_pluginList.h create mode 100644 src/gd_pluginWindow.cpp create mode 100644 src/gd_pluginWindow.h create mode 100644 src/gd_pluginWindowGUI.cpp create mode 100644 src/gd_pluginWindowGUI.h create mode 100644 src/gd_warnings.cpp create mode 100644 src/gd_warnings.h create mode 100644 src/ge_actionChannel.cpp create mode 100644 src/ge_actionChannel.h create mode 100644 src/ge_actionWidget.cpp create mode 100644 src/ge_actionWidget.h create mode 100644 src/ge_browser.cpp create mode 100644 src/ge_browser.h create mode 100644 src/ge_channel.cpp create mode 100644 src/ge_channel.h create mode 100644 src/ge_column.cpp create mode 100644 src/ge_column.h create mode 100644 src/ge_envelopeChannel.cpp create mode 100644 src/ge_envelopeChannel.h create mode 100644 src/ge_midiChannel.cpp create mode 100644 src/ge_midiChannel.h create mode 100644 src/ge_mixed.cpp create mode 100644 src/ge_mixed.h create mode 100644 src/ge_muteChannel.cpp create mode 100644 src/ge_muteChannel.h create mode 100644 src/ge_pianoRoll.cpp create mode 100644 src/ge_pianoRoll.h create mode 100644 src/ge_sampleChannel.cpp create mode 100644 src/ge_sampleChannel.h create mode 100644 src/ge_waveform.cpp create mode 100644 src/ge_waveform.h create mode 100644 src/ge_window.cpp create mode 100644 src/ge_window.h create mode 100644 src/gg_keyboard.cpp create mode 100644 src/gg_keyboard.h create mode 100644 src/gg_waveTools.cpp create mode 100644 src/gg_waveTools.h create mode 100644 src/giada.ico create mode 100644 src/glue.cpp create mode 100644 src/glue.h create mode 100644 src/graphics.cpp create mode 100644 src/graphics.h create mode 100644 src/gui_utils.cpp create mode 100644 src/gui_utils.h create mode 100644 src/init.cpp create mode 100644 src/init.h create mode 100644 src/kernelAudio.cpp create mode 100644 src/kernelAudio.h create mode 100644 src/kernelMidi.cpp create mode 100644 src/kernelMidi.h create mode 100644 src/log.cpp create mode 100644 src/log.h create mode 100644 src/main.cpp create mode 100644 src/midiChannel.cpp create mode 100644 src/midiChannel.h create mode 100644 src/mixer.cpp create mode 100644 src/mixer.h create mode 100644 src/mixerHandler.cpp create mode 100644 src/mixerHandler.h create mode 100644 src/patch.cpp create mode 100644 src/patch.h create mode 100644 src/plugin.cpp create mode 100644 src/plugin.h create mode 100644 src/pluginHost.cpp create mode 100644 src/pluginHost.h create mode 100644 src/recorder.cpp create mode 100644 src/recorder.h create mode 100644 src/resource.h create mode 100644 src/resource.rc create mode 100644 src/rtaudio-mod/Makefile.in create mode 100644 src/rtaudio-mod/RtAudio.cpp create mode 100644 src/rtaudio-mod/RtAudio.h create mode 100644 src/rtaudio-mod/config/config.guess create mode 100755 src/rtaudio-mod/config/config.sub create mode 100755 src/rtaudio-mod/config/install.sh create mode 100755 src/rtaudio-mod/configure create mode 100644 src/rtaudio-mod/configure.ac create mode 100644 src/rtaudio-mod/librtaudio.pc.in create mode 100644 src/rtaudio-mod/rtaudio-config.in create mode 100644 src/sampleChannel.cpp create mode 100644 src/sampleChannel.h create mode 100644 src/utils.cpp create mode 100644 src/utils.h create mode 100644 src/wave.cpp create mode 100644 src/wave.h create mode 100644 src/waveFx.cpp create mode 100644 src/waveFx.h diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..94a9ed0 --- /dev/null +++ b/COPYING @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..712ae50 --- /dev/null +++ b/ChangeLog @@ -0,0 +1,813 @@ + +-------------------------------------------------------------------------------- + + + Giada - Your Hardcore Loopmachine. + + Developed by Monocasual Laboratories + + www.giadamusic.com + + CHANGELOG +-------------------------------------------------------------------------------- + + + +0.9.6 --- 2015 . 05 . 11 +- Keyboard binding for MIDI channels +- Support for multiple files in drag-n-drop operations +- Different color for wait/end statuses +- Small improvements to Keyboard grabber widget +- Fix random crashes with Jack enabled +- Fix weird behavior with multiple drag and drop +- Code refactoring + + +0.9.5 --- 2015 . 03 . 28 +- Better column resize algorithm +- New patch loading system with permanent MIDI mapping +- Ability to clear assigned keys (keyboard mode) +- Improved zoom icons in editors +- Fix deprecation warning in configure.ac + + +0.9.4 --- 2015 . 02 . 24 +- Drag-n-drop now works also in existing channels +- Store 'resize recordings' flag in giada.conf +- Better management of duplicate samples +- Add more VST debug information +- Minor fixes and tweaks + + +0.9.3 --- 2015 . 02 . 01 +- New GUI improvement: responsive and resizable columns +- Upgrade to FLTK 1.3.3 +- More robust column handling mechanism +- Support for MIDI devices without note-off message (@blablack) +- Fix segfaults when saving a patch with missing plugins +- Fix many minor graphical bugs +- Fix wrong vector assignment in MIDI send event +- Fix reloaded patches with no right tempo/beats displayed +- Fix random odd frames when adding/moving events in Piano Roll +- Minor internal cleanup + + +0.9.2 --- 2014 . 11 . 29 +- New grid layout in Sample Editor +- Load samples via drag n drop +- Add new utility functions: gTrim and gStripFileUrl +- Fix "normalize" button position in Sample Editor +- Minor waveform drawing optimizations +- Add missing files for RtAudio-mod compilation +- All one-shot mode, if fired manually, get the first frame truncated (fixed) + + +0.9.1 --- 2014 . 09 . 24 +- Bring back custom version of rtAudio in source package +- Automatically turn up volume when adding new channel +- Updated 'misc' tab in configuration panel +- Fix startup crash on OS X +- Fix missing jack headers + + +0.9.0 --- 2014 . 08 . 18 +- New full-screen GUI +- Multi-column support +- Advanced logging system +- Upgrade to RtAudio 4.1.1 and RtMidi 2.1.0 +- Removed embedded RtAudio (thanks to Arty) +- Fix wrong processing of VST MIDI events on 64 bit version +- Fix stretched buttons when resizing sample editor window +- "Clear all samples" destroys channels (fixed) +- "Free channel" messes up loop / mute buttons (fixes) +- Fix potential recordings with odd frames + + +0.8.4 --- 2014 . 03 . 27 +- New mode 'Loop Bar Once' +- Several small improvements and cleanups to internal utils functions +- Fixed missing title in several subwindows +- (win) Fix runtime error when loading a new project +- Fix chan reset when clicking on waveform +- Properly close subwindows after a channel has been deleted +- Fix 'reload' button not working for samples with updated names + + +0.8.3 --- 2014 . 02 . 14 +- Experimental MIDI timing output with MTC and MIDI clock +- Expose Sequencer x2 and /2 via MIDI +- New pitch operators x2 and /2 +- Internal xfade process restored +- "set key..." becomes "setup keyboard input" for sample channels +- MIDI events are now saved as unsigned int in patch +- Same expression on both sides of '|' in recorder.cpp (fixed) +- Muted channels leak some glitches on 'kill' event (fixed) +- Piano roll can't be edited anymore if beats == 32 (fixed) +- Noise when adding new MIDI channel (fixed) +- Boost and Normalize not working (fixed) +- Multiple copies of every file used by the patch (fixed) +- Samples with -1, -2, ... -n suffix are not included in patch (fixed) +- Segfaults when quantizing samples (fixed) + + +0.8.2 --- 2014 . 01 . 13 +- Pitch control exposed via MIDI +- New tools in Sample Editor (linear fade in/out, smooth edges) +- Implemented vstEvent->deltaFrames, gaining more precision with vst + MIDI events +- Add Fl::lock/Fl::unlock dynamics to glue_ calls where needed +- Avoid pitch sliding when changing pitch of a sample in status OFF +- Update copyright info in source files +- Internal fade in and fade out restored +- Add 'Giada' keyword to desktop file +- Fix annoying glitches when playing very short samples +- Fix random crashes when controlling giada via MIDI +- Fix missing MIDI mapping for read-actions button + + +0.8.1 --- 2013 . 12 . 09 +- New, high-quality pitch control based on libsamplerate +- New set of functions 'spread sample to beat/song' +[known issues] +- Internal crossfades have been temporarily disabled. Some clicks may + occur + + +0.8.0 --- 2013 . 11 . 03 +- Initial MIDI input support +- Fix freeze when recording audio inputs on a second channel +- Fix 'R' button to show up even if the channel has no actions +- Fix weird drawings of keypress actions in action editor +- Free channel: delete 'R' button as well +- Shift+key does not kill loop mode channels in a wait status +- Fix issue with 'R' button and newly added actions +- Remove "left"/"right" labels from main buttons + + +0.7.3 --- 2013 . 09 . 14 +- Experimental 64 bit compilation (Linux only) +- Massive internal cleanup of channel/gui channel layers +- Set default mode to full volume on sample load +- Set default mode to oneshot basic +- Faster drawings in piano roll +- Visual aids in piano roll +- Scroll to pointer in piano roll +- Several minor improvements in piano roll's usability +- Revised VST Carbon window popup system +- Minor improvements in startInputRec/stopInputRec procedure +- Fix compile error using local type Plugin* in Channel's constructor +- Fix segfault in OSX when working with VST windows + + +0.7.2 --- 2013 . 07 . 27 +- Initial MIDI output support +- Mute now affects channels with VSTi signals +- Lots of deb package improvements +- Complete rewrite of VST GUI part on OS X +- Don't send MIDI mute on sample channels +- Send MIDI mute for MIDI channels in play mode +- Fix wrong looping due to VST processing in mixer::masterPlay +- Fix jack crashes when using Giada with ALSA +- Fix VST random crashes on OSX, bus error +- Fix input device set to -1 after a system change + + +0.7.1 --- 2013 . 06 . 27 +- Initial Jack Transport support +- Send global note off when sequencer is being stopped +- Send note off when deleting notes in Piano Roll +- Store position and size of Piano Roll in conf file +- Avoid overlap MIDI notes in Piano Roll +- MIDI channel refactoring +- MIDI channels now behave like loop-mode ones +- Fix graphical bugs in Action Editor, sample mode +- Fix refresh issue in Piano Roll when deleting items +- Lots of invisible cleanups and improvements + + +0.7.0 --- 2013 . 06 . 05 +- Initial MIDI output implementation +- Initial VSTi (instrument) support +- New piano roll widget in action editor +- New chan mode: MIDI vs SAMPLE +- Fix E-MU Tracker Pre not correctly listed in audio in/output + + +0.6.4 --- 2013 . 05 . 07 +- Resizable plugin parameter window +- New and standard package name format -. +- Implement RtAudio::getCompiledApi() to fetch compiled APIs +- Implement audioMasterGetSampleRate, audioMasterGetLanguage VST opcodes +- Add drop-down menu for buffer size values in config panel +- Enhance project portability between OSes +- Lots of fixes and improvements for VST strings and parameters +- Avoid segfault when loading recs from a patch with files not found +- Always remember selected program when shifting up/down plugins +- Fix wrong size of single_press displayed in action editor +- Fix volume actions resized with value set to zero +- Fix volume envelope always over the cover area +- Fix src package extracts to current dir +- Fix segfault in loadpatch process if plugin GUIs are open +- Fix segfault when closing patch with plugins in BAD status + + +0.6.3 --- 2013 . 04 . 23 +- New 'solo' button +- Portable project system +- New 'Single Endless' channel mode +- GUI enhancements for channels in WAIT or ENDING status +- Minor fixes & cleanups + + +0.6.2 --- 2013 . 04 . 05 +- New volume envelope widget +- Zoom with mouse wheel in the action editor +- Graphical enhancements & speedups for the action editor +- Loop-repeat doesn't stop when put in ending mode (fixed) +- Fix draw errors when zooming too much the action editor +- Set silence in wave editor messes up the waveform (fixed) +- Wrong slashes in file path when saving a patch in Windows (fixed) +- Many, many code improvements and bugs fixed + + +0.6.1 --- 2013 . 03 . 21 +- Unlimited number of channels +- Deep internal refactoring, mixer/GUI layers +- Fix random crashes on exit +- Fix crashes when closing Giada with VST windows opened +- Always free Master In plugin stack on exit +- Lots of other minor bugs fixed and small enhancements + + +0.6.0 --- 2013 . 03 . 02 +- New, full-screen, redesigned sample editor +- Zoom with mouse wheel in sample editor +- Use kernelAudio::defaultIn/defaultOut for DEFAULT_SOUNDDEV_OUT +- Volume knob in main window now updates the editor +- Sound system issues in OS X (fixed) +- Output device info dialog refers to wrong device (fixed) + + +0.5.8 --- 2013 . 02 . 07 +- Internal samplerate conversion (with libsamplerate) +- Bring channels automatically to full volume on sample load +- Ability to set the audio device frequency +- New "internal mute" feature +- fix for deprecated VST opcode 14 +- fix deb package issues on Ubuntu 12.10 / KXStudio + + +0.5.7 --- 2013 . 01 . 21 +- visual grid + snapping in the action editor +- implement more audioMasterCanDo's in pluginHost +- limit zoom in actionEditor +- revise zoom behavior in actionEditor, now more comfortable +- fix forward declaration & inclusion of several headers +- implemented VST opcode 32 +- implemented VST opcode 33 +- implemented VST opcode 34 +- update website link in tar files +- update copyright info for 2013 + + +0.5.6 --- 2013 . 01 . 03 +- New overdub mode for live recording +- Support for VST programs, aka presets +- Lots of VST opcodes implemented +- Fix crash when removing a plugin from the stack +- Fix pops when going to beat 0 +- Fix compilation issues without --enable-vst +- Many invisible optimizations and small bugs fixed + + +0.5.5 --- 2012 . 12 . 15 +- "Hear what you're playing" feature +- Fx processing on the input side +- Ability to add different action types (Action Editor) +- Desktop integration on Linux (via deb package) +- Upgrade to FLTK 1.3.2 +- Remove "the action might stop the channel" when loading new samples +- Fix wrong positioning of zoom tools (Action Editor) +- Fix unwanted interactions on the grey area (Action Editor) +- Fix wrong memory alloc during the VST processing +- VST don't show up in OS X (fixed) +- Minor internal refactoring + bugfixing + + +0.5.4 --- 2012 . 11 . 24 +- VST GUI support +- Better subwindow management +- Implemented many other VST opcodes +- Missing plugins are now shown in the list with a 'dead' state +- Refresh action editor when changing beats (via beat operator or + beat window) +- Graphical improvements in the action editor +- Resizable action editor doesn't work well (fixed) +- Fix auto fadeout for SINGLE_PRESS channels +- Fix compilation without --enable-vst +- Fix for a wrong prototype definition of the VST hostCallback + + +0.5.3 --- 2012 . 10 . 26 +- Live beat manipulators (x2)(/2) +- New sub-windows management, faster and more comfortable +- New optional hard limiter on the output side +- Action Editor window recalls x,y,w,h zoom and position +- Usability improvements while handling an action (action editor) +- Refresh actionEditor window when switching channel mode or delete + actions +- Unable to delete a killchan action (action editor) (fixed) +- Don't show ACTION_KILLCHAN in a singlepress channel (action editor) +- Libsndfile no longer statically linked in Linux +- Fixed a typo in config: "when the sequeCer is halted" +- redefinition of DEFAULT_PITCH in wingdi.h (windows) (fixed) +- Upgrade to FLTK 1.3.0 +- Other internal optimizations +- Other small bugs fixed + + +0.5.2 --- 2012 . 10 . 05 +- Add ability to handle actions for loop-mode channels +- Add ability to record live mute actions for loop-mode channels +- Lots of live action recording improvements +- Enhanced usability for the action editor +- More verbose output if kernel audio fails to start +- Several internal optimizations + + +0.5.1 --- 2012 . 09 . 13 +- First implementation of the Action Editor +- Added compatibility with Ubuntu >= 10.04 + + +0.5.0 --- 2012 . 07 . 23 +- New custom project folder (.gprj) +- Sample names are now made unique +- Fixed unwanted time stretching while exporting a mono sample +- Lots of minor internal improvements + + +0.4.12 --- 2012 . 07 . 01 +- VST parameters and stacks are now stored in patch file +- Upgrade to RtAudio 0.4.11 +- PulseAudio support in Linux (thanks to RtAudio 0.4.11) +- Revised .deb package +- Enhanced "normalize" function in wave editor +- Several memory issues fixed +- Internal enhancements and minor bugs fixed + + +0.4.11 --- 2012 . 06 . 10 +- VST stack for each channel +- Custom paths for plugins, samples and patches +- Crash in config panel if device is busy (fixed) +- Graphical bug in the input meter (fixed) +- ParamLabel added in the VST parameter list + + +0.4.10 --- 2012 . 05 . 30 +- Ability to shift up an down VST plugins +- Enhanced patch/conf architecture +- Ability to edit a sample while playing +- Mutex controls in VST processing +- Lots of security issues fixed while changing pitch dinamically +- Enhanced sub-window system +- Several minor bugs fixed + + +0.4.9 --- 2012 . 05 . 12 +- No more mandatory inputs +- Pitch value properly stored inside the patch +- Several small VST host improvements +- Enhanced window management +- Ability to browse files while playing with main GUI (non-modal browser) +- Improved error checking in KernelAudio +- Wrong style for lower scrollbar in Browser (fixed) +- Fixed compilation on 64 bit systems (thanks to Speps@Archlinux) +- Samplerate no longer hardcoded, auto-detected with JACK +- Minor internal improvements and bugfixing + + +0.4.8 --- 2012 . 04 . 21 +- Initial VST support (experimental) +- Pitch controller (experimental, no filtering) +- OSX bundles are now correctly handled by the file browser +- Fixed several memory leaks +- Minor internal improvements + + +0.4.7 --- 2012 . 03 . 31 +- Cut, trim & silence operations in sample editor +- New "Reload sample" button added +- Lots of optimizations in the waveform drawing routines +- The sample is no longer editable while in play mode +- Fixed potential startup crashes while using Giada with Jack Audio +- Other minor fixes applied to the configuration panel +- Fixed compilation on 64 bit systems (thanks to Speps@Archlinux) + + +0.4.6 --- 2012 . 03 . 11 +- New device information panel +- The device configuration now shows only active and available devices +- Channel panel no longer pops up during a recording process +- GUI beautifications and other minor graphical fixes +- Program icon added in all subwindows +- Action records no longer available during a take, and vice versa +- Fixed a serious bug that swapped input and output devices +- Fixed loop behavior in ending mode +- Fixed clicks when stopping a muted channel in loop + + +0.4.5 --- 2012 . 02 . 25 +- Complete GUI redesign +- New "start/stop action recs" button +- Lots of internal cleanups and micro refactorings +- Small drawing glithes in Editor and status box (fixed) +- An invalid patch puts Giada to init state (fixed) +- Fixed button repeat on start/stop, action rec, input rec +- Checks against takes with unique name +- Message "this action may stop the channel" always shown (fixed) +- Channel no longer freeable while a take is in progress + + +0.4.4 --- 2012 . 02 . 04 +- New input/output channel selector +- Rewind bypasses the quantizer if triggered via mouse (fixed) +- Fixed library paths in configure and makefile (thanks to Yann C.) +- Added AUTHORS and NEWS files to the source package (thanks to Yann C.) +- More robust sample export procedure +- Issues with mute buttons when opening a patch (fixed) +- Several usability improvements +- Minor code cleanups and optimizations + + +0.4.3 --- 2012 . 01 . 21 +- New "save project" feature +- Ability to export a single sample to disk +- More feedback when removing/clearing actions and samples +- Sequencer starts automatically when action-rec button is pressed +- Alert if patch name is empty while saving it +- Channels now store internally the name of the samples +- Missing "--no devices found--" in input devices menu (fixed) +- Alert added if there are no empty channels for recording +- "Edit->Clear all actions" no longer works (fixed) +- END button could be used as a channel trigger (fixed) +- Recorders are available even if device status is wrong (fixed) +- Missing sample rewind if channel is muted (fixed) +- Quantizer doesn't work if framesize is odd (fixed) +- Random segfault when closing Giada (fixed) +- Lots of code cleanups +- Other minor improvements and optimizations + + +0.4.2 --- 2012 . 01 . 09 +- Live sampling from external input with meter and delay compensation +- Check against uneven values and overflow in buffersize field +- Wrong normalized values if volume level is 0.0 (fixed) +- Boost dial goes crazy if normalized > 20.0 dB (fixed) +- Boost dial goes crazy if normalized < 0.0 dB (fixed) +- Unwanted noise click if a muted channel is being rewinded (fixed) +- Mute doesn't work well for single-shot samples (fixed) +- Wrong FLTK headers (fixed, thanks to Yann C.) +- Moving chanStart/chanEnd swaps stereo image (fixed) +- Reset to init state doesn't reset mute buttons (fixed) +- Wrong chanStart value if > 0 (fixed) + + +0.4.1 --- 2011 . 12 . 07 +- Complete mixer engine refactoring +- Faster audio buffer allocation +- Global beat system revisited +- Autocrossfade between samples is now enabled by default +- No more recorded actions on odd frames +- Unintentional channel swapping fixed +- Unable to list all sound systems and sound devs under OSX (fixed) +- Missing graceful stop of audio streaming under OSX (fixed) + + +0.4.0 --- 2011 . 11 . 16 +- Support for all major uncompressed file formats (with libsndfile) +- Enhanced mono > stereo conversion +- Fixed drawing issues for the start/stop labels inside the waveform +- Enhanced backward compatibility with old patches +- Support for compilation on OS X and Windows + + +0.3.6 --- 2011 . 11 . 02 +- Initial Mac OS X release +- (Windows) Ability to list and browse all active drives +- Change some internal routines plus minor optimizations +- Added -pedantic and -Werror flag to the compiler +- Crash if clicking on mute in an empty channel (fixed) +- Chan status changes if an empty channel is being muted (fixed) + + +0.3.5 --- 2011 . 10 . 22 +- Pan controller added +- New GNU-style source code packaging +- Revamped .deb package +- Program icon missing under Windows (fixed) +- Crash if a sample in patch is missing from the filesystem (fixed) +- Unable to rewind to beat 1 if quantizer is on and seq stopped (fixed) +- Several minor glitches fixed + + +0.3.4 --- 2011 . 10 . 10 +- Full source code released under GPL license +- Autosmooth is now toggleable via setup +- Faster loading process of patch files +- Various internal cleanups and optimizations +- Fixed incorrect reading of boost values from patch +- Fixed a potential bug that prevented the config panel to appear +- Fixed stereo swap bug +- Minor graphical revisions + + +0.3.3 --- 2011 . 09 . 28 +- New "normalize" function +- More editing tools added inside the sample editor +- Waveform beautifications +- Fixed interaction bugs for boost and volume controls + + +0.3.2 --- 2011 . 09 . 19 +- New "mute" button inside the main window +- Waveform is now updated when the boost value changes +- Zoomin/zoomout relative to the scrollbar position +- Fixed garbage output if the volume was "-inf" (windows version) +- Fixed several rendering issues for short waveforms + + +0.3.1 --- 2011 . 09 . 12 +- Boost volume + fine volume control in sample editor +- Start/End handles inside the editor are now draggable via mouse +- Fixed scrollbar issues in sample editor +- Start/end points are now always drawn in the foreground +- Waveform no longer overflow if a value is greater than the window +- (linux) giada.conf is saved inside the hidden folder /home/.giada +- patch loading process is now faster and cleaner +- Update to rtAudio 4.0.10 + + +0.3.0 --- 2011 . 09 . 01 +- New sample editor window +- Ability to set start/end points within a sample +- Update to rtAudio 4.0.9 +- Fixed an string overflow inside a patch +- Fixed a missing memory free if a sample is unreadable +- Several internal updates and optimizations + + +0.2.7 --- 2011 . 07. 22 +- New way to handle recorded channels as loops +- Fixed retrig for backspace key (rewind) +- Enhanced rewind with quantization support +- Main and alert windows now appear centered on screen +- Sanity check against old patches without metronome information +- Rewind now affects loops in rec-reading mode + + +0.2.6 --- 2011 . 07 . 11 +- Internal metronome +- Fixed some glitches in config panel +- Minor cleanups + + +0.2.5 --- 2011 . 06 . 20 +- Configuration panel redesign +- Several new control options +- Progress feedback when loading patches +- Internal optimizations +- Updated docs + + +0.2.4 --- 2011 . 06 . 08 +- New loop repeat mode +- Ability to save patches anywhere in the filesystem +- Sub-beat management +- Sound meter has been revisited and improved +- Several patch enhancements +- Core audio optimizations + + +0.2.3 --- 2011 . 05 . 18 +- ASIO support for Windows version +- Enhanced security when reading values from a patch +- Ability to disable the recordings when the sequencer is paused +- Master volume and rec status are now saved inside the patch +- Device selection fixed and improved +- Sequencer flickering in Windows has been fixed +- Feedback added if a sample from a patch is unreadable or corrupted +- Minor internal optimizations + + +0.2.2 --- 2011 . 05 . 04 +- New open-source patch system +- A patch can now be loaded from any location of the filesystem +- Enhanced file browser coords system +- Lots of minor improvements to the sample loading/unloading procedure +- (win) Init path of file browser now starts from %userProfile%/Desktop +- Wrong handling of "/" chars fixed in config menu +- Fixed potential hangs on quit +- Fixed clicks when stopping sequencer/sample +- Minor gui beautifications + + +0.2.1 --- 2011 . 04 . 26 +- Windows version + + +0.2.0 --- 2011 . 04 . 19 +- Full JACK and ALSA support with RtAudio +- New list of sound devices in menu window +- Enhanced shutdown procedure to prevent potential crashes +- Some GUI glitches fixed +- Fixed random locks when the screensaver is active + + +0.1.8 --- 2011 . 04 . 13 +- new functions: free al samples/recordings, reset to init patch +- main menu redesign +- the file browser is now resizable +- GUI feedback for samples in play mode +- some fixes when unloading a sample + + +0.1.7 --- 2011 . 04 . 07 +- Ability to remove only action recordings or mute recordings +- Shift+key now stops the sample if the master play is deactivated +- Frame 0 was always processed at the end of the sequencer +- Minor internal improvements + + +0.1.6 --- 2011 . 03 . 29 +- Autocrossfade to prevent clicks +- Internal improvements and bugfixing + + +0.1.5 --- 2011 . 03 . 10 +- decimal bpm adjustment +- ability to shrink/expand actions when changing the global beats +- improved GUI for beats and bpm controllers +- improved routines for action management +- actions are now updated when you change bpm + + +0.1.4 --- 2011 . 03 . 04 +- ability to save recorded actions +- status box now shows if a recorded chan is deactivated +- recorder is reset correctly when you load a new patch +- minor improvements + + +0.1.3 --- 2011 . 02 . 26 +- action recorder (first implementation) +- quantization procedure slightly optimized +- minor graphical adjustments +- expanded documentation + + +0.1.2 --- 2011 . 02 . 08 +- master volume controller +- improved sound meter with more accuracy +- improved verifications when reading or writing a patch +- beat counter is now always reset to 1 after a patch is loaded +- made loading wave files more robust, plus memory optimizations +- minor crashes fixed + + +0.1.1 --- 2011 . 01 . 26 +- expansion to 32 channels +- GUI restyling +- live quantizer +- fixed wrong handling of "mute" value when loading a patch +- minor internal improvements + + +0.1.0 --- 2011 . 01 . 18 +- ability to mute channels +- stop and rewind buttons now affect only channels in loop mode +- undo for ending loops +- internal patch improvements to provide backward compatibility +- better behaviour when exceeding the total amount of available memory +- fixed random reversals of stereo field at the end of the beat bar +- fixed a potential segmentation fault when freeing a sample + + +0.0.12 --- 2011 . 01 . 11 +- ability to free a channel +- "stop" button to suspend the general program +- new "stop-to-end" mode for looped channels +- new "full stop" key combination +- enhanced mouse interaction +- minor bugfixing + + +0.0.11 --- 2010 . 12 . 28 +- customizable keys +- GUI layer optimizations and improvements +- overwrite confirmation when saving a patch +- the browser always displays the patch folder when loading a new patch +- browser url is now read-only to prevent manipulations + + +0.0.10 --- 2010 . 12 . 16 +- new "single-mode retrig" mode added +- expansion to 16 channels +- new advanced file browser with the ability to navigate the filesystem +- audio configuration now uses the "default" device, if not changed +- graphical restyling for audio channels +- fixed a random crash on startup, due to a wrong thread synch + + +0.0.9 --- 2010 . 12 . 08 +- new loop once mode +- new graphical beat meter +- rewind-program button added +- heavy buttons and controls restyling +- reinforced header verification when a new patch is opened for reading +- some bugfixing for the loading procedure of a patch +- fixed a potential crash while a new sample is being loaded + + +0.0.8 --- 2010 . 11 . 28 +- fixed a critical crash while loading a sample +- GUI warning when loading a sample or a patch into an active channel +- little optimization during the search for data into waves +- all popup windows are now modal (always on top) +- fixed a potential crash in case of malformed wave files + + +0.0.7 --- 2010 . 11 . 18 +- new peak meter with clip warning and system status report +- any "ok" button is associated to the "return" key (for fast inputs) +- graphical improvements for checkboxes, buttons, smaller fonts in browsers +- graphical feedback for missing samples +- internal optimizations + + +0.0.6 --- 2010 . 11 . 01 +- new 32 bit floating point audio engine +- support for any wave bit-rate, from 8 bit pcm to 32 float +- Giada now prompts when a sound card error occurs +- removed the hard-limiting system, now useless +- the "save patch" panel now shows the actual patchname in use +- alphabetic sort into the file browser +- fixed an annoying gui flickering +- patch volume information are now handled correctly +- minor internal optimizations +- fixed a memory leak when loading a new patch +- other memory optimizations + + +0.0.5 --- 2010 . 10 . 21 +- Patch-based system: load/save your setup from/to a binary file +- New audio configuration panel +- New configuration file (giada.conf) where to store data +- Complete implementation of the double click startup +- Fixed a bug related to the confirm-on-quit window +- Minor GUI beautifications +- Extended documentation + + +0.0.4 --- 2010 . 10 . 11 +- New internal sample-accurate loop engine +- Ability to configure the period size through ini file +- First implementation of the double click startup +- Debug information are now properly tagged, reporting the interested layer + + +0.0.3 --- 2010 . 10 . 02 +- (giada) New official logo +- (giada) Ability to load single-channel samples +- (giada) Capital letter consistency between GUI buttons +- (giada) Added "cancel" button to the browser window +- (giada) Endianness verification +- (giada) Cleanup of the audio initialization procedure +- (giada) Several internal optimization for audio playback +- (giada) ALSA layer now tells if an underrun occurs +- (giada) Internal memory allocation improvements +- (giada) Fixed an unallocated hardware parameter into ALSA configuration +- (wa) Information about wave endianness +- Added a "Requirements" section to the readme file + + +0.0.2 --- 2010 . 09 . 17 +- (giada) More visual feedbacks if a key is pressed +- (giada) Added a graphical alert if a sample is in an incorrect format +- (giada) Confirm on exit +- (giada) Graphical improvements for the browser window +- (giada) Browser window doesn't close itself anymore if a sample format is incorrect +- (giada) Added "-- no sample --" for empty channels +- (giada) Startup no longer fails if a sample from the ini file is not found +- (giada) Internal optimization for the sample loading routine +- (giada) More graphical consistency between subwindows +- (giada) The sample name is now trucated to fit into its box, preventing overflow +- (giada) Other minor GUI tweaks +- (giada) Internal memory improvements to prevent a bad bug of allocation with malformed wave files +- (wa) More information about sample size +- (wa) Added calculations and comparison between data sizes + + +0.0.1 --- 2010 . 09 . 06 +(initial release) diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..e1c45f3 --- /dev/null +++ b/Makefile.am @@ -0,0 +1 @@ +SUBDIRS=src \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..ba0e0fb --- /dev/null +++ b/README.md @@ -0,0 +1,85 @@ + + +Giada - Your Hardcore Loopmachine +================================= + +http://www.giadamusic.com + + + +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. + +![Giada Loop Machine screenshot](http://giadamusic.com/public/img/screenshots/giada-loop-machine-screenshot-14-carousel.jpg) + +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. +* Unlimited number of channels controlled via computer keyboard. +* Different playback modes and combinations. +* BPM and beat sync with sample-accurate loop engine. +* Built-in wave editor. +* VST and VSTi (instrument) plugin support. +* MIDI input and output support. +* Live sampler from external inputs. +* Live quantizer. +* Piano Roll editor. +* Action recorder. +* Patch-based system. +* Support for all major uncompressed file formats. +* A constant stage of development. +* 100% open-source GPL. + + + + +License +------- +Giada is available under the terms of the GNU General Public License. +Take a look at the COPYING file for further informations. + + + +Documentation +------------- +Docs are available online on the official website + +http://www.giadamusic.com/documentation + + + +Bugs, requests and questions for non-developers +----------------------------------------------- +End users forum + +http://www.giadamusic.com/forum + + + +Copyright +--------- +Giada is Copyright (C) 2010-2015 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 +. diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 0000000..d1fea40 --- /dev/null +++ b/autogen.sh @@ -0,0 +1,1579 @@ +#!/bin/sh +# a u t o g e n . s h +# +# Copyright (c) 2005-2009 United States Government as represented by +# the U.S. Army Research Laboratory. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided +# with the distribution. +# +# 3. The name of the author may not be used to endorse or promote +# products derived from this software without specific prior written +# permission. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS +# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +# GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +### +# +# Script for automatically preparing the sources for compilation by +# performing the myriad of necessary steps. The script attempts to +# detect proper version support, and outputs warnings about particular +# systems that have autotool peculiarities. +# +# Basically, if everything is set up and installed correctly, the +# script will validate that minimum versions of the GNU Build System +# tools are installed, account for several common configuration +# issues, and then simply run autoreconf for you. +# +# If autoreconf fails, which can happen for many valid configurations, +# this script proceeds to run manual preparation steps effectively +# providing a POSIX shell script (mostly complete) reimplementation of +# autoreconf. +# +# The AUTORECONF, AUTOCONF, AUTOMAKE, LIBTOOLIZE, ACLOCAL, AUTOHEADER +# environment variables and corresponding _OPTIONS variables (e.g. +# AUTORECONF_OPTIONS) may be used to override the default automatic +# detection behaviors. Similarly the _VERSION variables will override +# the minimum required version numbers. +# +# Examples: +# +# To obtain help on usage: +# ./autogen.sh --help +# +# To obtain verbose output: +# ./autogen.sh --verbose +# +# To skip autoreconf and prepare manually: +# AUTORECONF=false ./autogen.sh +# +# To verbosely try running with an older (unsupported) autoconf: +# AUTOCONF_VERSION=2.50 ./autogen.sh --verbose +# +# Author: +# Christopher Sean Morrison +# +# Patches: +# Sebastian Pipping +# +###################################################################### + +# set to minimum acceptable version of autoconf +if [ "x$AUTOCONF_VERSION" = "x" ] ; then + AUTOCONF_VERSION=2.52 +fi +# set to minimum acceptable version of automake +if [ "x$AUTOMAKE_VERSION" = "x" ] ; then + AUTOMAKE_VERSION=1.6.0 +fi +# set to minimum acceptable version of libtool +if [ "x$LIBTOOL_VERSION" = "x" ] ; then + LIBTOOL_VERSION=1.4.2 +fi + + +################## +# ident function # +################## +ident ( ) { + # extract copyright from header + __copyright="`grep Copyright $AUTOGEN_SH | head -${HEAD_N}1 | awk '{print $4}'`" + if [ "x$__copyright" = "x" ] ; then + __copyright="`date +%Y`" + fi + + # extract version from CVS Id string + __id="$Id: autogen.sh 33925 2009-03-01 23:27:06Z brlcad $" + __version="`echo $__id | sed 's/.*\([0-9][0-9][0-9][0-9]\)[-\/]\([0-9][0-9]\)[-\/]\([0-9][0-9]\).*/\1\2\3/'`" + if [ "x$__version" = "x" ] ; then + __version="" + fi + + echo "autogen.sh build preparation script by Christopher Sean Morrison" + echo " + config.guess download patch by Sebastian Pipping (2008-12-03)" + echo "revised 3-clause BSD-style license, copyright (c) $__copyright" + echo "script version $__version, ISO/IEC 9945 POSIX shell script" +} + + +################## +# USAGE FUNCTION # +################## +usage ( ) { + echo "Usage: $AUTOGEN_SH [-h|--help] [-v|--verbose] [-q|--quiet] [-d|--download] [--version]" + echo " --help Help on $NAME_OF_AUTOGEN usage" + echo " --verbose Verbose progress output" + echo " --quiet Quiet suppressed progress output" + echo " --download Download the latest config.guess from gnulib" + echo " --version Only perform GNU Build System version checks" + echo + echo "Description: This script will validate that minimum versions of the" + echo "GNU Build System tools are installed and then run autoreconf for you." + echo "Should autoreconf fail, manual preparation steps will be run" + echo "potentially accounting for several common preparation issues. The" + + echo "AUTORECONF, AUTOCONF, AUTOMAKE, LIBTOOLIZE, ACLOCAL, AUTOHEADER," + echo "PROJECT, & CONFIGURE environment variables and corresponding _OPTIONS" + echo "variables (e.g. AUTORECONF_OPTIONS) may be used to override the" + echo "default automatic detection behavior." + echo + + ident + + return 0 +} + + +########################## +# VERSION_ERROR FUNCTION # +########################## +version_error ( ) { + if [ "x$1" = "x" ] ; then + echo "INTERNAL ERROR: version_error was not provided a version" + exit 1 + fi + if [ "x$2" = "x" ] ; then + echo "INTERNAL ERROR: version_error was not provided an application name" + exit 1 + fi + $ECHO + $ECHO "ERROR: To prepare the ${PROJECT} build system from scratch," + $ECHO " at least version $1 of $2 must be installed." + $ECHO + $ECHO "$NAME_OF_AUTOGEN does not need to be run on the same machine that will" + $ECHO "run configure or make. Either the GNU Autotools will need to be installed" + $ECHO "or upgraded on this system, or $NAME_OF_AUTOGEN must be run on the source" + $ECHO "code on another system and then transferred to here. -- Cheers!" + $ECHO +} + +########################## +# VERSION_CHECK FUNCTION # +########################## +version_check ( ) { + if [ "x$1" = "x" ] ; then + echo "INTERNAL ERROR: version_check was not provided a minimum version" + exit 1 + fi + _min="$1" + if [ "x$2" = "x" ] ; then + echo "INTERNAL ERROR: version check was not provided a comparison version" + exit 1 + fi + _cur="$2" + + # needed to handle versions like 1.10 and 1.4-p6 + _min="`echo ${_min}. | sed 's/[^0-9]/./g' | sed 's/\.\././g'`" + _cur="`echo ${_cur}. | sed 's/[^0-9]/./g' | sed 's/\.\././g'`" + + _min_major="`echo $_min | cut -d. -f1`" + _min_minor="`echo $_min | cut -d. -f2`" + _min_patch="`echo $_min | cut -d. -f3`" + + _cur_major="`echo $_cur | cut -d. -f1`" + _cur_minor="`echo $_cur | cut -d. -f2`" + _cur_patch="`echo $_cur | cut -d. -f3`" + + if [ "x$_min_major" = "x" ] ; then + _min_major=0 + fi + if [ "x$_min_minor" = "x" ] ; then + _min_minor=0 + fi + if [ "x$_min_patch" = "x" ] ; then + _min_patch=0 + fi + if [ "x$_cur_minor" = "x" ] ; then + _cur_major=0 + fi + if [ "x$_cur_minor" = "x" ] ; then + _cur_minor=0 + fi + if [ "x$_cur_patch" = "x" ] ; then + _cur_patch=0 + fi + + $VERBOSE_ECHO "Checking if ${_cur_major}.${_cur_minor}.${_cur_patch} is greater than ${_min_major}.${_min_minor}.${_min_patch}" + + if [ $_min_major -lt $_cur_major ] ; then + return 0 + elif [ $_min_major -eq $_cur_major ] ; then + if [ $_min_minor -lt $_cur_minor ] ; then + return 0 + elif [ $_min_minor -eq $_cur_minor ] ; then + if [ $_min_patch -lt $_cur_patch ] ; then + return 0 + elif [ $_min_patch -eq $_cur_patch ] ; then + return 0 + fi + fi + fi + return 1 +} + + +###################################### +# LOCATE_CONFIGURE_TEMPLATE FUNCTION # +###################################### +locate_configure_template ( ) { + _pwd="`pwd`" + if test -f "./configure.ac" ; then + echo "./configure.ac" + elif test -f "./configure.in" ; then + echo "./configure.in" + elif test -f "$_pwd/configure.ac" ; then + echo "$_pwd/configure.ac" + elif test -f "$_pwd/configure.in" ; then + echo "$_pwd/configure.in" + elif test -f "$PATH_TO_AUTOGEN/configure.ac" ; then + echo "$PATH_TO_AUTOGEN/configure.ac" + elif test -f "$PATH_TO_AUTOGEN/configure.in" ; then + echo "$PATH_TO_AUTOGEN/configure.in" + fi +} + + +################## +# argument check # +################## +ARGS="$*" +PATH_TO_AUTOGEN="`dirname $0`" +NAME_OF_AUTOGEN="`basename $0`" +AUTOGEN_SH="$PATH_TO_AUTOGEN/$NAME_OF_AUTOGEN" + +LIBTOOL_M4="${PATH_TO_AUTOGEN}/misc/libtool.m4" + +if [ "x$HELP" = "x" ] ; then + HELP=no +fi +if [ "x$QUIET" = "x" ] ; then + QUIET=no +fi +if [ "x$VERBOSE" = "x" ] ; then + VERBOSE=no +fi +if [ "x$VERSION_ONLY" = "x" ] ; then + VERSION_ONLY=no +fi +if [ "x$DOWNLOAD" = "x" ] ; then + DOWNLOAD=no +fi +if [ "x$AUTORECONF_OPTIONS" = "x" ] ; then + AUTORECONF_OPTIONS="-i -f" +fi +if [ "x$AUTOCONF_OPTIONS" = "x" ] ; then + AUTOCONF_OPTIONS="-f" +fi +if [ "x$AUTOMAKE_OPTIONS" = "x" ] ; then + AUTOMAKE_OPTIONS="-a -c -f" +fi +ALT_AUTOMAKE_OPTIONS="-a -c" +if [ "x$LIBTOOLIZE_OPTIONS" = "x" ] ; then + LIBTOOLIZE_OPTIONS="--automake -c -f" +fi +ALT_LIBTOOLIZE_OPTIONS="--automake --copy --force" +if [ "x$ACLOCAL_OPTIONS" = "x" ] ; then + ACLOCAL_OPTIONS="" +fi +if [ "x$AUTOHEADER_OPTIONS" = "x" ] ; then + AUTOHEADER_OPTIONS="" +fi +if [ "x$CONFIG_GUESS_URL" = "x" ] ; then + CONFIG_GUESS_URL="http://git.savannah.gnu.org/gitweb/?p=gnulib.git;a=blob_plain;f=build-aux/config.guess;hb=HEAD" +fi +for arg in $ARGS ; do + case "x$arg" in + x--help) HELP=yes ;; + x-[hH]) HELP=yes ;; + x--quiet) QUIET=yes ;; + x-[qQ]) QUIET=yes ;; + x--verbose) VERBOSE=yes ;; + x-[dD]) DOWNLOAD=yes ;; + x--download) DOWNLOAD=yes ;; + x-[vV]) VERBOSE=yes ;; + x--version) VERSION_ONLY=yes ;; + *) + echo "Unknown option: $arg" + echo + usage + exit 1 + ;; + esac +done + + +##################### +# environment check # +##################### + +# sanity check before recursions potentially begin +if [ ! -f "$AUTOGEN_SH" ] ; then + echo "INTERNAL ERROR: $AUTOGEN_SH does not exist" + if [ ! "x$0" = "x$AUTOGEN_SH" ] ; then + echo "INTERNAL ERROR: dirname/basename inconsistency: $0 != $AUTOGEN_SH" + fi + exit 1 +fi + +# force locale setting to C so things like date output as expected +LC_ALL=C + +# commands that this script expects +for __cmd in echo head tail pwd ; do + echo "test" | $__cmd > /dev/null 2>&1 + if [ $? != 0 ] ; then + echo "INTERNAL ERROR: '${__cmd}' command is required" + exit 2 + fi +done +echo "test" | grep "test" > /dev/null 2>&1 +if test ! x$? = x0 ; then + echo "INTERNAL ERROR: grep command is required" + exit 1 +fi +echo "test" | sed "s/test/test/" > /dev/null 2>&1 +if test ! x$? = x0 ; then + echo "INTERNAL ERROR: sed command is required" + exit 1 +fi + + +# determine the behavior of echo +case `echo "testing\c"; echo 1,2,3`,`echo -n testing; echo 1,2,3` in + *c*,-n*) ECHO_N= ECHO_C=' +' ECHO_T=' ' ;; + *c*,* ) ECHO_N=-n ECHO_C= ECHO_T= ;; + *) ECHO_N= ECHO_C='\c' ECHO_T= ;; +esac + +# determine the behavior of head +case "x`echo 'head' | head -n 1 2>&1`" in + *xhead*) HEAD_N="n " ;; + *) HEAD_N="" ;; +esac + +# determine the behavior of tail +case "x`echo 'tail' | tail -n 1 2>&1`" in + *xtail*) TAIL_N="n " ;; + *) TAIL_N="" ;; +esac + +VERBOSE_ECHO=: +ECHO=: +if [ "x$QUIET" = "xyes" ] ; then + if [ "x$VERBOSE" = "xyes" ] ; then + echo "Verbose output quelled by quiet option. Further output disabled." + fi +else + ECHO=echo + if [ "x$VERBOSE" = "xyes" ] ; then + echo "Verbose output enabled" + VERBOSE_ECHO=echo + fi +fi + + +# allow a recursive run to disable further recursions +if [ "x$RUN_RECURSIVE" = "x" ] ; then + RUN_RECURSIVE=yes +fi + + +################################################ +# check for help arg and bypass version checks # +################################################ +if [ "x`echo $ARGS | sed 's/.*[hH][eE][lL][pP].*/help/'`" = "xhelp" ] ; then + HELP=yes +fi +if [ "x$HELP" = "xyes" ] ; then + usage + $ECHO "---" + $ECHO "Help was requested. No preparation or configuration will be performed." + exit 0 +fi + + +####################### +# set up signal traps # +####################### +untrap_abnormal ( ) { + for sig in 1 2 13 15; do + trap - $sig + done +} + +# do this cleanup whenever we exit. +trap ' + # start from the root + if test -d "$START_PATH" ; then + cd "$START_PATH" + fi + + # restore/delete backup files + if test "x$PFC_INIT" = "x1" ; then + recursive_restore + fi +' 0 + +# trap SIGHUP (1), SIGINT (2), SIGPIPE (13), SIGTERM (15) +for sig in 1 2 13 15; do + trap ' + $ECHO "" + $ECHO "Aborting $NAME_OF_AUTOGEN: caught signal '$sig'" + + # start from the root + if test -d "$START_PATH" ; then + cd "$START_PATH" + fi + + # clean up on abnormal exit + $VERBOSE_ECHO "rm -rf autom4te.cache" + rm -rf autom4te.cache + + if test -f "acinclude.m4.$$.backup" ; then + $VERBOSE_ECHO "cat acinclude.m4.$$.backup > acinclude.m4" + chmod u+w acinclude.m4 + cat acinclude.m4.$$.backup > acinclude.m4 + + $VERBOSE_ECHO "rm -f acinclude.m4.$$.backup" + rm -f acinclude.m4.$$.backup + fi + + { (exit 1); exit 1; } +' $sig +done + + +############################# +# look for a configure file # +############################# +if [ "x$CONFIGURE" = "x" ] ; then + CONFIGURE="`locate_configure_template`" + if [ ! "x$CONFIGURE" = "x" ] ; then + $VERBOSE_ECHO "Found a configure template: $CONFIGURE" + fi +else + $ECHO "Using CONFIGURE environment variable override: $CONFIGURE" +fi +if [ "x$CONFIGURE" = "x" ] ; then + if [ "x$VERSION_ONLY" = "xyes" ] ; then + CONFIGURE=/dev/null + else + $ECHO + $ECHO "A configure.ac or configure.in file could not be located implying" + $ECHO "that the GNU Build System is at least not used in this directory. In" + $ECHO "any case, there is nothing to do here without one of those files." + $ECHO + $ECHO "ERROR: No configure.in or configure.ac file found in `pwd`" + exit 1 + fi +fi + +#################### +# get project name # +#################### +if [ "x$PROJECT" = "x" ] ; then + PROJECT="`grep AC_INIT $CONFIGURE | grep -v '.*#.*AC_INIT' | tail -${TAIL_N}1 | sed 's/^[ ]*AC_INIT(\([^,)]*\).*/\1/' | sed 's/.*\[\(.*\)\].*/\1/'`" + if [ "x$PROJECT" = "xAC_INIT" ] ; then + # projects might be using the older/deprecated arg-less AC_INIT .. look for AM_INIT_AUTOMAKE instead + PROJECT="`grep AM_INIT_AUTOMAKE $CONFIGURE | grep -v '.*#.*AM_INIT_AUTOMAKE' | tail -${TAIL_N}1 | sed 's/^[ ]*AM_INIT_AUTOMAKE(\([^,)]*\).*/\1/' | sed 's/.*\[\(.*\)\].*/\1/'`" + fi + if [ "x$PROJECT" = "xAM_INIT_AUTOMAKE" ] ; then + PROJECT="project" + fi + if [ "x$PROJECT" = "x" ] ; then + PROJECT="project" + fi +else + $ECHO "Using PROJECT environment variable override: $PROJECT" +fi +$ECHO "Preparing the $PROJECT build system...please wait" +$ECHO + + +######################## +# check for autoreconf # +######################## +HAVE_AUTORECONF=no +if [ "x$AUTORECONF" = "x" ] ; then + for AUTORECONF in autoreconf ; do + $VERBOSE_ECHO "Checking autoreconf version: $AUTORECONF --version" + $AUTORECONF --version > /dev/null 2>&1 + if [ $? = 0 ] ; then + HAVE_AUTORECONF=yes + break + fi + done +else + HAVE_AUTORECONF=yes + $ECHO "Using AUTORECONF environment variable override: $AUTORECONF" +fi + + +########################## +# autoconf version check # +########################## +_acfound=no +if [ "x$AUTOCONF" = "x" ] ; then + for AUTOCONF in autoconf ; do + $VERBOSE_ECHO "Checking autoconf version: $AUTOCONF --version" + $AUTOCONF --version > /dev/null 2>&1 + if [ $? = 0 ] ; then + _acfound=yes + break + fi + done +else + _acfound=yes + $ECHO "Using AUTOCONF environment variable override: $AUTOCONF" +fi + +_report_error=no +if [ ! "x$_acfound" = "xyes" ] ; then + $ECHO "ERROR: Unable to locate GNU Autoconf." + _report_error=yes +else + _version="`$AUTOCONF --version | head -${HEAD_N}1 | sed 's/[^0-9]*\([0-9\.][0-9\.]*\)/\1/'`" + if [ "x$_version" = "x" ] ; then + _version="0.0.0" + fi + $ECHO "Found GNU Autoconf version $_version" + version_check "$AUTOCONF_VERSION" "$_version" + if [ $? -ne 0 ] ; then + _report_error=yes + fi +fi +if [ "x$_report_error" = "xyes" ] ; then + version_error "$AUTOCONF_VERSION" "GNU Autoconf" + exit 1 +fi + + +########################## +# automake version check # +########################## +_amfound=no +if [ "x$AUTOMAKE" = "x" ] ; then + for AUTOMAKE in automake ; do + $VERBOSE_ECHO "Checking automake version: $AUTOMAKE --version" + $AUTOMAKE --version > /dev/null 2>&1 + if [ $? = 0 ] ; then + _amfound=yes + break + fi + done +else + _amfound=yes + $ECHO "Using AUTOMAKE environment variable override: $AUTOMAKE" +fi + + +_report_error=no +if [ ! "x$_amfound" = "xyes" ] ; then + $ECHO + $ECHO "ERROR: Unable to locate GNU Automake." + _report_error=yes +else + _version="`$AUTOMAKE --version | head -${HEAD_N}1 | sed 's/[^0-9]*\([0-9\.][0-9\.]*\)/\1/'`" + if [ "x$_version" = "x" ] ; then + _version="0.0.0" + fi + $ECHO "Found GNU Automake version $_version" + version_check "$AUTOMAKE_VERSION" "$_version" + if [ $? -ne 0 ] ; then + _report_error=yes + fi +fi +if [ "x$_report_error" = "xyes" ] ; then + version_error "$AUTOMAKE_VERSION" "GNU Automake" + exit 1 +fi + + +######################## +# check for libtoolize # +######################## +HAVE_LIBTOOLIZE=yes +HAVE_ALT_LIBTOOLIZE=no +_ltfound=no +if [ "x$LIBTOOLIZE" = "x" ] ; then + LIBTOOLIZE=libtoolize + $VERBOSE_ECHO "Checking libtoolize version: $LIBTOOLIZE --version" + $LIBTOOLIZE --version > /dev/null 2>&1 + if [ ! $? = 0 ] ; then + HAVE_LIBTOOLIZE=no + $ECHO + if [ "x$HAVE_AUTORECONF" = "xno" ] ; then + $ECHO "Warning: libtoolize does not appear to be available." + else + $ECHO "Warning: libtoolize does not appear to be available. This means that" + $ECHO "the automatic build preparation via autoreconf will probably not work." + $ECHO "Preparing the build by running each step individually, however, should" + $ECHO "work and will be done automatically for you if autoreconf fails." + fi + + # look for some alternates + for tool in glibtoolize libtoolize15 libtoolize14 libtoolize13 ; do + $VERBOSE_ECHO "Checking libtoolize alternate: $tool --version" + _glibtoolize="`$tool --version > /dev/null 2>&1`" + if [ $? = 0 ] ; then + $VERBOSE_ECHO "Found $tool --version" + _glti="`which $tool`" + if [ "x$_glti" = "x" ] ; then + $VERBOSE_ECHO "Cannot find $tool with which" + continue; + fi + if test ! -f "$_glti" ; then + $VERBOSE_ECHO "Cannot use $tool, $_glti is not a file" + continue; + fi + _gltidir="`dirname $_glti`" + if [ "x$_gltidir" = "x" ] ; then + $VERBOSE_ECHO "Cannot find $tool path with dirname of $_glti" + continue; + fi + if test ! -d "$_gltidir" ; then + $VERBOSE_ECHO "Cannot use $tool, $_gltidir is not a directory" + continue; + fi + HAVE_ALT_LIBTOOLIZE=yes + LIBTOOLIZE="$tool" + $ECHO + $ECHO "Fortunately, $tool was found which means that your system may simply" + $ECHO "have a non-standard or incomplete GNU Autotools install. If you have" + $ECHO "sufficient system access, it may be possible to quell this warning by" + $ECHO "running:" + $ECHO + sudo -V > /dev/null 2>&1 + if [ $? = 0 ] ; then + $ECHO " sudo ln -s $_glti $_gltidir/libtoolize" + $ECHO + else + $ECHO " ln -s $_glti $_gltidir/libtoolize" + $ECHO + $ECHO "Run that as root or with proper permissions to the $_gltidir directory" + $ECHO + fi + _ltfound=yes + break + fi + done + else + _ltfound=yes + fi +else + _ltfound=yes + $ECHO "Using LIBTOOLIZE environment variable override: $LIBTOOLIZE" +fi + + +############################ +# libtoolize version check # +############################ +_report_error=no +if [ ! "x$_ltfound" = "xyes" ] ; then + $ECHO + $ECHO "ERROR: Unable to locate GNU Libtool." + _report_error=yes +else + _version="`$LIBTOOLIZE --version | head -${HEAD_N}1 | sed 's/[^0-9]*\([0-9\.][0-9\.]*\)/\1/'`" + if [ "x$_version" = "x" ] ; then + _version="0.0.0" + fi + $ECHO "Found GNU Libtool version $_version" + version_check "$LIBTOOL_VERSION" "$_version" + if [ $? -ne 0 ] ; then + _report_error=yes + fi +fi +if [ "x$_report_error" = "xyes" ] ; then + version_error "$LIBTOOL_VERSION" "GNU Libtool" + exit 1 +fi + + +##################### +# check for aclocal # +##################### +if [ "x$ACLOCAL" = "x" ] ; then + for ACLOCAL in aclocal ; do + $VERBOSE_ECHO "Checking aclocal version: $ACLOCAL --version" + $ACLOCAL --version > /dev/null 2>&1 + if [ $? = 0 ] ; then + break + fi + done +else + $ECHO "Using ACLOCAL environment variable override: $ACLOCAL" +fi + + +######################## +# check for autoheader # +######################## +if [ "x$AUTOHEADER" = "x" ] ; then + for AUTOHEADER in autoheader ; do + $VERBOSE_ECHO "Checking autoheader version: $AUTOHEADER --version" + $AUTOHEADER --version > /dev/null 2>&1 + if [ $? = 0 ] ; then + break + fi + done +else + $ECHO "Using AUTOHEADER environment variable override: $AUTOHEADER" +fi + + +######################### +# check if version only # +######################### +$VERBOSE_ECHO "Checking whether to only output version information" +if [ "x$VERSION_ONLY" = "xyes" ] ; then + $ECHO + ident + $ECHO "---" + $ECHO "Version requested. No preparation or configuration will be performed." + exit 0 +fi + + +################################# +# PROTECT_FROM_CLOBBER FUNCTION # +################################# +protect_from_clobber ( ) { + PFC_INIT=1 + + # protect COPYING & INSTALL from overwrite by automake. the + # automake force option will (inappropriately) ignore the existing + # contents of a COPYING and/or INSTALL files (depending on the + # version) instead of just forcing *missing* files like it does + # for AUTHORS, NEWS, and README. this is broken but extremely + # prevalent behavior, so we protect against it by keeping a backup + # of the file that can later be restored. + + for file in COPYING INSTALL ; do + if test -f ${file} ; then + if test -f ${file}.$$.protect_from_automake.backup ; then + $VERBOSE_ECHO "Already backed up ${file} in `pwd`" + else + $VERBOSE_ECHO "Backing up ${file} in `pwd`" + $VERBOSE_ECHO "cp -p ${file} ${file}.$$.protect_from_automake.backup" + cp -p ${file} ${file}.$$.protect_from_automake.backup + fi + fi + done +} + + +############################## +# RECURSIVE_PROTECT FUNCTION # +############################## +recursive_protect ( ) { + + # for projects using recursive configure, run the build + # preparation steps for the subdirectories. this function assumes + # START_PATH was set to pwd before recursion begins so that + # relative paths work. + + # git 'r done, protect COPYING and INSTALL from being clobbered + protect_from_clobber + + if test -d autom4te.cache ; then + $VERBOSE_ECHO "Found an autom4te.cache directory, deleting it" + $VERBOSE_ECHO "rm -rf autom4te.cache" + rm -rf autom4te.cache + fi + + # find configure template + _configure="`locate_configure_template`" + if [ "x$_configure" = "x" ] ; then + return + fi + # $VERBOSE_ECHO "Looking for configure template found `pwd`/$_configure" + + # look for subdirs + # $VERBOSE_ECHO "Looking for subdirs in `pwd`" + _det_config_subdirs="`grep AC_CONFIG_SUBDIRS $_configure | grep -v '.*#.*AC_CONFIG_SUBDIRS' | sed 's/^[ ]*AC_CONFIG_SUBDIRS(\(.*\)).*/\1/' | sed 's/.*\[\(.*\)\].*/\1/'`" + CHECK_DIRS="" + for dir in $_det_config_subdirs ; do + if test -d "`pwd`/$dir" ; then + CHECK_DIRS="$CHECK_DIRS \"`pwd`/$dir\"" + fi + done + + # process subdirs + if [ ! "x$CHECK_DIRS" = "x" ] ; then + $VERBOSE_ECHO "Recursively scanning the following directories:" + $VERBOSE_ECHO " $CHECK_DIRS" + for dir in $CHECK_DIRS ; do + $VERBOSE_ECHO "Protecting files from automake in $dir" + cd "$START_PATH" + eval "cd $dir" + + # recursively git 'r done + recursive_protect + done + fi +} # end of recursive_protect + + +############################# +# RESTORE_CLOBBERED FUNCION # +############################# +restore_clobbered ( ) { + + # The automake (and autoreconf by extension) -f/--force-missing + # option may overwrite COPYING and INSTALL even if they do exist. + # Here we restore the files if necessary. + + spacer=no + + for file in COPYING INSTALL ; do + if test -f ${file}.$$.protect_from_automake.backup ; then + if test -f ${file} ; then + # compare entire content, restore if needed + if test "x`cat ${file}`" != "x`cat ${file}.$$.protect_from_automake.backup`" ; then + if test "x$spacer" = "xno" ; then + $VERBOSE_ECHO + spacer=yes + fi + # restore the backup + $VERBOSE_ECHO "Restoring ${file} from backup (automake -f likely clobbered it)" + $VERBOSE_ECHO "rm -f ${file}" + rm -f ${file} + $VERBOSE_ECHO "mv ${file}.$$.protect_from_automake.backup ${file}" + mv ${file}.$$.protect_from_automake.backup ${file} + fi # check contents + elif test -f ${file}.$$.protect_from_automake.backup ; then + $VERBOSE_ECHO "mv ${file}.$$.protect_from_automake.backup ${file}" + mv ${file}.$$.protect_from_automake.backup ${file} + fi # -f ${file} + + # just in case + $VERBOSE_ECHO "rm -f ${file}.$$.protect_from_automake.backup" + rm -f ${file}.$$.protect_from_automake.backup + fi # -f ${file}.$$.protect_from_automake.backup + done + + CONFIGURE="`locate_configure_template`" + if [ "x$CONFIGURE" = "x" ] ; then + return + fi + + _aux_dir="`grep AC_CONFIG_AUX_DIR $CONFIGURE | grep -v '.*#.*AC_CONFIG_AUX_DIR' | tail -${TAIL_N}1 | sed 's/^[ ]*AC_CONFIG_AUX_DIR(\(.*\)).*/\1/' | sed 's/.*\[\(.*\)\].*/\1/'`" + if test ! -d "$_aux_dir" ; then + _aux_dir=. + fi + + for file in config.guess config.sub ltmain.sh ; do + if test -f "${_aux_dir}/${file}" ; then + $VERBOSE_ECHO "rm -f \"${_aux_dir}/${file}.backup\"" + rm -f "${_aux_dir}/${file}.backup" + fi + done +} # end of restore_clobbered + + +############################## +# RECURSIVE_RESTORE FUNCTION # +############################## +recursive_restore ( ) { + + # restore COPYING and INSTALL from backup if they were clobbered + # for each directory recursively. + + # git 'r undone + restore_clobbered + + # find configure template + _configure="`locate_configure_template`" + if [ "x$_configure" = "x" ] ; then + return + fi + + # look for subdirs + _det_config_subdirs="`grep AC_CONFIG_SUBDIRS $_configure | grep -v '.*#.*AC_CONFIG_SUBDIRS' | sed 's/^[ ]*AC_CONFIG_SUBDIRS(\(.*\)).*/\1/' | sed 's/.*\[\(.*\)\].*/\1/'`" + CHECK_DIRS="" + for dir in $_det_config_subdirs ; do + if test -d "`pwd`/$dir" ; then + CHECK_DIRS="$CHECK_DIRS \"`pwd`/$dir\"" + fi + done + + # process subdirs + if [ ! "x$CHECK_DIRS" = "x" ] ; then + $VERBOSE_ECHO "Recursively scanning the following directories:" + $VERBOSE_ECHO " $CHECK_DIRS" + for dir in $CHECK_DIRS ; do + $VERBOSE_ECHO "Checking files for automake damage in $dir" + cd "$START_PATH" + eval "cd $dir" + + # recursively git 'r undone + recursive_restore + done + fi +} # end of recursive_restore + + +####################### +# INITIALIZE FUNCTION # +####################### +initialize ( ) { + + # this routine performs a variety of directory-specific + # initializations. some are sanity checks, some are preventive, + # and some are necessary setup detection. + # + # this function sets: + # CONFIGURE + # SEARCH_DIRS + # CONFIG_SUBDIRS + + ################################## + # check for a configure template # + ################################## + CONFIGURE="`locate_configure_template`" + if [ "x$CONFIGURE" = "x" ] ; then + $ECHO + $ECHO "A configure.ac or configure.in file could not be located implying" + $ECHO "that the GNU Build System is at least not used in this directory. In" + $ECHO "any case, there is nothing to do here without one of those files." + $ECHO + $ECHO "ERROR: No configure.in or configure.ac file found in `pwd`" + exit 1 + fi + + ##################### + # detect an aux dir # + ##################### + _aux_dir="`grep AC_CONFIG_AUX_DIR $CONFIGURE | grep -v '.*#.*AC_CONFIG_AUX_DIR' | tail -${TAIL_N}1 | sed 's/^[ ]*AC_CONFIG_AUX_DIR(\(.*\)).*/\1/' | sed 's/.*\[\(.*\)\].*/\1/'`" + if test ! -d "$_aux_dir" ; then + _aux_dir=. + else + $VERBOSE_ECHO "Detected auxillary directory: $_aux_dir" + fi + + ################################ + # detect a recursive configure # + ################################ + CONFIG_SUBDIRS="" + _det_config_subdirs="`grep AC_CONFIG_SUBDIRS $CONFIGURE | grep -v '.*#.*AC_CONFIG_SUBDIRS' | sed 's/^[ ]*AC_CONFIG_SUBDIRS(\(.*\)).*/\1/' | sed 's/.*\[\(.*\)\].*/\1/'`" + for dir in $_det_config_subdirs ; do + if test -d "`pwd`/$dir" ; then + $VERBOSE_ECHO "Detected recursive configure directory: `pwd`/$dir" + CONFIG_SUBDIRS="$CONFIG_SUBDIRS `pwd`/$dir" + fi + done + + ########################################################### + # make sure certain required files exist for GNU projects # + ########################################################### + _marker_found="" + _marker_found_message_intro='Detected non-GNU marker "' + _marker_found_message_mid='" in ' + for marker in foreign cygnus ; do + _marker_found_message=${_marker_found_message_intro}${marker}${_marker_found_message_mid} + _marker_found="`grep 'AM_INIT_AUTOMAKE.*'${marker} $CONFIGURE`" + if [ ! "x$_marker_found" = "x" ] ; then + $VERBOSE_ECHO "${_marker_found_message}`basename \"$CONFIGURE\"`" + break + fi + if test -f "`dirname \"$CONFIGURE\"/Makefile.am`" ; then + _marker_found="`grep 'AUTOMAKE_OPTIONS.*'${marker} Makefile.am`" + if [ ! "x$_marker_found" = "x" ] ; then + $VERBOSE_ECHO "${_marker_found_message}Makefile.am" + break + fi + fi + done + if [ "x${_marker_found}" = "x" ] ; then + _suggest_foreign=no + for file in AUTHORS COPYING ChangeLog INSTALL NEWS README ; do + if [ ! -f $file ] ; then + $VERBOSE_ECHO "Touching ${file} since it does not exist" + _suggest_foreign=yes + touch $file + fi + done + + if [ "x${_suggest_foreign}" = "xyes" ] ; then + $ECHO + $ECHO "Warning: Several files expected of projects that conform to the GNU" + $ECHO "coding standards were not found. The files were automatically added" + $ECHO "for you since you do not have a 'foreign' declaration specified." + $ECHO + $ECHO "Considered adding 'foreign' to AM_INIT_AUTOMAKE in `basename \"$CONFIGURE\"`" + if test -f "`dirname \"$CONFIGURE\"/Makefile.am`" ; then + $ECHO "or to AUTOMAKE_OPTIONS in your top-level Makefile.am file." + fi + $ECHO + fi + fi + + ################################################## + # make sure certain generated files do not exist # + ################################################## + for file in config.guess config.sub ltmain.sh ; do + if test -f "${_aux_dir}/${file}" ; then + $VERBOSE_ECHO "mv -f \"${_aux_dir}/${file}\" \"${_aux_dir}/${file}.backup\"" + mv -f "${_aux_dir}/${file}" "${_aux_dir}/${file}.backup" + fi + done + + ############################ + # search alternate m4 dirs # + ############################ + SEARCH_DIRS="" + for dir in m4 ; do + if [ -d $dir ] ; then + $VERBOSE_ECHO "Found extra aclocal search directory: $dir" + SEARCH_DIRS="$SEARCH_DIRS -I $dir" + fi + done + + ###################################### + # remove any previous build products # + ###################################### + if test -d autom4te.cache ; then + $VERBOSE_ECHO "Found an autom4te.cache directory, deleting it" + $VERBOSE_ECHO "rm -rf autom4te.cache" + rm -rf autom4te.cache + fi +# tcl/tk (and probably others) have a customized aclocal.m4, so can't delete it +# if test -f aclocal.m4 ; then +# $VERBOSE_ECHO "Found an aclocal.m4 file, deleting it" +# $VERBOSE_ECHO "rm -f aclocal.m4" +# rm -f aclocal.m4 +# fi + +} # end of initialize() + + +############## +# initialize # +############## + +# stash path +START_PATH="`pwd`" + +# Before running autoreconf or manual steps, some prep detection work +# is necessary or useful. Only needs to occur once per directory, but +# does need to traverse the entire subconfigure hierarchy to protect +# files from being clobbered even by autoreconf. +recursive_protect + +# start from where we started +cd "$START_PATH" + +# get ready to process +initialize + + +######################################### +# DOWNLOAD_GNULIB_CONFIG_GUESS FUNCTION # +######################################### + +# TODO - should make sure wget/curl exist and/or work before trying to +# use them. + +download_gnulib_config_guess () { + # abuse gitweb to download gnulib's latest config.guess via HTTP + config_guess_temp="config.guess.$$.download" + ret=1 + for __cmd in wget curl fetch ; do + $VERBOSE_ECHO "Checking for command ${__cmd}" + ${__cmd} --version > /dev/null 2>&1 + ret=$? + if [ ! $ret = 0 ] ; then + continue + fi + + __cmd_version=`${__cmd} --version | head -n 1 | sed -e 's/^[^0-9]\+//' -e 's/ .*//'` + $VERBOSE_ECHO "Found ${__cmd} ${__cmd_version}" + + opts="" + case ${__cmd} in + wget) + opts="-O" + ;; + curl) + opts="-o" + ;; + fetch) + opts="-t 5 -f" + ;; + esac + + $VERBOSE_ECHO "Running $__cmd \"${CONFIG_GUESS_URL}\" $opts \"${config_guess_temp}\"" + eval "$__cmd \"${CONFIG_GUESS_URL}\" $opts \"${config_guess_temp}\"" > /dev/null 2>&1 + if [ $? = 0 ] ; then + mv -f "${config_guess_temp}" ${_aux_dir}/config.guess + ret=0 + break + fi + done + + if [ ! $ret = 0 ] ; then + $ECHO "Warning: config.guess download failed from: $CONFIG_GUESS_URL" + rm -f "${config_guess_temp}" + fi +} + + +############################## +# LIBTOOLIZE_NEEDED FUNCTION # +############################## +libtoolize_needed () { + ret=1 # means no, don't need libtoolize + for feature in AC_PROG_LIBTOOL AM_PROG_LIBTOOL LT_INIT ; do + $VERBOSE_ECHO "Searching for $feature in $CONFIGURE" + found="`grep \"^$feature.*\" $CONFIGURE`" + if [ ! "x$found" = "x" ] ; then + ret=0 # means yes, need to run libtoolize + break + fi + done + return ${ret} +} + + + +############################################ +# prepare build via autoreconf or manually # +############################################ +reconfigure_manually=no +if [ "x$HAVE_AUTORECONF" = "xyes" ] ; then + $ECHO + $ECHO $ECHO_N "Automatically preparing build ... $ECHO_C" + + $VERBOSE_ECHO "$AUTORECONF $SEARCH_DIRS $AUTORECONF_OPTIONS" + autoreconf_output="`$AUTORECONF $SEARCH_DIRS $AUTORECONF_OPTIONS 2>&1`" + ret=$? + $VERBOSE_ECHO "$autoreconf_output" + + if [ ! $ret = 0 ] ; then + if [ "x$HAVE_ALT_LIBTOOLIZE" = "xyes" ] ; then + if [ ! "x`echo \"$autoreconf_output\" | grep libtoolize | grep \"No such file or directory\"`" = "x" ] ; then + $ECHO + $ECHO "Warning: autoreconf failed but due to what is usually a common libtool" + $ECHO "misconfiguration issue. This problem is encountered on systems that" + $ECHO "have installed libtoolize under a different name without providing a" + $ECHO "symbolic link or without setting the LIBTOOLIZE environment variable." + $ECHO + $ECHO "Restarting the preparation steps with LIBTOOLIZE set to $LIBTOOLIZE" + + export LIBTOOLIZE + RUN_RECURSIVE=no + export RUN_RECURSIVE + untrap_abnormal + + $VERBOSE_ECHO sh $AUTOGEN_SH "$1" "$2" "$3" "$4" "$5" "$6" "$7" "$8" "$9" + sh "$AUTOGEN_SH" "$1" "$2" "$3" "$4" "$5" "$6" "$7" "$8" "$9" + exit $? + fi + fi + + $ECHO "Warning: $AUTORECONF failed" + + if test -f ltmain.sh ; then + $ECHO "libtoolize being run by autoreconf is not creating ltmain.sh in the auxillary directory like it should" + fi + + $ECHO "Attempting to run the preparation steps individually" + reconfigure_manually=yes + else + if [ "x$DOWNLOAD" = "xyes" ] ; then + if libtoolize_needed ; then + download_gnulib_config_guess + fi + fi + fi +else + reconfigure_manually=yes +fi + + +############################ +# LIBTOOL_FAILURE FUNCTION # +############################ +libtool_failure ( ) { + + # libtool is rather error-prone in comparison to the other + # autotools and this routine attempts to compensate for some + # common failures. the output after a libtoolize failure is + # parsed for an error related to AC_PROG_LIBTOOL and if found, we + # attempt to inject a project-provided libtool.m4 file. + + _autoconf_output="$1" + + if [ "x$RUN_RECURSIVE" = "xno" ] ; then + # we already tried the libtool.m4, don't try again + return 1 + fi + + if test -f "$LIBTOOL_M4" ; then + found_libtool="`$ECHO $_autoconf_output | grep AC_PROG_LIBTOOL`" + if test ! "x$found_libtool" = "x" ; then + if test -f acinclude.m4 ; then + rm -f acinclude.m4.$$.backup + $VERBOSE_ECHO "cat acinclude.m4 > acinclude.m4.$$.backup" + cat acinclude.m4 > acinclude.m4.$$.backup + fi + $VERBOSE_ECHO "cat \"$LIBTOOL_M4\" >> acinclude.m4" + chmod u+w acinclude.m4 + cat "$LIBTOOL_M4" >> acinclude.m4 + + # don't keep doing this + RUN_RECURSIVE=no + export RUN_RECURSIVE + untrap_abnormal + + $ECHO + $ECHO "Restarting the preparation steps with libtool macros in acinclude.m4" + $VERBOSE_ECHO sh $AUTOGEN_SH "$1" "$2" "$3" "$4" "$5" "$6" "$7" "$8" "$9" + sh "$AUTOGEN_SH" "$1" "$2" "$3" "$4" "$5" "$6" "$7" "$8" "$9" + exit $? + fi + fi +} + + +########################### +# MANUAL_AUTOGEN FUNCTION # +########################### +manual_autogen ( ) { + + ################################################## + # Manual preparation steps taken are as follows: # + # aclocal [-I m4] # + # libtoolize --automake -c -f # + # aclocal [-I m4] # + # autoconf -f # + # autoheader # + # automake -a -c -f # + ################################################## + + ########### + # aclocal # + ########### + $VERBOSE_ECHO "$ACLOCAL $SEARCH_DIRS $ACLOCAL_OPTIONS" + aclocal_output="`$ACLOCAL $SEARCH_DIRS $ACLOCAL_OPTIONS 2>&1`" + ret=$? + $VERBOSE_ECHO "$aclocal_output" + if [ ! $ret = 0 ] ; then $ECHO "ERROR: $ACLOCAL failed" && exit 2 ; fi + + ############## + # libtoolize # + ############## + if libtoolize_needed ; then + if [ "x$HAVE_LIBTOOLIZE" = "xyes" ] ; then + $VERBOSE_ECHO "$LIBTOOLIZE $LIBTOOLIZE_OPTIONS" + libtoolize_output="`$LIBTOOLIZE $LIBTOOLIZE_OPTIONS 2>&1`" + ret=$? + $VERBOSE_ECHO "$libtoolize_output" + + if [ ! $ret = 0 ] ; then $ECHO "ERROR: $LIBTOOLIZE failed" && exit 2 ; fi + else + if [ "x$HAVE_ALT_LIBTOOLIZE" = "xyes" ] ; then + $VERBOSE_ECHO "$LIBTOOLIZE $ALT_LIBTOOLIZE_OPTIONS" + libtoolize_output="`$LIBTOOLIZE $ALT_LIBTOOLIZE_OPTIONS 2>&1`" + ret=$? + $VERBOSE_ECHO "$libtoolize_output" + + if [ ! $ret = 0 ] ; then $ECHO "ERROR: $LIBTOOLIZE failed" && exit 2 ; fi + fi + fi + + ########### + # aclocal # + ########### + # re-run again as instructed by libtoolize + $VERBOSE_ECHO "$ACLOCAL $SEARCH_DIRS $ACLOCAL_OPTIONS" + aclocal_output="`$ACLOCAL $SEARCH_DIRS $ACLOCAL_OPTIONS 2>&1`" + ret=$? + $VERBOSE_ECHO "$aclocal_output" + + # libtoolize might put ltmain.sh in the wrong place + if test -f ltmain.sh ; then + if test ! -f "${_aux_dir}/ltmain.sh" ; then + $ECHO + $ECHO "Warning: $LIBTOOLIZE is creating ltmain.sh in the wrong directory" + $ECHO + $ECHO "Fortunately, the problem can be worked around by simply copying the" + $ECHO "file to the appropriate location (${_aux_dir}/). This has been done for you." + $ECHO + $VERBOSE_ECHO "cp -p ltmain.sh \"${_aux_dir}/ltmain.sh\"" + cp -p ltmain.sh "${_aux_dir}/ltmain.sh" + $ECHO $ECHO_N "Continuing build preparation ... $ECHO_C" + fi + fi # ltmain.sh + + if [ "x$DOWNLOAD" = "xyes" ] ; then + download_gnulib_config_guess + fi + fi # libtoolize_needed + + ############ + # autoconf # + ############ + $VERBOSE_ECHO + $VERBOSE_ECHO "$AUTOCONF $AUTOCONF_OPTIONS" + autoconf_output="`$AUTOCONF $AUTOCONF_OPTIONS 2>&1`" + ret=$? + $VERBOSE_ECHO "$autoconf_output" + + if [ ! $ret = 0 ] ; then + # retry without the -f and check for usage of macros that are too new + ac2_59_macros="AC_C_RESTRICT AC_INCLUDES_DEFAULT AC_LANG_ASSERT AC_LANG_WERROR AS_SET_CATFILE" + ac2_55_macros="AC_COMPILER_IFELSE AC_FUNC_MBRTOWC AC_HEADER_STDBOOL AC_LANG_CONFTEST AC_LANG_SOURCE AC_LANG_PROGRAM AC_LANG_CALL AC_LANG_FUNC_TRY_LINK AC_MSG_FAILURE AC_PREPROC_IFELSE" + ac2_54_macros="AC_C_BACKSLASH_A AC_CONFIG_LIBOBJ_DIR AC_GNU_SOURCE AC_PROG_EGREP AC_PROG_FGREP AC_REPLACE_FNMATCH AC_FUNC_FNMATCH_GNU AC_FUNC_REALLOC AC_TYPE_MBSTATE_T" + + macros_to_search="" + ac_major="`echo ${AUTOCONF_VERSION}. | cut -d. -f1 | sed 's/[^0-9]//g'`" + ac_minor="`echo ${AUTOCONF_VERSION}. | cut -d. -f2 | sed 's/[^0-9]//g'`" + + if [ $ac_major -lt 2 ] ; then + macros_to_search="$ac2_59_macros $ac2_55_macros $ac2_54_macros" + else + if [ $ac_minor -lt 54 ] ; then + macros_to_search="$ac2_59_macros $ac2_55_macros $ac2_54_macros" + elif [ $ac_minor -lt 55 ] ; then + macros_to_search="$ac2_59_macros $ac2_55_macros" + elif [ $ac_minor -lt 59 ] ; then + macros_to_search="$ac2_59_macros" + fi + fi + + configure_ac_macros=__none__ + for feature in $macros_to_search ; do + $VERBOSE_ECHO "Searching for $feature in $CONFIGURE" + found="`grep \"^$feature.*\" $CONFIGURE`" + if [ ! "x$found" = "x" ] ; then + if [ "x$configure_ac_macros" = "x__none__" ] ; then + configure_ac_macros="$feature" + else + configure_ac_macros="$feature $configure_ac_macros" + fi + fi + done + if [ ! "x$configure_ac_macros" = "x__none__" ] ; then + $ECHO + $ECHO "Warning: Unsupported macros were found in $CONFIGURE" + $ECHO + $ECHO "The `basename \"$CONFIGURE\"` file was scanned in order to determine if any" + $ECHO "unsupported macros are used that exceed the minimum version" + $ECHO "settings specified within this file. As such, the following macros" + $ECHO "should be removed from configure.ac or the version numbers in this" + $ECHO "file should be increased:" + $ECHO + $ECHO "$configure_ac_macros" + $ECHO + $ECHO $ECHO_N "Ignorantly continuing build preparation ... $ECHO_C" + fi + + ################### + # autoconf, retry # + ################### + $VERBOSE_ECHO + $VERBOSE_ECHO "$AUTOCONF" + autoconf_output="`$AUTOCONF 2>&1`" + ret=$? + $VERBOSE_ECHO "$autoconf_output" + + if [ ! $ret = 0 ] ; then + # test if libtool is busted + libtool_failure "$autoconf_output" + + # let the user know what went wrong + cat <"]) +fi + +case "$target" in + linux) + os=linux + ;; + windows) + os=windows + ;; + osx) + os=osx + ;; + *) + AC_MSG_ERROR(["Unrecognised target OS: $target"]) + ;; +esac +AM_CONDITIONAL(LINUX, test "x$os" = "xlinux") +AM_CONDITIONAL(WINDOWS, test "x$os" = "xwindows") +AM_CONDITIONAL(OSX, test "x$os" = "xosx") + +# ---------------------------------------------------------------------- + +# --enable-vst. VST compilation is disabled by default +# +# WITH_VST 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)] +) + +# ---------------------------------------------------------------------- + +# Check for programs. + +AC_PROG_CXX +AC_PROG_CC +AC_PROG_INSTALL +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 + +AC_LANG_PUSH([C++]) +AC_CHECK_HEADER( + [RtMidi.h], + [], + [AC_MSG_ERROR([library 'rtMidi' 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 + +# brutal and temporary hack for OS X: don't use pkg-config + +if test "x$os" = "xosx"; then + AC_LANG_PUSH([C++]) + AC_CHECK_HEADER( + [samplerate.h], + [], + [AC_MSG_ERROR([library 'samplerate' not found!])] + ) + AC_LANG_POP +else +# PKG_CHECK_MODULES( +# SAMPLERATE, +# samplerate >= 0.1.8, +# [], +# AC_MSG_ERROR([library 'libsamplerate' not found!]) +# ) + AC_LANG_PUSH([C++]) + AC_CHECK_HEADER( + [samplerate.h], + [], + [AC_MSG_ERROR([library 'samplerate' not found!])] + ) + AC_LANG_POP +fi + + +# ---------------------------------------------------------------------- + +# Check for linux header files. + +if test "x$os" = "xlinux"; then + +# TODO + +# AC_LANG_PUSH([C++]) +# AC_CHECK_HEADER( +# [X11/extensions/Xext.h], +# [], +# [AC_MSG_ERROR([missing Xext.h, maybe you need to install the libxext-dev package?])] +# ) +# AC_LANG_POP + +# AC_LANG_PUSH([C++]) +# AC_CHECK_HEADER( +# [X11/Xft.h], +# [], +# [AC_MSG_ERROR([missing Xft.h, maybe you need to install the libxft-dev package?])] +# ) +# AC_LANG_POP + + 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 src/Makefile]) +AC_OUTPUT diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..644c35d --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,115 @@ +AUTOMAKE_OPTIONS = foreign + + + +# aeffect.h (header from VST SDK) uses 'long long' which is not supported +# in ISO C++ 1998 and -Werror flag breaks the compilation. +# This is a workaround, fixes needed. + +if WINDOWS +AM_CXXFLAGS = -Wall -pedantic +else +AM_CXXFLAGS = -Wall -pedantic -Werror +endif + +bin_PROGRAMS = giada + +giada_SOURCES = \ +const.h gd_keyGrabber.h glue.h mixerHandler.cpp \ +gd_about.cpp gd_mainWindow.cpp graphics.cpp mixerHandler.h \ +gd_about.h gd_mainWindow.h graphics.h patch.cpp \ +gd_beatsInput.cpp gd_warnings.cpp ge_mixed.cpp patch.h \ +gd_beatsInput.h gd_warnings.h ge_mixed.h recorder.cpp \ +gd_bpmInput.cpp ge_waveform.cpp gui_utils.cpp recorder.h \ +gd_bpmInput.h ge_waveform.h gui_utils.h utils.cpp \ +gd_browser.cpp init.cpp channel.h utils.h \ +gd_browser.h init.h gd_config.cpp channel.cpp \ +gg_keyboard.cpp kernelAudio.cpp wave.cpp gd_config.h \ +gg_keyboard.h kernelAudio.h waveFx.cpp gd_editor.cpp \ +gg_waveTools.cpp main.cpp waveFx.h gd_editor.h \ +gg_waveTools.h mixer.cpp wave.h gd_keyGrabber.cpp \ +glue.cpp mixer.h ge_browser.h ge_browser.cpp \ +gd_devInfo.cpp gd_devInfo.h plugin.h plugin.cpp \ +pluginHost.h pluginHost.cpp gd_pluginList.h gd_pluginList.cpp \ +gd_pluginWindow.h gd_pluginWindow.cpp ge_window.h ge_window.cpp \ +dataStorage.h dataStorage.cpp conf.h conf.cpp \ +gd_actionEditor.h gd_actionEditor.cpp ge_muteChannel.h ge_muteChannel.cpp \ +ge_actionChannel.h ge_actionChannel.cpp gd_pluginWindowGUI.h gd_pluginWindowGUI.cpp \ +ge_actionWidget.h ge_actionWidget.cpp ge_envelopeChannel.h ge_envelopeChannel.cpp \ +ge_pianoRoll.h ge_pianoRoll.cpp kernelMidi.h kernelMidi.cpp \ +gd_midiOutputSetup.h gd_midiOutputSetup.cpp gd_midiGrabber.h gd_midiGrabber.cpp \ +sampleChannel.h sampleChannel.cpp midiChannel.cpp midiChannel.h \ +ge_channel.h ge_channel.cpp log.h log.cpp \ +ge_column.h ge_column.cpp ge_sampleChannel.h ge_sampleChannel.cpp \ +ge_midiChannel.h ge_midiChannel.cpp + + + +# Check for environment: these vars are defined via AM_CONDITIONAL +# inside configure.ac + +if LINUX +giada_LDADD = -lsndfile -lfltk -lXext -lX11 -lXft -lXpm -lm \ + rtaudio-mod/librtaudio.a -ljack -lasound -lpthread -ldl \ + -lpulse-simple -lpulse -lsamplerate -lrtmidi +endif +if WINDOWS +giada_LDADD = -lrtaudio -ldsound -lwsock32 -lm -lpthread \ + -lfltk -lwininet -lgdi32 -lshell32 -lvfw32 -lrpcrt4 \ + -luuid -lcomctl32 -lole32 -lws2_32 -lsndfile \ + -lsamplerate -lrtmidi -lwinmm -lsetupapi -lksuser +giada_LDFLAGS = -mwindows -mno-cygwin -static +giada_SOURCES += resource.rc +endif +if OSX +giada_LDADD = -lsndfile -lm -lpthread -lfltk -lrtmidi -lrtaudio \ + -lsamplerate +giada_LDFLAGS = -framework CoreAudio -framework Cocoa -framework Carbon \ + -framework CoreMIDI -framework CoreFoundation +endif + + +# used only under MinGW to compile the resource.rc file (program icon) + +.rc.o: + windres $^ -o $@ +%.o : %.rc + windres $^ -o $@ + + + +#compile libraries + +libs: +if LINUX + @cd rtaudio-mod; echo "Building RtAudio for Linux..."; \ + ./configure --with-jack --with-alsa --with-pulse; \ + make; +endif +if WINDOWS + @cd rtaudio-mod; echo "Building RtAudio for Windows..."; \ + ./configure --with-asio --with-ds; \ + make; +endif +if OSX + @cd rtaudio-mod; echo "Building RtAudio for OS X..."; \ + ./configure --with-core; \ + make; +endif + + + +# rename the binaries + +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 diff --git a/src/channel.cpp b/src/channel.cpp new file mode 100644 index 0000000..f9a3b7c --- /dev/null +++ b/src/channel.cpp @@ -0,0 +1,141 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * channel + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include "channel.h" +#include "ge_channel.h" +#include "pluginHost.h" +#include "kernelMidi.h" +#include "patch.h" +#include "wave.h" +#include "mixer.h" +#include "mixerHandler.h" +#include "conf.h" +#include "waveFx.h" +#include "log.h" + + +extern Patch G_Patch; +extern Mixer G_Mixer; +extern Conf G_Conf; +#ifdef WITH_VST +extern PluginHost G_PluginHost; +#endif + + +Channel::Channel(int type, int status, int bufferSize) + : bufferSize(bufferSize), + type (type), + status (status), + key (0), + volume (DEFAULT_VOL), + volume_i (1.0f), + volume_d (0.0f), + panLeft (1.0f), + panRight (1.0f), + mute_i (false), + mute_s (false), + mute (false), + solo (false), + hasActions(false), + recStatus (REC_STOPPED), + vChan (NULL), + guiChannel(NULL), + midiIn (true), + midiInKeyPress(0x0), + midiInKeyRel (0x0), + midiInKill (0x0), + midiInVolume (0x0), + midiInMute (0x0), + midiInSolo (0x0) +{ + vChan = (float *) malloc(bufferSize * sizeof(float)); + if (!vChan) + gLog("[Channel] unable to alloc memory for vChan\n"); + memset(vChan, 0, bufferSize * sizeof(float)); +} + + +/* -------------------------------------------------------------------------- */ + + +Channel::~Channel() +{ + status = STATUS_OFF; + if (vChan) + free(vChan); +} + + +/* -------------------------------------------------------------------------- */ + + +void Channel::readPatchMidiIn(int i) +{ + midiIn = G_Patch.getMidiValue(i, "In"); + midiInKeyPress = G_Patch.getMidiValue(i, "InKeyPress"); + midiInKeyRel = G_Patch.getMidiValue(i, "InKeyRel"); + midiInKill = G_Patch.getMidiValue(i, "InKill"); + midiInVolume = G_Patch.getMidiValue(i, "InVolume"); + midiInMute = G_Patch.getMidiValue(i, "InMute"); + midiInSolo = G_Patch.getMidiValue(i, "InSolo"); +} + + +/* -------------------------------------------------------------------------- */ + + +bool Channel::isPlaying() +{ + return status & (STATUS_PLAY | STATUS_ENDING); +} + + +/* -------------------------------------------------------------------------- */ + + +void Channel::writePatch(FILE *fp, int i, bool isProject) +{ + fprintf(fp, "chanType%d=%d\n", i, type); + fprintf(fp, "chanIndex%d=%d\n", i, index); + fprintf(fp, "chanColumn%d=%d\n", i, guiChannel->getColumnIndex()); + fprintf(fp, "chanMute%d=%d\n", i, mute); + fprintf(fp, "chanMute_s%d=%d\n", i, mute_s); + fprintf(fp, "chanSolo%d=%d\n", i, solo); + fprintf(fp, "chanvol%d=%f\n", i, volume); + fprintf(fp, "chanPanLeft%d=%f\n", i, panLeft); + fprintf(fp, "chanPanRight%d=%f\n", i, panRight); + + fprintf(fp, "chanMidiIn%d=%u\n", i, midiIn); + fprintf(fp, "chanMidiInKeyPress%d=%u\n", i, midiInKeyPress); + fprintf(fp, "chanMidiInKeyRel%d=%u\n", i, midiInKeyRel); + fprintf(fp, "chanMidiInKill%d=%u\n", i, midiInKill); + fprintf(fp, "chanMidiInVolume%d=%u\n", i, midiInVolume); + fprintf(fp, "chanMidiInMute%d=%u\n", i, midiInMute); + fprintf(fp, "chanMidiInSolo%d=%u\n", i, midiInSolo); +} diff --git a/src/channel.h b/src/channel.h new file mode 100644 index 0000000..6540fab --- /dev/null +++ b/src/channel.h @@ -0,0 +1,195 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * channel + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#ifndef CHANNEL_H +#define CHANNEL_H + + +#include "utils.h" +#include "const.h" +#include "recorder.h" + + +#ifdef WITH_VST + +/* before including aeffetx(x).h we must define __cdecl, otherwise VST + * headers can't be compiled correctly. In windows __cdecl is already + * defined. */ + + #ifdef __GNUC__ + #ifndef _WIN32 + #define __cdecl + #endif + #endif + #include "vst/aeffectx.h" + +#endif + + +class Channel { + +protected: + + /* bufferSize + * size of every buffer in this channel (vChan, pChan) */ + + int bufferSize; + +public: + + Channel(int type, int status, int bufferSize); + virtual ~Channel(); + + /* writePatch + * store values in patch, writing to *fp. */ + + virtual void writePatch(FILE *fp, int i, bool isProject); + + /* loadByPatch + * load a sample inside a patch. */ + + virtual int loadByPatch(const char *file, int i) = 0; + + /* process + * merge vChannels into buffer, plus plugin processing (if any). */ + + virtual void process(float *buffer) = 0; + + /* start + * action to do when channel starts. doQuantize = false (don't + * quantize) when Mixer is reading actions from Recorder::. */ + + virtual void start(int frame, bool doQuantize) = 0; + + /* stop + * action to do when channel is stopped normally (via key or MIDI). */ + + virtual void stop() = 0; + + /* kill + * action to do when channel stops abruptly. */ + + virtual void kill(int frame) = 0; + + /* mute + * action to do when channel is muted. If internal == true, set + * internal mute without altering main mute. */ + + virtual void setMute (bool internal) = 0; + virtual void unsetMute(bool internal) = 0; + + /* empty + * free any associated resources (e.g. waveform for SAMPLE). */ + + virtual void empty() = 0; + + /* stopBySeq + * action to do when channel is stopped by sequencer. */ + + virtual void stopBySeq() = 0; + + /* quantize + * start channel according to quantizer. Index = array index of + * mixer::channels, used by recorder. LocalFrame = frame within buffer. + * GloalFrame = actual frame from mixer. */ + + virtual void quantize(int index, int localFrame, int globalFrame) = 0; + + /* onZero + * action to do when frame goes to zero, i.e. sequencer restart. */ + + virtual void onZero(int frame) = 0; + + /* onBar + * action to do when a bar has passed. */ + + virtual void onBar(int frame) = 0; + + /* parseAction + * do something on a recorded action. Parameters: + * action *a - action to parse + * localFrame - frame number of the processed buffer + * globalFrame - actual frame in Mixer */ + + virtual void parseAction(recorder::action *a, int localFrame, int globalFrame) = 0; + + /* rewind + * rewind channel when rewind button is pressed. */ + + virtual void rewind() = 0; + + /* ------------------------------------------------------------------------ */ + + int index; // unique id + int type; // midi or sample + int status; // status: see const.h + int key; // keyboard button + float volume; // global volume + float volume_i; // internal volume + float volume_d; // delta volume (for envelope) + float panLeft; + float panRight; + bool mute_i; // internal mute + bool mute_s; // previous mute status after being solo'd + bool mute; // global mute + bool solo; + bool hasActions; // has something recorded + int recStatus; // status of recordings (waiting, ending, ...) + float *vChan; // virtual channel + class gChannel *guiChannel; // pointer to a gChannel object, part of the GUI + + bool midiIn; // enable midi output + uint32_t midiInKeyPress; + uint32_t midiInKeyRel; + uint32_t midiInKill; + uint32_t midiInVolume; + uint32_t midiInMute; + uint32_t midiInSolo; + +#ifdef WITH_VST + gVector plugins; +#endif + + + /* ------------------------------------------------------------------------ */ + + /* isPlaying + * tell wether the channel is playing or is stopped. */ + + bool isPlaying(); + + /* readPatchMidiIn + * read from patch all midi-related parameters such as keypress, mute + * and so on. */ + + void readPatchMidiIn(int i); +}; + + +#endif diff --git a/src/conf.cpp b/src/conf.cpp new file mode 100644 index 0000000..af1a4fe --- /dev/null +++ b/src/conf.cpp @@ -0,0 +1,473 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * conf + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + + +#include +#include "const.h" +#include "conf.h" +#include "utils.h" +#include "log.h" + + +int Conf::openFileForReading() +{ + char path[PATH_MAX]; + +#if defined(__linux__) + snprintf(path, PATH_MAX, "%s/.giada/%s", getenv("HOME"), CONF_FILENAME); +#elif defined(_WIN32) + snprintf(path, PATH_MAX, "%s", CONF_FILENAME); +#elif defined(__APPLE__) + struct passwd *p = getpwuid(getuid()); + if (p == NULL) { + gLog("[Conf::openFile] unable to fetch user infos\n"); + return 0; + } + else { + const char *home = p->pw_dir; + snprintf(path, PATH_MAX, "%s/Library/Application Support/Giada/giada.conf", home); + } +#endif + + fp = fopen(path, "r"); + if (fp == NULL) { + gLog("[Conf::openFile] unable to open conf file for reading\n"); + return 0; + } + return 1; +} + + +/* ------------------------------------------------------------------ */ + + +int Conf::createConfigFolder(const char *path) +{ + if (gDirExists(path)) + return 1; + + gLog("[Conf] .giada folder not present. Updating...\n"); + + if (gMkdir(path)) { + gLog("[Conf] status: ok\n"); + return 1; + } + else { + gLog("[Conf] status: error!\n"); + return 0; + } +} + + +/* ------------------------------------------------------------------ */ + + +int Conf::openFileForWriting() +{ + /* writing config file. In windows is in the same dir of the .exe, + * in Linux and OS X in home */ + +#if defined(__linux__) + + char giadaPath[PATH_MAX]; + sprintf(giadaPath, "%s/.giada", getenv("HOME")); + + if (!createConfigFolder(giadaPath)) + return 0; + + char path[PATH_MAX]; + sprintf(path, "%s/%s", giadaPath, CONF_FILENAME); + +#elif defined(_WIN32) + + const char *path = CONF_FILENAME; + +#elif defined(__APPLE__) + + struct passwd *p = getpwuid(getuid()); + const char *home = p->pw_dir; + char giadaPath[PATH_MAX]; + snprintf(giadaPath, PATH_MAX, "%s/Library/Application Support/Giada", home); + + if (!createConfigFolder(giadaPath)) + return 0; + + char path[PATH_MAX]; + sprintf(path, "%s/%s", giadaPath, CONF_FILENAME); + +#endif + + fp = fopen(path, "w"); + if (fp == NULL) + return 0; + return 1; + +} + + +/* ------------------------------------------------------------------ */ + + +void Conf::setDefault() +{ + logMode = LOG_MODE_MUTE; + + soundSystem = DEFAULT_SOUNDSYS; + soundDeviceOut = DEFAULT_SOUNDDEV_OUT; + soundDeviceIn = DEFAULT_SOUNDDEV_IN; + samplerate = DEFAULT_SAMPLERATE; + buffersize = DEFAULT_BUFSIZE; + delayComp = DEFAULT_DELAYCOMP; + limitOutput = false; + rsmpQuality = 0; + + midiPortIn = DEFAULT_MIDI_PORT_IN; + noNoteOff = false; + midiPortOut = DEFAULT_MIDI_PORT_OUT; + midiSync = MIDI_SYNC_NONE; + midiTCfps = 25.0f; + + midiInRewind = 0x0; + midiInStartStop = 0x0; + midiInActionRec = 0x0; + midiInInputRec = 0x0; + midiInVolumeIn = 0x0; + midiInVolumeOut = 0x0; + midiInBeatDouble = 0x0; + midiInBeatHalf = 0x0; + midiInMetronome = 0x0; + + pluginPath[0] = '\0'; + patchPath [0] = '\0'; + samplePath[0] = '\0'; + + recsStopOnChanHalt = false; + chansStopOnSeqHalt = false; + treatRecsAsLoops = false; + + resizeRecordings = true; + + actionEditorZoom = 100; + actionEditorGridOn = false; + actionEditorGridVal = 1; + + mainWindowX = 0; + mainWindowY = 0; + mainWindowW = GUI_WIDTH; + mainWindowH = GUI_HEIGHT; + + pianoRollY = -1; + pianoRollH = 422; +} + + + +/* ------------------------------------------------------------------ */ + + +int Conf::read() +{ + setDefault(); + + if (!openFileForReading()) { + gLog("[Conf] unreadable .conf file, using default parameters\n"); + return 0; + } + + if (getValue("header") != "GIADACFG") { + gLog("[Conf] corrupted .conf file, using default parameters\n"); + return -1; + } + + logMode = atoi(getValue("logMode").c_str()); + + soundSystem = atoi(getValue("soundSystem").c_str()); + if (!soundSystem & (SYS_API_ANY)) soundSystem = DEFAULT_SOUNDSYS; + + soundDeviceOut = atoi(getValue("soundDeviceOut").c_str()); + if (soundDeviceOut < 0) soundDeviceOut = DEFAULT_SOUNDDEV_OUT; + + soundDeviceIn = atoi(getValue("soundDeviceIn").c_str()); + if (soundDeviceIn < -1) soundDeviceIn = DEFAULT_SOUNDDEV_IN; + + channelsOut = atoi(getValue("channelsOut").c_str()); + channelsIn = atoi(getValue("channelsIn").c_str()); + if (channelsOut < 0) channelsOut = 0; + if (channelsIn < 0) channelsIn = 0; + + buffersize = atoi(getValue("buffersize").c_str()); + if (buffersize < 8) buffersize = DEFAULT_BUFSIZE; + + delayComp = atoi(getValue("delayComp").c_str()); + if (delayComp < 0) delayComp = DEFAULT_DELAYCOMP; + + midiSystem = atoi(getValue("midiSystem").c_str()); + if (midiPortOut < -1) midiPortOut = DEFAULT_MIDI_SYSTEM; + + midiPortOut = atoi(getValue("midiPortOut").c_str()); + if (midiPortOut < -1) midiPortOut = DEFAULT_MIDI_PORT_OUT; + + midiPortIn = atoi(getValue("midiPortIn").c_str()); + if (midiPortIn < -1) midiPortIn = DEFAULT_MIDI_PORT_IN; + + noNoteOff = atoi(getValue("noNoteOff").c_str()); + + midiSync = atoi(getValue("midiSync").c_str()); + midiTCfps = atof(getValue("midiTCfps").c_str()); + + midiInRewind = strtoul(getValue("midiInRewind").c_str(), NULL, 10); + midiInStartStop = strtoul(getValue("midiInStartStop").c_str(), NULL, 10); + midiInActionRec = strtoul(getValue("midiInActionRec").c_str(), NULL, 10); + midiInInputRec = strtoul(getValue("midiInInputRec").c_str(), NULL, 10); + midiInMetronome = strtoul(getValue("midiInMetronome").c_str(), NULL, 10); + midiInVolumeIn = strtoul(getValue("midiInVolumeIn").c_str(), NULL, 10); + midiInVolumeOut = strtoul(getValue("midiInVolumeOut").c_str(), NULL, 10); + midiInBeatDouble = strtoul(getValue("midiInBeatDouble").c_str(), NULL, 10); + midiInBeatHalf = strtoul(getValue("midiInBeatHalf").c_str(), NULL, 10); + + mainWindowX = atoi(getValue("mainWindowX").c_str()); + mainWindowY = atoi(getValue("mainWindowY").c_str()); + mainWindowW = atoi(getValue("mainWindowW").c_str()); + mainWindowH = atoi(getValue("mainWindowH").c_str()); + + browserX = atoi(getValue("browserX").c_str()); + browserY = atoi(getValue("browserY").c_str()); + browserW = atoi(getValue("browserW").c_str()); + browserH = atoi(getValue("browserH").c_str()); + if (browserX < 0) browserX = 0; + if (browserY < 0) browserY = 0; + if (browserW < 396) browserW = 396; + if (browserH < 302) browserH = 302; + + actionEditorX = atoi(getValue("actionEditorX").c_str()); + actionEditorY = atoi(getValue("actionEditorY").c_str()); + actionEditorW = atoi(getValue("actionEditorW").c_str()); + actionEditorH = atoi(getValue("actionEditorH").c_str()); + actionEditorZoom = atoi(getValue("actionEditorZoom").c_str()); + actionEditorGridVal = atoi(getValue("actionEditorGridVal").c_str()); + actionEditorGridOn = atoi(getValue("actionEditorGridOn").c_str()); + 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 = 0; + if (actionEditorGridOn < 0) actionEditorGridOn = 0; + + pianoRollY = atoi(getValue("pianoRollY").c_str()); + pianoRollH = atoi(getValue("pianoRollH").c_str()); + if (pianoRollH <= 0) + pianoRollH = 422; + + sampleEditorX = atoi(getValue("sampleEditorX").c_str()); + sampleEditorY = atoi(getValue("sampleEditorY").c_str()); + sampleEditorW = atoi(getValue("sampleEditorW").c_str()); + sampleEditorH = atoi(getValue("sampleEditorH").c_str()); + sampleEditorGridVal = atoi(getValue("sampleEditorGridVal").c_str()); + sampleEditorGridOn = atoi(getValue("sampleEditorGridOn").c_str()); + if (sampleEditorX < 0) sampleEditorX = 0; + if (sampleEditorY < 0) sampleEditorY = 0; + if (sampleEditorW < 500) sampleEditorW = 500; + if (sampleEditorH < 292) sampleEditorH = 292; + if (sampleEditorGridVal < 0) sampleEditorGridVal = 0; + if (sampleEditorGridOn < 0) sampleEditorGridOn = 0; + + configX = atoi(getValue("configX").c_str()); + configY = atoi(getValue("configY").c_str()); + if (configX < 0) configX = 0; + if (configY < 0) configY = 0; + + pluginListX = atoi(getValue("pluginListX").c_str()); + pluginListY = atoi(getValue("pluginListY").c_str()); + if (pluginListX < 0) pluginListX = 0; + if (pluginListY < 0) pluginListY = 0; + + bpmX = atoi(getValue("bpmX").c_str()); + bpmY = atoi(getValue("bpmY").c_str()); + if (bpmX < 0) bpmX = 0; + if (bpmY < 0) bpmY = 0; + + beatsX = atoi(getValue("beatsX").c_str()); + beatsY = atoi(getValue("beatsY").c_str()); + if (beatsX < 0) beatsX = 0; + if (beatsY < 0) beatsY = 0; + + aboutX = atoi(getValue("aboutX").c_str()); + aboutY = atoi(getValue("aboutY").c_str()); + if (aboutX < 0) aboutX = 0; + if (aboutY < 0) aboutY = 0; + + samplerate = atoi(getValue("samplerate").c_str()); + if (samplerate < 8000) samplerate = DEFAULT_SAMPLERATE; + + limitOutput = atoi(getValue("limitOutput").c_str()); + rsmpQuality = atoi(getValue("rsmpQuality").c_str()); + + std::string p = getValue("pluginPath"); + strncpy(pluginPath, p.c_str(), p.size()); + pluginPath[p.size()] = '\0'; // strncpy doesn't add '\0' + + p = getValue("patchPath"); + strncpy(patchPath, p.c_str(), p.size()); + patchPath[p.size()] = '\0'; // strncpy doesn't add '\0' + + p = getValue("samplePath"); + strncpy(samplePath, p.c_str(), p.size()); + samplePath[p.size()] = '\0'; // strncpy doesn't add '\0' + + recsStopOnChanHalt = atoi(getValue("recsStopOnChanHalt").c_str()); + chansStopOnSeqHalt = atoi(getValue("chansStopOnSeqHalt").c_str()); + treatRecsAsLoops = atoi(getValue("treatRecsAsLoops").c_str()); + + resizeRecordings = atoi(getValue("resizeRecordings").c_str()); + + close(); + return 1; +} + + +/* ------------------------------------------------------------------ */ + + +int Conf::write() +{ + if (!openFileForWriting()) + return 0; + + fprintf(fp, "# --- Giada configuration file --- \n"); + fprintf(fp, "header=GIADACFG\n"); + fprintf(fp, "version=%s\n", VERSIONE); + + fprintf(fp, "logMode=%d\n", logMode); + + fprintf(fp, "soundSystem=%d\n", soundSystem); + fprintf(fp, "soundDeviceOut=%d\n", soundDeviceOut); + fprintf(fp, "soundDeviceIn=%d\n", soundDeviceIn); + fprintf(fp, "channelsOut=%d\n", channelsOut); + fprintf(fp, "channelsIn=%d\n", channelsIn); + fprintf(fp, "buffersize=%d\n", buffersize); + fprintf(fp, "delayComp=%d\n", delayComp); + fprintf(fp, "samplerate=%d\n", samplerate); + fprintf(fp, "limitOutput=%d\n", limitOutput); + fprintf(fp, "rsmpQuality=%d\n", rsmpQuality); + + fprintf(fp, "midiSystem=%d\n", midiSystem); + fprintf(fp, "midiPortOut=%d\n", midiPortOut); + fprintf(fp, "midiPortIn=%d\n", midiPortIn); + fprintf(fp, "noNoteOff=%d\n", noNoteOff); + fprintf(fp, "midiSync=%d\n", midiSync); + fprintf(fp, "midiTCfps=%f\n", midiTCfps); + + fprintf(fp, "midiInRewind=%u\n", midiInRewind); + fprintf(fp, "midiInStartStop=%u\n", midiInStartStop); + fprintf(fp, "midiInActionRec=%u\n", midiInActionRec); + fprintf(fp, "midiInInputRec=%u\n", midiInInputRec); + fprintf(fp, "midiInMetronome=%u\n", midiInMetronome); + fprintf(fp, "midiInVolumeIn=%u\n", midiInVolumeIn); + fprintf(fp, "midiInVolumeOut=%u\n", midiInVolumeOut); + fprintf(fp, "midiInBeatDouble=%u\n", midiInBeatDouble); + fprintf(fp, "midiInBeatHalf=%u\n", midiInBeatHalf); + + fprintf(fp, "pluginPath=%s\n", pluginPath); + fprintf(fp, "patchPath=%s\n", patchPath); + fprintf(fp, "samplePath=%s\n", samplePath); + + fprintf(fp, "mainWindowX=%d\n", mainWindowX); + fprintf(fp, "mainWindowY=%d\n", mainWindowY); + fprintf(fp, "mainWindowW=%d\n", mainWindowW); + fprintf(fp, "mainWindowH=%d\n", mainWindowH); + + fprintf(fp, "browserX=%d\n", browserX); + fprintf(fp, "browserY=%d\n", browserY); + fprintf(fp, "browserW=%d\n", browserW); + fprintf(fp, "browserH=%d\n", browserH); + + fprintf(fp, "actionEditorX=%d\n", actionEditorX); + fprintf(fp, "actionEditorY=%d\n", actionEditorY); + fprintf(fp, "actionEditorW=%d\n", actionEditorW); + fprintf(fp, "actionEditorH=%d\n", actionEditorH); + fprintf(fp, "actionEditorZoom=%d\n", actionEditorZoom); + fprintf(fp, "actionEditorGridOn=%d\n", actionEditorGridOn); + fprintf(fp, "actionEditorGridVal=%d\n", actionEditorGridVal); + + fprintf(fp, "pianoRollY=%d\n", pianoRollY); + fprintf(fp, "pianoRollH=%d\n", pianoRollH); + + fprintf(fp, "sampleEditorX=%d\n", sampleEditorX); + fprintf(fp, "sampleEditorY=%d\n", sampleEditorY); + fprintf(fp, "sampleEditorW=%d\n", sampleEditorW); + fprintf(fp, "sampleEditorH=%d\n", sampleEditorH); + fprintf(fp, "sampleEditorGridOn=%d\n", sampleEditorGridOn); + fprintf(fp, "sampleEditorGridVal=%d\n", sampleEditorGridVal); + + fprintf(fp, "configX=%d\n", configX); + fprintf(fp, "configY=%d\n", configY); + + fprintf(fp, "pluginListX=%d\n", pluginListX); + fprintf(fp, "pluginListY=%d\n", pluginListY); + + fprintf(fp, "bpmX=%d\n", bpmX); + fprintf(fp, "bpmY=%d\n", bpmY); + + fprintf(fp, "beatsX=%d\n", beatsX); + fprintf(fp, "beatsY=%d\n", beatsY); + + fprintf(fp, "aboutX=%d\n", aboutX); + fprintf(fp, "aboutY=%d\n", aboutY); + + fprintf(fp, "recsStopOnChanHalt=%d\n", recsStopOnChanHalt); + fprintf(fp, "chansStopOnSeqHalt=%d\n", chansStopOnSeqHalt); + fprintf(fp, "treatRecsAsLoops=%d\n", treatRecsAsLoops); + + fprintf(fp, "resizeRecordings=%d\n", resizeRecordings); + + close(); + return 1; +} + + + +/* ------------------------------------------------------------------ */ + + +void Conf::close() +{ + if (fp != NULL) + fclose(fp); +} + + +/* ------------------------------------------------------------------ */ + + +void Conf::setPath(char *path, const char *p) +{ + path[0] = '\0'; + strncpy(path, p, strlen(p)); + path[strlen(p)] = '\0'; +} diff --git a/src/conf.h b/src/conf.h new file mode 100644 index 0000000..69ea870 --- /dev/null +++ b/src/conf.h @@ -0,0 +1,122 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * conf + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + + +#ifndef __CONF_H__ +#define __CONF_H__ + + +#include +#include +#include "dataStorage.h" + + +#if defined(__APPLE__) + #include +#endif + + +class Conf : public DataStorage +{ +private: + + int openFileForReading(); + int openFileForWriting(); + int createConfigFolder(const char *path); + +public: + + int logMode; + + int soundSystem; + int soundDeviceOut; + int soundDeviceIn; + int channelsOut; + int channelsIn; + int samplerate; + int buffersize; + int delayComp; + bool limitOutput; + int rsmpQuality; + + int midiSystem; + int midiPortOut; + int midiPortIn; + bool noNoteOff; + int midiSync; // see const.h + float midiTCfps; + + uint32_t midiInRewind; + uint32_t midiInStartStop; + uint32_t midiInActionRec; + uint32_t midiInInputRec; + uint32_t midiInMetronome; + uint32_t midiInVolumeIn; + uint32_t midiInVolumeOut; + uint32_t midiInBeatDouble; + uint32_t midiInBeatHalf; + + bool recsStopOnChanHalt; + bool chansStopOnSeqHalt; + bool treatRecsAsLoops; + + bool resizeRecordings; + + char pluginPath[FILENAME_MAX]; + char patchPath [FILENAME_MAX]; + char samplePath[FILENAME_MAX]; + + int mainWindowX, mainWindowY, mainWindowW, mainWindowH; + int browserX, browserY, browserW, browserH; + int actionEditorX, actionEditorY, actionEditorW, actionEditorH, actionEditorZoom; + int actionEditorGridVal; + int actionEditorGridOn; + int sampleEditorX, sampleEditorY, sampleEditorW, sampleEditorH; + int sampleEditorGridVal; + int sampleEditorGridOn; + int pianoRollY, pianoRollH; + int pluginListX, pluginListY; + int configX, configY; + int bpmX, bpmY; + int beatsX, beatsY; + int aboutX, aboutY; + + int read(); + int write(); + void setDefault(); + + /* setPath + * updates one of the following values: plugin, patch or sample. + * Pass it a pointer to one of these (path) and the string to save (p). */ + + void setPath(char *path, const char *p); + + void close(); +}; + +#endif diff --git a/src/const.h b/src/const.h new file mode 100644 index 0000000..a0b5152 --- /dev/null +++ b/src/const.h @@ -0,0 +1,291 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * const.h + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + +#ifndef CONST_H +#define CONST_H + + +/* -- version --------------------------------------------------------------- */ +#define VERSIONE "0.9.6" +#define VERSIONE_STR "Giada" +#define VERSIONE_FLOAT 0.96f + +#define CONF_FILENAME "giada.conf" + + + +/* -- GUI ------------------------------------------------------------------- */ +#ifdef _WIN32 + #define GUI_SLEEP 1000/24 +#else + #define GUI_SLEEP 1000000/24 // == 1.000.000 / 24 == 1/24 sec == 24 Hz +#endif +#define GUI_WIDTH 810 +#define GUI_HEIGHT 510 + +#define COLOR_BD_0 fl_rgb_color(78, 78, 78) // border off +#define COLOR_BD_1 fl_rgb_color(188, 188, 188) // border on +#define COLOR_BG_0 fl_rgb_color(37, 37, 37) // bg off +#define COLOR_BG_1 fl_rgb_color(78, 78, 78) // bg on (clicked) +#define COLOR_BG_2 fl_rgb_color(177, 142, 142) // bg active (play, for some widgets) +#define COLOR_BG_3 fl_rgb_color(28, 32, 80) // bg input rec +#define COLOR_BG_4 fl_rgb_color(113, 31, 31) // bg action rec +#define COLOR_ALERT fl_rgb_color(239, 75, 53) // peak meter alert +#define COLOR_TEXT_0 fl_rgb_color(200, 200, 200) +#define COLOR_TEXT_1 fl_rgb_color(25, 25, 25) // TODO - duplicate! +#define COLOR_BG_MAIN fl_rgb_color(25, 25, 25) // windows background +#define COLOR_BG_DARK fl_rgb_color(0, 0, 0) // inputs background + + + +/* -- MIN/MAX values -------------------------------------------------------- */ +#define MAX_BEATS 32 +#define MAX_BARS 32 +#define MAX_PATCHNAME_LEN 32 +#define DB_MIN_SCALE 60.0f +#define MAX_VST_EVENTS 32 + + + +/* -- kernel audio ---------------------------------------------------------- */ +#define SYS_API_JACK 0x01 // 0000 0001 +#define SYS_API_ALSA 0x02 // 0000 0010 +#define SYS_API_DS 0x04 // 0000 0100 +#define SYS_API_ASIO 0x08 // 0000 1000 +#define SYS_API_CORE 0x10 // 0001 0000 +#define SYS_API_PULSE 0x20 // 0010 0000 +#define SYS_API_ANY 0x3F // 0011 1111 + +#define KERNEL_OK 0 +#define KERNEL_UNDERRUN -1 +#define KERNEL_CRITICAL -2 + + + +/* -- kernel midi ----------------------------------------------------------- */ +#define MIDI_API_JACK 0x01 // 0000 0001 +#define MIDI_API_ALSA 0x02 // 0000 0010 + + + +/* -- default system -------------------------------------------------------- */ +#if defined(__linux__) + #define DEFAULT_SOUNDSYS SYS_API_ALSA +#elif defined(_WIN32) + #define DEFAULT_SOUNDSYS SYS_API_DS +#elif defined(__APPLE__) + #define DEFAULT_SOUNDSYS SYS_API_CORE +#endif + +#define DEFAULT_SOUNDDEV_OUT 0 /// FIXME - please override with rtAudio::getDefaultDevice (or similar) +#define DEFAULT_SOUNDDEV_IN -1 // no recording by default: input disabled +#define DEFAULT_MIDI_SYSTEM 0 +#define DEFAULT_MIDI_PORT_IN -1 +#define DEFAULT_MIDI_PORT_OUT -1 +#define DEFAULT_SAMPLERATE 44100 +#define DEFAULT_BUFSIZE 1024 +#define DEFAULT_DELAYCOMP 0 +#define DEFAULT_VOL 0.0f +#define DEFAULT_BOOST 0.0f +#define gDEFAULT_PITCH 1.0f // ugly and temporary fix to avoid conflicts with wingdi.h (Windows only). +#define DEFAULT_OUT_VOL 1.0f +#define DEFAULT_IN_VOL 0.0f +#define DEFAULT_CHANMODE SINGLE_BASIC +#define DEFAULT_BPM 120.0f +#define DEFAULT_BEATS 4 +#define DEFAULT_BARS 1 +#define DEFAULT_QUANTIZE 0 // quantizer off +#define DEFAULT_FADEOUT_STEP 0.01f // micro-fadeout speed + + + +/* -- mixer statuses and modes ---------------------------------------------- */ +#define LOOP_BASIC 0x01 // 0000 0001 chanMode +#define LOOP_ONCE 0x02 // 0000 0010 chanMode +#define SINGLE_BASIC 0x04 // 0000 0100 chanMode +#define SINGLE_PRESS 0x08 // 0000 1000 chanMode +#define SINGLE_RETRIG 0x10 // 0001 0000 chanMode +#define LOOP_REPEAT 0x20 // 0010 0000 chanMode +#define SINGLE_ENDLESS 0x40 // 0100 0000 chanMode +#define LOOP_ONCE_BAR 0x80 // 1000 0000 chanMode + +#define LOOP_ANY 0xA3 // 1010 0011 chanMode - any loop mode +#define SINGLE_ANY 0x5C // 0101 1100 chanMode - any single mode + +#define STATUS_ENDING 0x01 // 0000 0001 chanStatus - ending (loop mode only) +#define STATUS_WAIT 0x02 // 0000 0010 chanStatus - waiting for start (loop mode only) +#define STATUS_PLAY 0x04 // 0000 0100 chanStatus - playing +#define STATUS_OFF 0x08 // 0000 1000 chanStatus - off +#define STATUS_EMPTY 0x10 // 0001 0000 chanStatus - not loaded (empty chan) +#define STATUS_MISSING 0x20 // 0010 0000 chanStatus - not found +#define STATUS_WRONG 0x40 // 0100 0000 chanStatus - something wrong (freq, bitrate, ...) + +#define REC_WAITING 0x01 // 0000 0001 +#define REC_ENDING 0x02 // 0000 0010 +#define REC_READING 0x04 // 0000 0100 +#define REC_STOPPED 0x08 // 0000 1000 + + + +/* -- actions --------------------------------------------------------------- */ +#define ACTION_KEYPRESS 0x01 // 0000 0001 +#define ACTION_KEYREL 0x02 // 0000 0010 +#define ACTION_KILLCHAN 0x04 // 0000 0100 +#define ACTION_MUTEON 0x08 // 0000 1000 +#define ACTION_MUTEOFF 0x10 // 0001 0000 +#define ACTION_VOLUME 0x20 // 0010 0000 +#define ACTION_MIDI 0x40 // 0100 0000 + +#define ACTION_KEYS 0x03 // 0000 0011 any key +#define ACTION_MUTES 0x24 // 0001 1000 any mute + +#define RANGE_CHAR 0x01 // range for MIDI (0-127) +#define RANGE_FLOAT 0x02 // range for volumes and VST params (0.0-1.0) + + + +/* -- mixerHandler signals -------------------------------------------------- */ +#define SAMPLE_LOADED_OK 0x01 +#define SAMPLE_LEFT_EMPTY 0x02 +#define SAMPLE_NOT_VALID 0x04 +#define SAMPLE_MULTICHANNEL 0x08 +#define SAMPLE_WRONG_BIT 0x10 +#define SAMPLE_WRONG_ENDIAN 0x20 +#define SAMPLE_WRONG_FORMAT 0x40 +#define SAMPLE_READ_ERROR 0x80 +#define SAMPLE_PATH_TOO_LONG 0x100 + +/** FIXME - add to SAMPLE_ series those for when exporting */ + + + +/* -- log modes ------------------------------------------------------------- */ +#define LOG_MODE_STDOUT 0x01 +#define LOG_MODE_FILE 0x02 +#define LOG_MODE_MUTE 0x04 + + + +/* -- browser types --------------------------------------------------------- */ +#define BROWSER_LOAD_PATCH 0x00 +#define BROWSER_LOAD_SAMPLE 0x01 +#define BROWSER_SAVE_PATCH 0x02 +#define BROWSER_SAVE_SAMPLE 0x04 +#define BROWSER_SAVE_PROJECT 0x08 +#define BROWSER_LOAD_PLUGIN 0x10 + + + +/* -- channel types --------------------------------------------------------- */ +#define CHANNEL_SAMPLE 0x01 +#define CHANNEL_MIDI 0x02 + + + +/* -- unique IDs of mainWin's subwindows ------------------------------------ */ +/* -- wid > 0 are reserved by gg_keyboard ----------------------------------- */ +#define WID_BEATS -1 +#define WID_BPM -2 +#define WID_ABOUT -3 +#define WID_FILE_BROWSER -4 +#define WID_CONFIG -5 +#define WID_FX_LIST -6 +#define WID_ACTION_EDITOR -7 +#define WID_SAMPLE_EDITOR -8 +#define WID_FX -9 +#define WID_KEY_GRABBER -10 + + +/* -- patch signals --------------------------------------------------------- */ +#define PATCH_UNREADABLE 0 +#define PATCH_INVALID -1 +#define PATCH_OPEN_OK 1 + +/** TODO - addo to PATCH_ serie the signals for saving/loading */ + + + +/* -- 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_ALL_NOTES_OFF (MIDI_CONTROLLER) | (0x7B << 16) +#define MIDI_VOLUME (MIDI_CONTROLLER) | (0x07 << 16) + +/* system common / real-time messages. Single bytes */ + +#define MIDI_SYSEX 0xF0 +#define MIDI_MTC_QUARTER 0xF1 +#define MIDI_POSITION_PTR 0xF2 +#define MIDI_CLOCK 0xF8 +#define MIDI_START 0xFA +#define MIDI_CONTINUE 0xFB +#define MIDI_STOP 0xFC +#define MIDI_EOX 0xF7 // end of sysex + +/* channels */ + +#define MIDI_CHAN_0 0x00 << 24 +#define MIDI_CHAN_1 0x01 << 24 +#define MIDI_CHAN_2 0x02 << 24 +#define MIDI_CHAN_3 0x03 << 24 +#define MIDI_CHAN_4 0x04 << 24 +#define MIDI_CHAN_5 0x05 << 24 +#define MIDI_CHAN_6 0x06 << 24 +#define MIDI_CHAN_7 0x07 << 24 +#define MIDI_CHAN_8 0x08 << 24 +#define MIDI_CHAN_9 0x09 << 24 +#define MIDI_CHAN_10 0x0A << 24 +#define MIDI_CHAN_11 0x0B << 24 +#define MIDI_CHAN_12 0x0C << 24 +#define MIDI_CHAN_13 0x0D << 24 +#define MIDI_CHAN_14 0x0E << 24 +#define MIDI_CHAN_15 0x0F << 24 + +const int MIDI_CHANS[16] = { + MIDI_CHAN_0, MIDI_CHAN_1, MIDI_CHAN_2, MIDI_CHAN_3, + MIDI_CHAN_4, MIDI_CHAN_5, MIDI_CHAN_6, MIDI_CHAN_7, + MIDI_CHAN_8, MIDI_CHAN_9, MIDI_CHAN_10, MIDI_CHAN_11, + MIDI_CHAN_12, MIDI_CHAN_13, MIDI_CHAN_14, MIDI_CHAN_15 +}; + +/* midi sync constants */ + +#define MIDI_SYNC_NONE 0x00 +#define MIDI_SYNC_CLOCK_M 0x01 +#define MIDI_SYNC_CLOCK_S 0x02 +#define MIDI_SYNC_MTC_M 0x04 +#define MIDI_SYNC_MTC_S 0x08 + +#endif diff --git a/src/dataStorage.cpp b/src/dataStorage.cpp new file mode 100644 index 0000000..4704afc --- /dev/null +++ b/src/dataStorage.cpp @@ -0,0 +1,72 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * dataStorage + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + + +#include "dataStorage.h" +#include "log.h" + + +std::string DataStorage::getValue(const char *in) { + + /* on each call reset the pointe to the beginning of the file. Not so + * good but necessary if you want to pick up random values from the + * file. */ + + fseek(fp, 0L, SEEK_SET); + std::string out = ""; + + while (!feof(fp)) { + + char buffer[MAX_LINE_LEN]; + if (fgets(buffer, MAX_LINE_LEN, fp) == NULL) { + gLog("[PATCH] get_value error (key=%s)\n", in); + return ""; + } + + if (buffer[0] == '#') + continue; + + unsigned len = strlen(in); + if (strncmp(buffer, in, len) == 0) { + + for (unsigned i=len+1; i. + * + * ------------------------------------------------------------------ */ + + +#ifndef __DATA_STORAGE_H__ +#define __DATA_STORAGE_H__ + +#include +#include +#include + +#define MAX_LINE_LEN 1024 + + +class DataStorage { + +protected: + + FILE *fp; + std::string getValue(const char *in); + +}; + +#endif diff --git a/src/gd_about.cpp b/src/gd_about.cpp new file mode 100644 index 0000000..8caa7a0 --- /dev/null +++ b/src/gd_about.cpp @@ -0,0 +1,126 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * gd_about + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + + +#include "gd_about.h" +#include "conf.h" +#include "const.h" +#include "kernelAudio.h" +#include "kernelMidi.h" +#include "ge_mixed.h" +#include "graphics.h" +#include "gui_utils.h" + + +extern Conf G_Conf; + + +gdAbout::gdAbout() +#ifdef WITH_VST +: gWindow(340, 405, "About Giada") { +#else +: gWindow(340, 320, "About Giada") { +#endif + + if (G_Conf.aboutX) + resize(G_Conf.aboutX, G_Conf.aboutY, w(), h()); + + set_modal(); + + logo = new gBox(8, 10, 324, 86); + text = new gBox(8, 120, 324, 145); + close = new gClick(252, h()-28, 80, 20, "Close"); +#ifdef WITH_VST + vstLogo = new gBox(8, 265, 324, 50); + vstText = new gBox(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); + + char message[512]; + sprintf( + message, + "Version " VERSIONE " (" __DATE__ ")\n\n" + "Developed by Monocasual\n" + "Based on FLTK (%d.%d.%d), RtAudio (%s),\n" + "RtMidi (%s), libsamplerate and libsndfile\n\n" + "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, + kernelAudio::getRtAudioVersion().c_str(), + kernelMidi::getRtMidiVersion().c_str()); + + int tw = 0; + int th = 0; + fl_measure(message, tw, th); + text->copy_label(message); + 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() { + G_Conf.aboutX = x(); + G_Conf.aboutY = y(); +} + + +/* ------------------------------------------------------------------ */ + + +void gdAbout::cb_close(Fl_Widget *w, void *p) { ((gdAbout*)p)->__cb_close(); } + + +/* ------------------------------------------------------------------ */ + + +void gdAbout::__cb_close() { + do_callback(); +} diff --git a/src/gd_about.h b/src/gd_about.h new file mode 100644 index 0000000..695a91e --- /dev/null +++ b/src/gd_about.h @@ -0,0 +1,58 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * gd_about + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + + +#ifndef GD_ABOUT_H +#define GD_ABOUT_H + +#include +#include +#include "ge_window.h" + + +class gdAbout : public gWindow { +private: + class gBox *logo; + class gBox *text; + class gClick *close; + +#ifdef WITH_VST + class gBox *vstText; + class gBox *vstLogo; +#endif + +public: + gdAbout(); + ~gdAbout(); + + static void cb_close(Fl_Widget *w, void *p); + inline void __cb_close(); + +}; + +#endif diff --git a/src/gd_actionEditor.cpp b/src/gd_actionEditor.cpp new file mode 100644 index 0000000..9737e9a --- /dev/null +++ b/src/gd_actionEditor.cpp @@ -0,0 +1,472 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * gd_actionEditor + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + + +#include +#include "gd_actionEditor.h" +#include "ge_actionChannel.h" +#include "ge_muteChannel.h" +#include "ge_envelopeChannel.h" +#include "ge_pianoRoll.h" +#include "gui_utils.h" +#include "graphics.h" +#include "mixer.h" +#include "recorder.h" +#include "conf.h" +#include "ge_mixed.h" +#include "channel.h" +#include "sampleChannel.h" + + +extern Mixer G_Mixer; +extern Conf G_Conf; + + +gdActionEditor::gdActionEditor(Channel *chan) + : gWindow(640, 284), + chan (chan), + zoom (100), + coverX (0) +{ + if (G_Conf.actionEditorW) { + resize(G_Conf.actionEditorX, G_Conf.actionEditorY, G_Conf.actionEditorW, G_Conf.actionEditorH); + zoom = G_Conf.actionEditorZoom; + } + + totalWidth = (int) ceilf(G_Mixer.framesInSequencer / (float) zoom); + + /* container with zoom buttons and the action type selector. Scheme of + * the resizable boxes: |[--b1--][actionType][--b2--][+][-]| */ + + Fl_Group *upperArea = new Fl_Group(8, 8, w()-16, 20); + + upperArea->begin(); + + if (chan->type == CHANNEL_SAMPLE) { + actionType = new gChoice(8, 8, 80, 20); + gridTool = new gGridTool(actionType->x()+actionType->w()+4, 8, this); + actionType->add("key press"); + actionType->add("key release"); + actionType->add("kill chan"); + actionType->value(0); + + SampleChannel *ch = (SampleChannel*) chan; + if (ch->mode == SINGLE_PRESS || ch->mode & LOOP_ANY) + actionType->deactivate(); + } + else { + gridTool = new gGridTool(8, 8, this); + } + + gBox *b1 = new gBox(gridTool->x()+gridTool->w()+4, 8, 300, 20); // padding actionType - zoomButtons + zoomIn = new gClick(w()-8-40-4, 8, 20, 20, "", zoomInOff_xpm, zoomInOn_xpm); + zoomOut = new gClick(w()-8-20, 8, 20, 20, "", zoomOutOff_xpm, zoomOutOn_xpm); + upperArea->end(); + upperArea->resizable(b1); + + zoomIn->callback(cb_zoomIn, (void*)this); + zoomOut->callback(cb_zoomOut, (void*)this); + + /* main scroller: contains all widgets */ + + scroller = new gScroll(8, 36, w()-16, h()-44); + + if (chan->type == CHANNEL_SAMPLE) { + + SampleChannel *ch = (SampleChannel*) chan; + + ac = new gActionChannel (scroller->x(), upperArea->y()+upperArea->h()+8, this, ch); + mc = new gMuteChannel (scroller->x(), ac->y()+ac->h()+8, this); + vc = new gEnvelopeChannel (scroller->x(), mc->y()+mc->h()+8, this, ACTION_VOLUME, RANGE_FLOAT, "volume"); + scroller->add(ac); + //scroller->add(new gResizerBar(ac->x(), ac->y()+ac->h(), scroller->w(), 8)); + scroller->add(mc); + //scroller->add(new gResizerBar(mc->x(), mc->y()+mc->h(), scroller->w(), 8)); + scroller->add(vc); + //scroller->add(new gResizerBar(vc->x(), vc->y()+vc->h(), scroller->w(), 8)); + + /* fill volume envelope with actions from recorder */ + + vc->fill(); + + /* if channel is LOOP_ANY, deactivate it: a loop mode channel cannot + * hold keypress/keyrelease actions */ + + if (ch->mode & LOOP_ANY) + ac->deactivate(); + } + else { + pr = new gPianoRollContainer(scroller->x(), upperArea->y()+upperArea->h()+8, this); + scroller->add(pr); + scroller->add(new gResizerBar(pr->x(), pr->y()+pr->h(), scroller->w(), 8)); + } + + end(); + + /* compute values */ + + update(); + gridTool->calc(); + + gu_setFavicon(this); + + char buf[256]; + sprintf(buf, "Edit Actions in Channel %d", chan->index+1); + label(buf); + + set_non_modal(); + size_range(640, 284); + resizable(scroller); + + show(); +} + + +/* ------------------------------------------------------------------ */ + + +gdActionEditor::~gdActionEditor() { + G_Conf.actionEditorX = x(); + G_Conf.actionEditorY = y(); + G_Conf.actionEditorW = w(); + G_Conf.actionEditorH = h(); + G_Conf.actionEditorZoom = zoom; + + /** CHECKME - missing clear() ? */ + +} + + +/* ------------------------------------------------------------------ */ + + +void gdActionEditor::cb_zoomIn(Fl_Widget *w, void *p) { ((gdActionEditor*)p)->__cb_zoomIn(); } +void gdActionEditor::cb_zoomOut(Fl_Widget *w, void *p) { ((gdActionEditor*)p)->__cb_zoomOut(); } + + +/* ------------------------------------------------------------------ */ + + +void gdActionEditor::__cb_zoomIn() { + + /* zoom 50: empirical value, to avoid a totalWidth > 16 bit signed + * (32767 max), unsupported by FLTK 1.3.x */ + + if (zoom <= 50) + return; + + zoom /= 2; + + update(); + + if (chan->type == CHANNEL_SAMPLE) { + ac->size(totalWidth, ac->h()); + mc->size(totalWidth, mc->h()); + vc->size(totalWidth, vc->h()); + ac->updateActions(); + mc->updateActions(); + vc->updateActions(); + } + else { + pr->size(totalWidth, pr->h()); + pr->updateActions(); + } + + /* scroll to pointer */ + + int shift = Fl::event_x() + scroller->xposition(); + scroller->scroll_to(scroller->xposition() + shift, scroller->yposition()); + + /* update all underlying widgets */ + + gridTool->calc(); + scroller->redraw(); +} + + +/* ------------------------------------------------------------------ */ + + +void gdActionEditor::__cb_zoomOut() { + + zoom *= 2; + + update(); + + if (chan->type == CHANNEL_SAMPLE) { + ac->size(totalWidth, ac->h()); + mc->size(totalWidth, mc->h()); + vc->size(totalWidth, vc->h()); + ac->updateActions(); + mc->updateActions(); + vc->updateActions(); + } + else { + pr->size(totalWidth, pr->h()); + pr->updateActions(); + } + + /* scroll to pointer */ + + int shift = (Fl::event_x() + scroller->xposition()) / -2; + if (scroller->xposition() + shift < 0) + shift = 0; + scroller->scroll_to(scroller->xposition() + shift, scroller->yposition()); + + /* update all underlying widgets */ + + gridTool->calc(); + scroller->redraw(); +} + + +/* ------------------------------------------------------------------ */ + + +void gdActionEditor::update() { + totalWidth = (int) ceilf(G_Mixer.framesInSequencer / (float) zoom); + if (totalWidth < scroller->w()) { + totalWidth = scroller->w(); + zoom = (int) ceilf(G_Mixer.framesInSequencer / (float) totalWidth); + scroller->scroll_to(0, scroller->yposition()); + } +} + + +/* ------------------------------------------------------------------ */ + + +int gdActionEditor::handle(int e) { + int ret = Fl_Group::handle(e); + switch (e) { + case FL_MOUSEWHEEL: { + Fl::event_dy() == -1 ? __cb_zoomIn() : __cb_zoomOut(); + ret = 1; + break; + } + } + return ret; +} + + +/* ------------------------------------------------------------------ */ + + +int gdActionEditor::getActionType() { + if (actionType->value() == 0) + return ACTION_KEYPRESS; + else + if (actionType->value() == 1) + return ACTION_KEYREL; + else + if (actionType->value() == 2) + return ACTION_KILLCHAN; + else + return -1; +} + + +/* ------------------------------------------------------------------ */ +/* ------------------------------------------------------------------ */ +/* ------------------------------------------------------------------ */ + + +gGridTool::gGridTool(int x, int y, gdActionEditor *parent) + : Fl_Group(x, y, 80, 20), parent(parent) +{ + gridType = new gChoice(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 gCheck (x+44, y+4, 12, 12); + + gridType->value(G_Conf.actionEditorGridVal); + active->value(G_Conf.actionEditorGridOn); + + end(); +} + + +/* ------------------------------------------------------------------ */ + + +gGridTool::~gGridTool() { + G_Conf.actionEditorGridVal = gridType->value(); + G_Conf.actionEditorGridOn = active->value(); +} + + +/* ------------------------------------------------------------------ */ + + +void gGridTool::cb_changeType(Fl_Widget *w, void *p) { ((gGridTool*)p)->__cb_changeType(); } + + +/* ------------------------------------------------------------------ */ + + +void gGridTool::__cb_changeType() { + calc(); + parent->redraw(); +} + + +/* ------------------------------------------------------------------ */ + + +bool gGridTool::isOn() { + return active->value(); +} + + +/* ------------------------------------------------------------------ */ + + +int gGridTool::getValue() { + switch (gridType->value()) { + case 0: return 1; + case 1: return 2; + case 2: return 3; + case 3: return 4; + case 4: return 6; + case 5: return 8; + case 6: return 16; + case 7: return 32; + } + return 0; +} + + +/* ------------------------------------------------------------------ */ + + +void gGridTool::calc() { + + points.clear(); + frames.clear(); + bars.clear(); + beats.clear(); + + /* find beats, bars and grid. The method is the same of the waveform in sample + * editor. Take totalwidth (the width in pixel of the area to draw), knowing + * that totalWidth = totalFrames / zoom. Then, for each pixel of totalwidth, + * put a concentrate of each block (which is totalFrames / zoom) */ + + int j = 0; + int fpgc = floor(G_Mixer.framesPerBeat / getValue()); // frames per grid cell + + for (int i=1; itotalWidth; i++) { // if i=0, step=0 -> useless cycle + int step = parent->zoom*i; + while (j < step && j < G_Mixer.totalFrames) { + if (j % fpgc == 0) { + points.add(i); + frames.add(j); + } + if (j % G_Mixer.framesPerBeat == 0) + beats.add(i); + if (j % G_Mixer.framesPerBar == 0 && i != 1) + bars.add(i); + if (j == G_Mixer.totalFrames-1) + parent->coverX = i; + j++; + } + j = step; + } + + /* fix coverX if == 0, which means G_Mixer.beats == 32 */ + + if (G_Mixer.beats == 32) + parent->coverX = parent->totalWidth; +} + + +/* ------------------------------------------------------------------ */ + + +int gGridTool::getSnapPoint(int v) { + + if (v == 0) return 0; + + for (int i=0; i<(int)points.size; i++) { + + if (i == (int) points.size-1) + return points.at(i); + + int gp = points.at(i); + int gpn = points.at(i+1); + + if (v >= gp && v < gpn) + return gp; + } + return v; // default value +} + + +/* ------------------------------------------------------------------ */ + + +int gGridTool::getSnapFrame(int v) { + + v *= parent->zoom; // transformation pixel -> frame + + for (int i=0; i<(int)frames.size; i++) { + + if (i == (int) frames.size-1) + return frames.at(i); + + int gf = frames.at(i); // grid frame + int gfn = frames.at(i+1); // grid frame next + + if (v >= gf && v < gfn) { + + /* which one is the closest? gf < v < gfn */ + + if ((gfn - v) < (v - gf)) + return gfn; + else + return gf; + } + } + return v; // default value +} + + +/* ------------------------------------------------------------------ */ + + +int gGridTool::getCellSize() { + return (parent->coverX - parent->ac->x()) / G_Mixer.beats / getValue(); +} diff --git a/src/gd_actionEditor.h b/src/gd_actionEditor.h new file mode 100644 index 0000000..71c37fd --- /dev/null +++ b/src/gd_actionEditor.h @@ -0,0 +1,131 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * gd_actionEditor + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + + +#ifndef GD_ACTIONEDITOR_H +#define GD_ACTIONEDITOR_H + +#include +#include +#include +#include "ge_window.h" + + +/* gActionEditor + * main window which contains the tools for dealing with actions. + * This class calculates chan, zoom, frames per beat, and so on. Each + * sub-widget contains a pointer to this window to query those data. */ + +class gdActionEditor : public gWindow { + +private: + + /* update + * compute total width, in pixel. */ + + void update(); + +public: + + gdActionEditor(class Channel *chan); + ~gdActionEditor(); + + int handle(int e); + + int getActionType(); + + static void cb_zoomIn(Fl_Widget *w, void *p); + static void cb_zoomOut(Fl_Widget *w, void *p); + inline void __cb_zoomIn(); + inline void __cb_zoomOut(); + + class gChoice *actionType; + class gGridTool *gridTool; + class gClick *zoomIn; + class gClick *zoomOut; + class gScroll *scroller; // widget container + + class gActionChannel *ac; + class gMuteChannel *mc; + class gEnvelopeChannel *vc; + class gPianoRollContainer *pr; + + gVector widgets; + + class Channel *chan; + + int zoom; + int totalWidth; // total width of the widget, in pixel (zoom affected) + int coverX; // x1 of the unused area (x2 = totalWidth) +}; + + +/* ------------------------------------------------------------------ */ + + +class gGridTool : public Fl_Group { + +private: + class gChoice *gridType; + class gCheck *active; + + class gdActionEditor *parent; + + static void cb_changeType(Fl_Widget *w, void *p); + inline void __cb_changeType(); + +public: + + gGridTool(int x, int y, gdActionEditor *parent); + ~gGridTool(); + + int getValue(); + bool isOn(); + void calc(); + + /* getSnapPoint + * given a cursor position in input, return the x coordinates of the + * nearest snap point (in pixel, clean, ie. not x()-shifted) */ + + int getSnapPoint(int v); + int getSnapFrame(int v); + + /* getCellSize + * return the size in pixel of a single cell of the grid. */ + + int getCellSize(); + + gVector points; // points of the grid + gVector frames; // frames of the grid + + gVector bars; + gVector beats; +}; + + +#endif diff --git a/src/gd_beatsInput.cpp b/src/gd_beatsInput.cpp new file mode 100644 index 0000000..0c381c9 --- /dev/null +++ b/src/gd_beatsInput.cpp @@ -0,0 +1,102 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * gd_beatsInput + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + + +#include "gd_beatsInput.h" +#include "gd_mainWindow.h" +#include "gui_utils.h" +#include "patch.h" +#include "glue.h" +#include "mixer.h" +#include "conf.h" + + +extern Mixer G_Mixer; +extern Conf G_Conf; +extern gdMainWindow *mainWin; + + +gdBeatsInput::gdBeatsInput() + : gWindow(164, 60, "Beats") +{ + if (G_Conf.beatsX) + resize(G_Conf.beatsX, G_Conf.beatsY, w(), h()); + + set_modal(); + + beats = new gInput(8, 8, 35, 20); + bars = new gInput(47, 8, 35, 20); + ok = new gClick(86, 8, 70, 20, "Ok"); + resizeRec = new gCheck(8, 40, 12, 12, "resize recorded actions"); + end(); + + char buf_bars[3]; sprintf(buf_bars, "%d", G_Mixer.bars); + char buf_beats[3]; sprintf(buf_beats, "%d", G_Mixer.beats); + beats->maximum_size(2); + beats->value(buf_beats); + beats->type(FL_INT_INPUT); + bars->maximum_size(2); + bars->value(buf_bars); + bars->type(FL_INT_INPUT); + ok->shortcut(FL_Enter); + ok->callback(cb_update_batt, (void*)this); + resizeRec->value(G_Conf.resizeRecordings); + + gu_setFavicon(this); + setId(WID_BEATS); + show(); +} + + +/* ------------------------------------------------------------------ */ + + +gdBeatsInput::~gdBeatsInput() +{ + G_Conf.beatsX = x(); + G_Conf.beatsY = y(); + G_Conf.resizeRecordings = resizeRec->value(); +} + + +/* ------------------------------------------------------------------ */ + + +void gdBeatsInput::cb_update_batt(Fl_Widget *w, void *p) { ((gdBeatsInput*)p)->__cb_update_batt(); } + + +/* ------------------------------------------------------------------ */ + + +void gdBeatsInput::__cb_update_batt() +{ + if (!strcmp(beats->value(), "") || !strcmp(bars->value(), "")) + return; + glue_setBeats(atoi(beats->value()), atoi(bars->value()), resizeRec->value()); + do_callback(); +} diff --git a/src/gd_beatsInput.h b/src/gd_beatsInput.h new file mode 100644 index 0000000..8a5ccca --- /dev/null +++ b/src/gd_beatsInput.h @@ -0,0 +1,56 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * gd_beatsInput + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + +#ifndef GD_BEATSINPUT_H +#define GD_BEATSINPUT_H + +#include +#include +#include "ge_window.h" + + +class gdBeatsInput : public gWindow +{ +private: + + static void cb_update_batt(Fl_Widget *w, void *p); + inline void __cb_update_batt(); + + class gInput *beats; + class gInput *bars; + class gClick *ok; + class gCheck *resizeRec; + +public: + + gdBeatsInput(); + ~gdBeatsInput(); +}; + + +#endif diff --git a/src/gd_bpmInput.cpp b/src/gd_bpmInput.cpp new file mode 100644 index 0000000..048fe0a --- /dev/null +++ b/src/gd_bpmInput.cpp @@ -0,0 +1,105 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * gd_bpmInput + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + + +#include "gd_bpmInput.h" +#include "gd_mainWindow.h" +#include "conf.h" +#include "ge_mixed.h" +#include "mixer.h" +#include "gui_utils.h" +#include "glue.h" + + +extern Mixer G_Mixer; +extern Conf G_Conf; +extern gdMainWindow *mainWin; + + +gdBpmInput::gdBpmInput(const char *label) +: gWindow(144, 36, "Bpm") { + + if (G_Conf.bpmX) + resize(G_Conf.bpmX, G_Conf.bpmY, w(), h()); + + set_modal(); + + input_a = new gInput(8, 8, 30, 20); + input_b = new gInput(42, 8, 20, 20); + ok = new gClick(66, 8, 70, 20, "Ok"); + end(); + + char a[4]; + snprintf(a, 4, "%d", (int) G_Mixer.bpm); + char b[2]; + for (unsigned i=0; imaximum_size(3); + input_a->type(FL_INT_INPUT); + input_a->value(a); + input_b->maximum_size(1); + input_b->type(FL_INT_INPUT); + input_b->value(b); + + ok->shortcut(FL_Enter); + ok->callback(cb_update_bpm, (void*)this); + + gu_setFavicon(this); + setId(WID_BPM); + show(); +} + + +/* ------------------------------------------------------------------ */ + + +gdBpmInput::~gdBpmInput() { + G_Conf.bpmX = x(); + G_Conf.bpmY = y(); +} + + +/* ------------------------------------------------------------------ */ + + +void gdBpmInput::cb_update_bpm(Fl_Widget *w, void *p) { ((gdBpmInput*)p)->__cb_update_bpm(); } + + +/* ------------------------------------------------------------------ */ + + +void gdBpmInput::__cb_update_bpm() { + if (strcmp(input_a->value(), "") == 0) + return; + glue_setBpm(input_a->value(), input_b->value()); + do_callback(); +} diff --git a/src/gd_bpmInput.h b/src/gd_bpmInput.h new file mode 100644 index 0000000..e3bc211 --- /dev/null +++ b/src/gd_bpmInput.h @@ -0,0 +1,51 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * gd_bpmInput + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + +#ifndef GD_BPMINPUT_H +#define GD_BPMINPUT_H + +#include +#include +#include "ge_window.h" + + +class gdBpmInput : public gWindow { +private: + static void cb_update_bpm(Fl_Widget *w, void *p); + inline void __cb_update_bpm(); + + class gInput *input_a; + class gInput *input_b; + class gClick *ok; + +public: + gdBpmInput(const char *label); // pointer to mainWin->timing->bpm->label() + ~gdBpmInput(); +}; + +#endif diff --git a/src/gd_browser.cpp b/src/gd_browser.cpp new file mode 100644 index 0000000..188a6c0 --- /dev/null +++ b/src/gd_browser.cpp @@ -0,0 +1,395 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * gd_browser + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include "gd_browser.h" +#include "ge_browser.h" +#include "gd_pluginList.h" +#include "gd_mainWindow.h" +#include "gg_keyboard.h" +#include "gd_warnings.h" +#include "ge_channel.h" +#include "mixer.h" +#include "graphics.h" +#include "wave.h" +#include "glue.h" +#include "pluginHost.h" +#include "channel.h" +#include "sampleChannel.h" +#include "patch.h" +#include "conf.h" + + +extern Patch G_Patch; +extern Conf G_Conf; +extern Mixer G_Mixer; +#ifdef WITH_VST +extern PluginHost G_PluginHost; +#endif +extern gdMainWindow *mainWin; + + +gdBrowser::gdBrowser(const char *title, const char *initPath, Channel *ch, int type, int stackType) + : gWindow (396, 302, title), + ch (ch), + type (type), + stackType(stackType) +{ + set_non_modal(); + + browser = new gBrowser(8, 36, 380, 230); + Fl_Group *group_btn = new Fl_Group(8, 274, 380, 20); + gBox *b = new gBox(8, 274, 204, 20); // spacer window border <-> buttons + ok = new gClick(308, 274, 80, 20); + cancel = new gClick(220, 274, 80, 20, "Cancel"); + status = new gProgress(8, 274, 204, 20); + status->minimum(0); + status->maximum(1); + status->hide(); // show the bar only if necessary + group_btn->resizable(b); + group_btn->end(); + + Fl_Group *group_upd = new Fl_Group(8, 8, 380, 25); + if (type == BROWSER_SAVE_PATCH || type == BROWSER_SAVE_SAMPLE || type == BROWSER_SAVE_PROJECT) /// bitmask please! + name = new gInput(208, 8, 152, 20); + if (type == BROWSER_SAVE_PATCH || type == BROWSER_SAVE_SAMPLE || type == BROWSER_SAVE_PROJECT) /// bitmask please! + where = new gInput(8, 8, 192, 20); + else + where = new gInput(8, 8, 352, 20); + updir = new gClick(368, 8, 20, 20, "", updirOff_xpm, updirOn_xpm); + group_upd->resizable(where); + group_upd->end(); + + end(); + + resizable(browser); + size_range(w(), h(), 0, 0); + + where->readonly(true); + where->cursor_color(COLOR_BG_DARK); + + if (type == BROWSER_SAVE_PATCH || type == BROWSER_SAVE_SAMPLE || type == BROWSER_SAVE_PROJECT) /// bitmask please! + ok->label("Save"); + else + ok->label("Load"); + + if (type == BROWSER_LOAD_PATCH) + ok->callback(cb_load_patch, (void*)this); + else + if (type == BROWSER_LOAD_SAMPLE) + ok->callback(cb_load_sample, (void*)this); + else + if (type == BROWSER_SAVE_PATCH) { + ok->callback(cb_save_patch, (void*)this); + name->value(G_Patch.name[0] == '\0' ? "my_patch.gptc" : G_Patch.name); + name->maximum_size(MAX_PATCHNAME_LEN+5); // +5 for ".gptc" + } + else + if (type == BROWSER_SAVE_SAMPLE) { + ok->callback(cb_save_sample, (void*)this); + name->value(((SampleChannel*)ch)->wave->name.c_str()); + } + else + if (type == BROWSER_SAVE_PROJECT) { + ok->callback(cb_save_project, (void*)this); + name->value(gStripExt(G_Patch.name).c_str()); + } +#ifdef WITH_VST + else + if (type == BROWSER_LOAD_PLUGIN) { + ok->callback(cb_loadPlugin, (void*)this); + } +#endif + + ok->shortcut(FL_Enter); + + updir->callback(cb_up, (void*)this); + cancel->callback(cb_close, (void*)this); + browser->callback(cb_down, this); + browser->path_obj = where; + browser->init(initPath); + + if (G_Conf.browserW) + resize(G_Conf.browserX, G_Conf.browserY, G_Conf.browserW, G_Conf.browserH); + + gu_setFavicon(this); + show(); +} + + +/* -------------------------------------------------------------------------- */ + + +gdBrowser::~gdBrowser() { + G_Conf.browserX = x(); + G_Conf.browserY = y(); + G_Conf.browserW = w(); + G_Conf.browserH = h(); +} + + +/* -------------------------------------------------------------------------- */ + + +void gdBrowser::cb_load_patch (Fl_Widget *v, void *p) { ((gdBrowser*)p)->__cb_load_patch(); } +void gdBrowser::cb_load_sample (Fl_Widget *v, void *p) { ((gdBrowser*)p)->__cb_load_sample(); } +void gdBrowser::cb_save_sample (Fl_Widget *v, void *p) { ((gdBrowser*)p)->__cb_save_sample(); } +void gdBrowser::cb_save_patch (Fl_Widget *v, void *p) { ((gdBrowser*)p)->__cb_save_patch(); } +void gdBrowser::cb_save_project(Fl_Widget *v, void *p) { ((gdBrowser*)p)->__cb_save_project(); } +void gdBrowser::cb_down (Fl_Widget *v, void *p) { ((gdBrowser*)p)->__cb_down(); } +void gdBrowser::cb_up (Fl_Widget *v, void *p) { ((gdBrowser*)p)->__cb_up(); } +void gdBrowser::cb_close (Fl_Widget *v, void *p) { ((gdBrowser*)p)->__cb_close(); } +#ifdef WITH_VST +void gdBrowser::cb_loadPlugin (Fl_Widget *v, void *p) { ((gdBrowser*)p)->__cb_loadPlugin(); } +#endif + + +/* -------------------------------------------------------------------------- */ + + +void gdBrowser::__cb_load_patch() { + + if (browser->text(browser->value()) == NULL) + return; + + /* patchFile is the file to open. + * For patches: browser->get_selected_item() + * for projects: browser->get_selected_item() without extention + + * patch name appended */ + + std::string patchFile = browser->get_selected_item();; + bool isProject; + + if (gIsProject(browser->get_selected_item())) { + std::string patchName = gGetProjectName(browser->get_selected_item()); +#if defined(__linux__) || defined(__APPLE__) + patchFile = patchFile+"/"+patchName+".gptc"; +#elif defined(_WIN32) + patchFile = patchFile+"\\"+patchName+".gptc"; +#endif + isProject = true; + } + else + isProject = false; + + int res = glue_loadPatch(patchFile.c_str(), browser->path_obj->value(), status, isProject); + + if (res == PATCH_UNREADABLE) { + status->hide(); + if (isProject) + gdAlert("This project is unreadable."); + else + gdAlert("This patch is unreadable."); + } + else if (res == PATCH_INVALID) { + status->hide(); + if (isProject) + gdAlert("This project is not valid."); + else + gdAlert("This patch is not valid."); + } + else + do_callback(); +} + + +/* -------------------------------------------------------------------------- */ + + +void gdBrowser::__cb_save_sample() { + + if (strcmp(name->value(), "") == 0) { /// FIXME glue business + gdAlert("Please choose a file name."); + return; + } + + /* bruteforce check extension. */ + + std::string filename = gStripExt(name->value()); + char fullpath[PATH_MAX]; + sprintf(fullpath, "%s/%s.wav", where->value(), filename.c_str()); + + if (gFileExists(fullpath)) + if (!gdConfirmWin("Warning", "File exists: overwrite?")) + return; + + if (((SampleChannel*)ch)->save(fullpath)) + do_callback(); + else + gdAlert("Unable to save this sample!"); +} + + +/* -------------------------------------------------------------------------- */ + + +void gdBrowser::__cb_load_sample() { + if (browser->text(browser->value()) == NULL) + return; + + int res = glue_loadChannel((SampleChannel*) ch, browser->get_selected_item()); + + if (res == SAMPLE_LOADED_OK) { + do_callback(); + mainWin->delSubWindow(WID_SAMPLE_EDITOR); // if editor is open + } + else + mainWin->keyboard->printChannelMessage(res); +} + + +/* -------------------------------------------------------------------------- */ + + +void gdBrowser::__cb_down() { + const char *path = browser->get_selected_item(); + if (!path) // when click on an empty area + return; + if (!gIsDir(path)) { + + /* set the name of the patch/sample/project as the selected item */ + + if (type == BROWSER_SAVE_PATCH || type == BROWSER_SAVE_SAMPLE || type == BROWSER_SAVE_PROJECT) { + if (gIsProject(path)) { + std::string tmp = browser->text(browser->value()); + tmp.erase(0, 4); + name->value(tmp.c_str()); + } + else + name->value(browser->text(browser->value())); + } + return; + } + browser->clear(); + browser->down_dir(path); + browser->sort(); +} + + +/* -------------------------------------------------------------------------- */ + + +void gdBrowser::__cb_up() { + browser->clear(); + browser->up_dir(); + browser->sort(); +} + + +/* -------------------------------------------------------------------------- */ + + +void gdBrowser::__cb_save_patch() { + + if (strcmp(name->value(), "") == 0) { /// FIXME glue business + gdAlert("Please choose a file name."); + return; + } + + /* if name->value() contains ".gptc" */ + + char ext[6] = ".gptc"; + if (strstr(name->value(), ".gptc") != NULL) + ext[0] = '\0'; + + char fullpath[PATH_MAX]; + sprintf(fullpath, "%s/%s%s", where->value(), name->value(), ext); + if (gFileExists(fullpath)) + if (!gdConfirmWin("Warning", "File exists: overwrite?")) + return; + + if (glue_savePatch(fullpath, name->value(), false)) // false == not a project + do_callback(); + else + gdAlert("Unable to save the patch!"); +} + + +/* -------------------------------------------------------------------------- */ + + +void gdBrowser::__cb_save_project() { + + if (strcmp(name->value(), "") == 0) { /// FIXME glue business + gdAlert("Please choose a project name."); + return; + } + + /* check if name->value() contains ".gprj" */ + + char ext[6] = ".gprj"; + if (strstr(name->value(), ".gprj") != NULL) + ext[0] = '\0'; + + char fullpath[PATH_MAX]; +#if defined(_WIN32) + sprintf(fullpath, "%s\\%s%s", where->value(), name->value(), ext); +#else + sprintf(fullpath, "%s/%s%s", where->value(), name->value(), ext); +#endif + + if (gIsProject(fullpath) && !gdConfirmWin("Warning", "Project exists: overwrite?")) + return; + + if (glue_saveProject(fullpath, name->value())) + do_callback(); + else + gdAlert("Unable to save the project!"); +} + + +/* -------------------------------------------------------------------------- */ + + +#ifdef WITH_VST +void gdBrowser::__cb_loadPlugin() { + + if (browser->text(browser->value()) == NULL) + return; + + int res = G_PluginHost.addPlugin(browser->get_selected_item(), stackType, ch); + + /* store the folder path inside G_Conf, in order to reuse it the + * next time. */ + + G_Conf.setPath(G_Conf.pluginPath, where->value()); + + if (res) + do_callback(); + else + gdAlert("Unable to load the selected plugin!"); +} +#endif + + +/* -------------------------------------------------------------------------- */ + + +void gdBrowser::__cb_close() { + do_callback(); +} diff --git a/src/gd_browser.h b/src/gd_browser.h new file mode 100644 index 0000000..b5e8734 --- /dev/null +++ b/src/gd_browser.h @@ -0,0 +1,98 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * gd_browser + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + + +#ifndef GD_BROWSER_H +#define GD_BROWSER_H + + +#include +#include +#include "ge_window.h" + + +/* TODO - this class must be subclassed into gdPluginBrowser, gdFileBrowser, + * and so on. It's a real mess right now. */ + +class gdBrowser : public gWindow { + +private: + static void cb_down(Fl_Widget *v, void *p); + static void cb_up (Fl_Widget *v, void *p); + static void cb_load_sample (Fl_Widget *v, void *p); + static void cb_save_sample (Fl_Widget *v, void *p); + static void cb_load_patch (Fl_Widget *v, void *p); + static void cb_save_patch (Fl_Widget *v, void *p); + static void cb_save_project(Fl_Widget *v, void *p); + static void cb_close (Fl_Widget *w, void *p); +#ifdef WITH_VST + static void cb_loadPlugin (Fl_Widget *v, void *p); +#endif + + inline void __cb_down(); + inline void __cb_up(); + inline void __cb_load_sample(); + inline void __cb_save_sample(); + inline void __cb_save_project(); + inline void __cb_load_patch(); + inline void __cb_save_patch(); + inline void __cb_close(); +#ifdef WITH_VST + inline void __cb_loadPlugin(); +#endif + + class gBrowser *browser; + class gClick *ok; + class gClick *cancel; + class gInput *where; + class gInput *name; + class gClick *updir; + class gProgress *status; + + class Channel *ch; + + /* browser type: see const.h */ + + /** FIXME internal enum: + * enum browserType { + * TYPE_A, + * TYPE_B, + * .... + * }; */ + int type; + + /* PluginHost stack type. Used only when loading plugins */ + + int stackType; + +public: + gdBrowser(const char *title, const char *initPath, class Channel *ch, int type, int stackType=0); + ~gdBrowser(); +}; + +#endif diff --git a/src/gd_config.cpp b/src/gd_config.cpp new file mode 100644 index 0000000..54f2627 --- /dev/null +++ b/src/gd_config.cpp @@ -0,0 +1,841 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * gd_config + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + + +#include "gd_config.h" +#include "gd_keyGrabber.h" +#include "gd_devInfo.h" +#include "ge_mixed.h" +#include "conf.h" +#include "log.h" +#include "gui_utils.h" +#include "patch.h" +#include "kernelAudio.h" +#include "kernelMidi.h" + + +extern Patch G_Patch; +extern Conf G_Conf; +extern bool G_audio_status; + + +/* ------------------------------------------------------------------ */ + + +gTabMisc::gTabMisc(int X, int Y, int W, int H) + : Fl_Group(X, Y, W, H, "Misc") +{ + begin(); + debugMsg = new gChoice(x()+92, y()+9, 253, 20, "Debug messages"); + end(); + + debugMsg->add("(disabled)"); + debugMsg->add("To standard output"); + debugMsg->add("To file"); + + labelsize(11); + + switch (G_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 gTabMisc::save() +{ + switch(debugMsg->value()) { + case 0: + G_Conf.logMode = LOG_MODE_MUTE; + break; + case 1: + G_Conf.logMode = LOG_MODE_STDOUT; + break; + case 2: + G_Conf.logMode = LOG_MODE_FILE; + break; + } +} + + +/* ------------------------------------------------------------------ */ +/* ------------------------------------------------------------------ */ +/* ------------------------------------------------------------------ */ + + +gTabAudio::gTabAudio(int X, int Y, int W, int H) + : Fl_Group(X, Y, W, H, "Sound System") +{ + begin(); + soundsys = new gChoice(x()+92, y()+9, 253, 20, "System"); + buffersize = new gChoice(x()+92, y()+37, 55, 20, "Buffer size"); + samplerate = new gChoice(x()+290, y()+37, 55, 20, "Sample rate"); + sounddevOut = new gChoice(x()+92, y()+65, 225, 20, "Output device"); + devOutInfo = new gClick (x()+325, y()+65, 20, 20, "?"); + channelsOut = new gChoice(x()+92, y()+93, 55, 20, "Output channels"); + limitOutput = new gCheck (x()+155, y()+97, 55, 20, "Limit output"); + sounddevIn = new gChoice(x()+92, y()+121, 225, 20, "Input device"); + devInInfo = new gClick (x()+325, y()+121, 20, 20, "?"); + channelsIn = new gChoice(x()+92, y()+149, 55, 20, "Input channels"); + delayComp = new gInput (x()+290, y()+149, 55, 20, "Rec delay comp."); + rsmpQuality = new gChoice(x()+92, y()+177, 253, 20, "Resampling"); + new gBox(x(), rsmpQuality->y()+rsmpQuality->h()+8, w(), 92, "Restart Giada for the changes to take effect."); + end(); + labelsize(11); + +#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 (G_Conf.soundSystem) { + case SYS_API_ALSA: + soundsys->show("ALSA"); + break; + case SYS_API_JACK: + soundsys->show("Jack"); + buffersize->deactivate(); + samplerate->deactivate(); + break; + case SYS_API_PULSE: + soundsys->show("PulseAudio"); + break; + } + soundsysInitValue = soundsys->value(); + +#elif defined(_WIN32) + + if (kernelAudio::hasAPI(RtAudio::WINDOWS_DS)) + soundsys->add("DirectSound"); + if (kernelAudio::hasAPI(RtAudio::WINDOWS_ASIO)) + soundsys->add("ASIO"); + soundsys->show(G_Conf.soundSystem == SYS_API_DS ? "DirectSound" : "ASIO"); + soundsysInitValue = soundsys->value(); + +#elif defined (__APPLE__) + + if (kernelAudio::hasAPI(RtAudio::MACOSX_CORE)) + soundsys->add("CoreAudio"); + soundsys->show("CoreAudio"); + soundsysInitValue = soundsys->value(); + +#endif + + sounddevIn->callback(cb_fetchInChans, this); + sounddevOut->callback(cb_fetchOutChans, this); + + devOutInfo->callback(cb_showOutputInfo, this); + devInInfo->callback(cb_showInputInfo, this); + + fetchSoundDevs(); + + fetchOutChans(sounddevOut->value()); + fetchInChans(sounddevIn->value()); + + 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"); + + char buf[8]; + sprintf(buf, "%d", G_Conf.buffersize); + buffersize->show(buf); + + /* fill frequency dropdown menu */ + + int nfreq = kernelAudio::getTotalFreqs(sounddevOut->value()); + for (int i=0; ivalue(), i); + sprintf(buf, "%d", freq); + samplerate->add(buf); + if (freq == G_Conf.samplerate) + samplerate->value(i); + } + + 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(G_Conf.rsmpQuality); + + buf[0] = '\0'; + sprintf(buf, "%d", G_Conf.delayComp); + delayComp->value(buf); + delayComp->type(FL_INT_INPUT); + delayComp->maximum_size(5); + + limitOutput->value(G_Conf.limitOutput); + soundsys->callback(cb_deactivate_sounddev, (void*)this); +} + + +/* ------------------------------------------------------------------ */ + + +void gTabAudio::cb_deactivate_sounddev(Fl_Widget *w, void *p) { ((gTabAudio*)p)->__cb_deactivate_sounddev(); } +void gTabAudio::cb_fetchInChans(Fl_Widget *w, void *p) { ((gTabAudio*)p)->__cb_fetchInChans(); } +void gTabAudio::cb_fetchOutChans(Fl_Widget *w, void *p) { ((gTabAudio*)p)->__cb_fetchOutChans(); } +void gTabAudio::cb_showInputInfo(Fl_Widget *w, void *p) { ((gTabAudio*)p)->__cb_showInputInfo(); } +void gTabAudio::cb_showOutputInfo(Fl_Widget *w, void *p) { ((gTabAudio*)p)->__cb_showOutputInfo(); } + + +/* ------------------------------------------------------------------ */ + + +void gTabAudio::__cb_fetchInChans() +{ + fetchInChans(sounddevIn->value()); + channelsIn->value(0); +} + + +/* ------------------------------------------------------------------ */ + + +void gTabAudio::__cb_fetchOutChans() +{ + fetchOutChans(sounddevOut->value()); + channelsOut->value(0); +} + + +/* ------------------------------------------------------------------ */ + + +void gTabAudio::__cb_showInputInfo() +{ + unsigned dev = kernelAudio::getDeviceByName(sounddevIn->text(sounddevIn->value())); + new gdDevInfo(dev); +} + + +/* ------------------------------------------------------------------ */ + + +void gTabAudio::__cb_showOutputInfo() +{ + unsigned dev = kernelAudio::getDeviceByName(sounddevOut->text(sounddevOut->value())); + new gdDevInfo(dev); +} + + +/* ------------------------------------------------------------------ */ + + +void gTabAudio::__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. */ + + if (soundsysInitValue == soundsys->value()) { + 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(); + } + else { + sounddevOut->deactivate(); + sounddevOut->clear(); + sounddevOut->add("-- restart to fetch device(s) --"); + sounddevOut->value(0); + channelsOut->deactivate(); + devOutInfo->deactivate(); + + sounddevIn->deactivate(); + sounddevIn->clear(); + sounddevIn->add("-- restart to fetch device(s) --"); + sounddevIn->value(0); + channelsIn->deactivate(); + devInInfo->deactivate(); + } +} + +/* ------------------------------------------------------------------ */ + + +void gTabAudio::fetchInChans(int menuItem) +{ + /* if menuItem==0 device in input is disabled. */ + + if (menuItem == 0) { + devInInfo ->deactivate(); + channelsIn->deactivate(); + delayComp ->deactivate(); + return; + } + + devInInfo ->activate(); + channelsIn->activate(); + delayComp ->activate(); + + channelsIn->clear(); + + unsigned dev = kernelAudio::getDeviceByName(sounddevIn->text(sounddevIn->value())); + unsigned chs = kernelAudio::getMaxInChans(dev); + + if (chs == 0) { + channelsIn->add("none"); + channelsIn->value(0); + return; + } + for (unsigned i=0; iadd(str); + } + channelsIn->value(G_Conf.channelsIn); +} + + +/* ------------------------------------------------------------------ */ + + +void gTabAudio::fetchOutChans(int menuItem) +{ + channelsOut->clear(); + + unsigned dev = kernelAudio::getDeviceByName(sounddevOut->text(sounddevOut->value())); + unsigned chs = kernelAudio::getMaxOutChans(dev); + + if (chs == 0) { + channelsOut->add("none"); + channelsOut->value(0); + return; + } + for (unsigned i=0; iadd(str); + } + channelsOut->value(G_Conf.channelsOut); +} + + +/* ------------------------------------------------------------------ */ + + +int gTabAudio::findMenuDevice(gChoice *m, int device) +{ + if (device == -1) + return 0; + + if (G_audio_status == false) + return 0; + + for (int i=0; isize(); i++) { + if (kernelAudio::getDeviceName(device) == NULL) + continue; + if (m->text(i) == NULL) + continue; + if (strcmp(m->text(i), kernelAudio::getDeviceName(device))==0) + return i; + } + + return 0; +} + + +/* ------------------------------------------------------------------ */ + + +void gTabAudio::fetchSoundDevs() +{ + if (kernelAudio::numDevs == 0) { + sounddevOut->add("-- no devices found --"); + sounddevOut->value(0); + sounddevIn->add("-- no devices found --"); + sounddevIn->value(0); + devInInfo ->deactivate(); + devOutInfo->deactivate(); + } + else { + + devInInfo ->activate(); + devOutInfo->activate(); + + /* input device may be disabled: now device number -1 is the disabled + * one. KernelAudio knows how to handle it. */ + + sounddevIn->add("(disabled)"); + + for (unsigned i=0; i 0) + sounddevOut->add(tmp.c_str()); + + if (kernelAudio::getMaxInChans(i) > 0) + sounddevIn->add(tmp.c_str()); + } + + /* we show the device saved in the configuration file. */ + + if (sounddevOut->size() == 0) { + sounddevOut->add("-- no devices found --"); + sounddevOut->value(0); + devOutInfo->deactivate(); + } + else { + int outMenuValue = findMenuDevice(sounddevOut, G_Conf.soundDeviceOut); + sounddevOut->value(outMenuValue); + } + + if (sounddevIn->size() == 0) { + sounddevIn->add("-- no devices found --"); + sounddevIn->value(0); + devInInfo->deactivate(); + } + else { + int inMenuValue = findMenuDevice(sounddevIn, G_Conf.soundDeviceIn); + sounddevIn->value(inMenuValue); + } + } +} + + +/* ------------------------------------------------------------------ */ + + +void gTabAudio::save() +{ + /** FIXME - wrong, if API is missing! Right way in gTabMidi::save */ + +#ifdef __linux__ + if (soundsys->value() == 0) G_Conf.soundSystem = SYS_API_ALSA; + else if (soundsys->value() == 1) G_Conf.soundSystem = SYS_API_JACK; + else if (soundsys->value() == 2) G_Conf.soundSystem = SYS_API_PULSE; +#else +#ifdef _WIN32 + if (soundsys->value() == 0) G_Conf.soundSystem = SYS_API_DS; + else if (soundsys->value() == 1) G_Conf.soundSystem = SYS_API_ASIO; +#endif +#endif + + /* use the device name to search into the drop down menu's */ + + G_Conf.soundDeviceOut = kernelAudio::getDeviceByName(sounddevOut->text(sounddevOut->value())); + G_Conf.soundDeviceIn = kernelAudio::getDeviceByName(sounddevIn->text(sounddevIn->value())); + G_Conf.channelsOut = channelsOut->value(); + G_Conf.channelsIn = channelsIn->value(); + G_Conf.limitOutput = limitOutput->value(); + G_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 (G_Conf.soundDeviceOut == -1) + G_Conf.soundDeviceOut = 0; + + int bufsize = atoi(buffersize->text()); + if (bufsize % 2 != 0) bufsize++; + if (bufsize < 8) bufsize = 8; + if (bufsize > 8192) bufsize = 8192; + G_Conf.buffersize = bufsize; + + const Fl_Menu_Item *i = NULL; + i = samplerate->mvalue(); // mvalue() returns a pointer to the last menu item that was picked + if (i) + G_Conf.samplerate = atoi(i->label()); + + int _delayComp = atoi(delayComp->value()); + if (_delayComp < 0) _delayComp = 0; + G_Conf.delayComp = _delayComp; +} + + +/* ------------------------------------------------------------------ */ +/* ------------------------------------------------------------------ */ +/* ------------------------------------------------------------------ */ + + +gTabMidi::gTabMidi(int X, int Y, int W, int H) + : Fl_Group(X, Y, W, H, "MIDI") +{ + begin(); + system = new gChoice(x()+92, y()+9, 253, 20, "System"); + portOut = new gChoice(x()+92, system->y()+system->h()+8, 253, 20, "Output port"); + portIn = new gChoice(x()+92, portOut->y()+portOut->h()+8, 253, 20, "Input port"); + noNoteOff = new gCheck (x()+92, portIn->y()+portIn->h()+8, 253, 20, "Device does not send NoteOff"); + sync = new gChoice(x()+92, noNoteOff->y()+noNoteOff->h(), 253, 20, "Sync"); + new gBox(x(), sync->y()+sync->h()+8, w(), h()-125, "Restart Giada for the changes to take effect."); + end(); + + labelsize(11); + + system->callback(cb_changeSystem, (void*)this); + + fetchSystems(); + fetchOutPorts(); + fetchInPorts(); + + noNoteOff->value(G_Conf.noNoteOff); + + sync->add("(disabled)"); + sync->add("MIDI Clock (master)"); + sync->add("MTC (master)"); + if (G_Conf.midiSync == MIDI_SYNC_NONE) + sync->value(0); + else if (G_Conf.midiSync == MIDI_SYNC_CLOCK_M) + sync->value(1); + else if (G_Conf.midiSync == MIDI_SYNC_MTC_M) + sync->value(2); + + systemInitValue = system->value(); +} + + +/* ------------------------------------------------------------------ */ + + +void gTabMidi::fetchOutPorts() { + + if (kernelMidi::numOutPorts == 0) { + portOut->add("-- no ports found --"); + portOut->value(0); + portOut->deactivate(); + } + else { + + portOut->add("(disabled)"); + + for (unsigned i=0; iadd(t); + } + + portOut->value(G_Conf.midiPortOut+1); // +1 because midiPortOut=-1 is '(disabled)' + } +} + +/* ------------------------------------------------------------------ */ + + +void gTabMidi::fetchInPorts() +{ + if (kernelMidi::numInPorts == 0) { + portIn->add("-- no ports found --"); + portIn->value(0); + portIn->deactivate(); + } + else { + + portIn->add("(disabled)"); + + for (unsigned i=0; iadd(t); + } + + portIn->value(G_Conf.midiPortIn+1); // +1 because midiPortIn=-1 is '(disabled)' + } +} + + +/* ------------------------------------------------------------------ */ + + +void gTabMidi::save() +{ + if (!strcmp("ALSA", system->text(system->value()))) + G_Conf.midiSystem = RtMidi::LINUX_ALSA; + else if (!strcmp("Jack", system->text(system->value()))) + G_Conf.midiSystem = RtMidi::UNIX_JACK; + else if (!strcmp("Multimedia MIDI", system->text(system->value()))) + G_Conf.midiSystem = RtMidi::WINDOWS_MM; + //else if (!strcmp("Kernel Streaming MIDI", system->text(system->value()))) + // G_Conf.midiSystem = RtMidi::WINDOWS_KS; + else if (!strcmp("OSX Core MIDI", system->text(system->value()))) + G_Conf.midiSystem = RtMidi::MACOSX_CORE; + + G_Conf.midiPortOut = portOut->value()-1; // -1 because midiPortOut=-1 is '(disabled)' + G_Conf.midiPortIn = portIn->value()-1; // -1 because midiPortIn=-1 is '(disabled)' + + G_Conf.noNoteOff = noNoteOff->value(); + + if (sync->value() == 0) + G_Conf.midiSync = MIDI_SYNC_NONE; + else if (sync->value() == 1) + G_Conf.midiSync = MIDI_SYNC_CLOCK_M; + else if (sync->value() == 2) + G_Conf.midiSync = MIDI_SYNC_MTC_M; +} + + +/* ------------------------------------------------------------------ */ + + +void gTabMidi::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"); + //if (kernelMidi::hasAPI(RtMidi::WINDOWS_KS)) + // system->add("Kernel Streaming MIDI"); + +#elif defined (__APPLE__) + + system->add("OSX Core MIDI"); + +#endif + + switch (G_Conf.midiSystem) { + case RtMidi::LINUX_ALSA: system->show("ALSA"); break; + case RtMidi::UNIX_JACK: system->show("Jack"); break; + case RtMidi::WINDOWS_MM: system->show("Multimedia MIDI"); break; + //case RtMidi::WINDOWS_KS: system->show("Kernel Streaming MIDI"); break; + case RtMidi::MACOSX_CORE: system->show("OSX Core MIDI"); break; + default: system->value(0); break; + } +} + + +/* ------------------------------------------------------------------ */ + + +void gTabMidi::cb_changeSystem(Fl_Widget *w, void *p) { ((gTabMidi*)p)->__cb_changeSystem(); } + + +/* ------------------------------------------------------------------ */ + + +void gTabMidi::__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(); + } + else { + portOut->deactivate(); + portOut->clear(); + portOut->add("-- restart to fetch device(s) --"); + portOut->value(0); + } + +} + + +/* ------------------------------------------------------------------ */ +/* ------------------------------------------------------------------ */ +/* ------------------------------------------------------------------ */ + + +gTabBehaviors::gTabBehaviors(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 gBox(x(), y()+10, 70, 25, "When a channel with recorded actions is halted:", FL_ALIGN_LEFT); + recsStopOnChanHalt_1 = new gRadio(x()+25, y()+35, 280, 20, "stop it immediately"); + recsStopOnChanHalt_0 = new gRadio(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 gBox(x(), y()+80, 70, 25, "When the sequencer is halted:", FL_ALIGN_LEFT); + chansStopOnSeqHalt_1 = new gRadio(x()+25, y()+105, 280, 20, "stop immediately all dynamic channels"); + chansStopOnSeqHalt_0 = new gRadio(x()+25, y()+125, 280, 20, "play all dynamic channels until finished"); + radioGrp_2->end(); + + treatRecsAsLoops = new gCheck(x(), y()+155, 280, 20, "Treat one shot channels with actions as loops"); + + end(); + labelsize(11); + + G_Conf.recsStopOnChanHalt == 1 ? recsStopOnChanHalt_1->value(1) : recsStopOnChanHalt_0->value(1); + G_Conf.chansStopOnSeqHalt == 1 ? chansStopOnSeqHalt_1->value(1) : chansStopOnSeqHalt_0->value(1); + G_Conf.treatRecsAsLoops == 1 ? treatRecsAsLoops->value(1) : treatRecsAsLoops->value(0); + + 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 gTabBehaviors::cb_radio_mutex(Fl_Widget *w, void *p) { ((gTabBehaviors*)p)->__cb_radio_mutex(w); } + + +/* ------------------------------------------------------------------ */ + + +void gTabBehaviors::__cb_radio_mutex(Fl_Widget *w) +{ + ((Fl_Button *)w)->type(FL_RADIO_BUTTON); +} + + +/* ------------------------------------------------------------------ */ + + +void gTabBehaviors::save() +{ + G_Conf.recsStopOnChanHalt = recsStopOnChanHalt_1->value() == 1 ? 1 : 0; + G_Conf.chansStopOnSeqHalt = chansStopOnSeqHalt_1->value() == 1 ? 1 : 0; + G_Conf.treatRecsAsLoops = treatRecsAsLoops->value() == 1 ? 1 : 0; +} + + +/* ------------------------------------------------------------------ */ +/* ------------------------------------------------------------------ */ +/* ------------------------------------------------------------------ */ + + +gdConfig::gdConfig(int w, int h) : gWindow(w, h, "Configuration") +{ + set_modal(); + + if (G_Conf.configX) + resize(G_Conf.configX, G_Conf.configY, this->w(), this->h()); + + Fl_Tabs *tabs = new Fl_Tabs(8, 8, w-16, h-44); + tabAudio = new gTabAudio(tabs->x()+10, tabs->y()+20, tabs->w()-20, tabs->h()-40); + tabMidi = new gTabMidi(tabs->x()+10, tabs->y()+20, tabs->w()-20, tabs->h()-40); + tabBehaviors = new gTabBehaviors(tabs->x()+10, tabs->y()+20, tabs->w()-20, tabs->h()-40); + tabMisc = new gTabMisc(tabs->x()+10, tabs->y()+20, tabs->w()-20, tabs->h()-40); + tabs->end(); + + save = new gClick (w-88, h-28, 80, 20, "Save"); + cancel = new gClick (w-176, h-28, 80, 20, "Cancel"); + + end(); + + tabs->box(FL_FLAT_BOX); // TODO - G_BOX crashes FLTK 1.3.3 + + tabs->labelcolor(COLOR_TEXT_0); + + save->callback(cb_save_config, (void*)this); + cancel->callback(cb_cancel, (void*)this); + + gu_setFavicon(this); + setId(WID_CONFIG); + show(); +} + + +/* ------------------------------------------------------------------ */ + + +gdConfig::~gdConfig() +{ + G_Conf.configX = x(); + G_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(); + do_callback(); +} + + +/* ------------------------------------------------------------------ */ + + +void gdConfig::__cb_cancel() +{ + do_callback(); +} diff --git a/src/gd_config.h b/src/gd_config.h new file mode 100644 index 0000000..1cfc202 --- /dev/null +++ b/src/gd_config.h @@ -0,0 +1,167 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * gd_config + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + +#ifndef GD_CONFIG_H +#define GD_CONFIG_H + +#include +#include +#include +#include "ge_window.h" + + +class gdConfig : public gWindow +{ +private: + static void cb_save_config (Fl_Widget *w, void *p); + static void cb_cancel (Fl_Widget *w, void *p); + inline void __cb_save_config(); + inline void __cb_cancel(); + +public: + gdConfig(int w, int h); + ~gdConfig(); + + class gTabAudio *tabAudio; + class gTabBehaviors *tabBehaviors; + class gTabMidi *tabMidi; + class gTabMisc *tabMisc; + class gClick *save; + class gClick *cancel; +}; + + +/* ------------------------------------------------------------------ */ + + +class gTabMidi : public Fl_Group +{ +private: + void fetchSystems(); + void fetchOutPorts(); + void fetchInPorts(); + + static void cb_changeSystem (Fl_Widget *w, void *p); + inline void __cb_changeSystem(); + + int systemInitValue; + +public: + class gChoice *system; + class gChoice *portOut; + class gChoice *portIn; + class gCheck *noNoteOff; + class gChoice *sync; + + gTabMidi(int x, int y, int w, int h); + + void save(); +}; + + +/* ------------------------------------------------------------------ */ + + +class gTabAudio : 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(class gChoice *m, int device); + + int soundsysInitValue; + +public: + class gChoice *soundsys; + class gChoice *samplerate; + class gChoice *rsmpQuality; + class gChoice *sounddevIn; + class gClick *devInInfo; + class gChoice *channelsIn; + class gChoice *sounddevOut; + class gClick *devOutInfo; + class gChoice *channelsOut; + class gCheck *limitOutput; + class gChoice *buffersize; + class gInput *delayComp; + + gTabAudio(int x, int y, int w, int h); + + void save(); +}; + + +/* ------------------------------------------------------------------ */ + + +class gTabBehaviors : public Fl_Group +{ +private: + static void cb_radio_mutex (Fl_Widget *w, void *p); + inline void __cb_radio_mutex(Fl_Widget *w); + +public: + class gRadio *recsStopOnChanHalt_1; + class gRadio *recsStopOnChanHalt_0; + class gRadio *chansStopOnSeqHalt_1; + class gRadio *chansStopOnSeqHalt_0; + class gCheck *treatRecsAsLoops; + + gTabBehaviors(int x, int y, int w, int h); + + void save(); +}; + + +/* ------------------------------------------------------------------ */ + + +class gTabMisc : public Fl_Group +{ +public: + class gChoice *debugMsg; + + gTabMisc(int x, int y, int w, int h); + + void save(); +}; + + +#endif diff --git a/src/gd_devInfo.cpp b/src/gd_devInfo.cpp new file mode 100644 index 0000000..b7c5e40 --- /dev/null +++ b/src/gd_devInfo.cpp @@ -0,0 +1,107 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * gd_devInfo + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + + +#include "gd_devInfo.h" +#include "ge_mixed.h" +#include "kernelAudio.h" +#include "gui_utils.h" + + +gdDevInfo::gdDevInfo(unsigned dev) +: Fl_Window(340, 300, "Device information") { + set_modal(); + + text = new gBox(8, 8, 320, 200, "", (Fl_Align) (FL_ALIGN_LEFT | FL_ALIGN_TOP)); + close = new gClick(252, h()-28, 80, 20, "Close"); + end(); + + std::string bufTxt; + char bufNum[128]; + int lines = 0; + + bufTxt = "Device name: "; + bufTxt += +kernelAudio::getDeviceName(dev); + bufTxt += "\n"; + lines++; + + bufTxt += "Total output(s): "; + sprintf(bufNum, "%d\n", kernelAudio::getMaxOutChans(dev)); + bufTxt += bufNum; + lines++; + + bufTxt += "Total intput(s): "; + sprintf(bufNum, "%d\n", kernelAudio::getMaxInChans(dev)); + bufTxt += bufNum; + lines++; + + bufTxt += "Duplex channel(s): "; + sprintf(bufNum, "%d\n", kernelAudio::getDuplexChans(dev)); + bufTxt += bufNum; + lines++; + + bufTxt += "Default output: "; + sprintf(bufNum, "%s\n", kernelAudio::isDefaultOut(dev) ? "yes" : "no"); + bufTxt += bufNum; + lines++; + + bufTxt += "Default input: "; + sprintf(bufNum, "%s\n", kernelAudio::isDefaultIn(dev) ? "yes" : "no"); + bufTxt += bufNum; + lines++; + + int totalFreq = kernelAudio::getTotalFreqs(dev); + bufTxt += "Supported frequencies: "; + sprintf(bufNum, "%d", totalFreq); + bufTxt += bufNum; + lines++; + + for (int i=0; icopy_label(bufTxt.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() {} diff --git a/src/gd_devInfo.h b/src/gd_devInfo.h new file mode 100644 index 0000000..c5a06ca --- /dev/null +++ b/src/gd_devInfo.h @@ -0,0 +1,47 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * gd_devInfo + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + + +#ifndef GD_DEV_INFO_H +#define GD_DEV_INFO_H + +#include +#include + + +class gdDevInfo : public Fl_Window { +private: + class gBox *text; + class gClick *close; + +public: + gdDevInfo(unsigned dev); + ~gdDevInfo(); +}; + +#endif diff --git a/src/gd_editor.cpp b/src/gd_editor.cpp new file mode 100644 index 0000000..3e96a1b --- /dev/null +++ b/src/gd_editor.cpp @@ -0,0 +1,491 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * gd_editor + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + + +#include "gd_editor.h" +#include "gd_mainWindow.h" +#include "ge_waveform.h" +#include "gd_warnings.h" +#include "gg_waveTools.h" +#include "ge_mixed.h" +#include "gg_keyboard.h" +#include "ge_channel.h" +#include "waveFx.h" +#include "conf.h" +#include "graphics.h" +#include "gui_utils.h" +#include "glue.h" +#include "mixerHandler.h" +#include "channel.h" +#include "sampleChannel.h" +#include "mixer.h" +#include "wave.h" + + +extern Mixer G_Mixer; +extern gdMainWindow *mainWin; +extern Conf G_Conf; + + +gdEditor::gdEditor(SampleChannel *ch) + : gWindow(640, 480), + ch(ch) +{ + set_non_modal(); + + if (G_Conf.sampleEditorX) + resize(G_Conf.sampleEditorX, G_Conf.sampleEditorY, G_Conf.sampleEditorW, G_Conf.sampleEditorH); + + /* top bar: grid and zoom tools */ + + Fl_Group *bar = new Fl_Group(8, 8, w()-16, 20); + bar->begin(); + grid = new gChoice(bar->x(), bar->y(), 50, 20); + snap = new gCheck(grid->x()+grid->w()+4, bar->y()+4, 12, 12); + zoomOut = new gClick(bar->x()+bar->w()-20, bar->y(), 20, 20, "", zoomOutOff_xpm, zoomOutOn_xpm); + zoomIn = new gClick(zoomOut->x()-24, bar->y(), 20, 20, "", zoomInOff_xpm, zoomInOn_xpm); + bar->end(); + bar->resizable(new gBox(grid->x()+grid->w()+4, bar->y(), 80, bar->h())); + + /* waveform */ + + waveTools = new gWaveTools(8, 36, w()-16, h()-120, ch); + waveTools->end(); + + /* other tools */ + + Fl_Group *tools = new Fl_Group(8, waveTools->y()+waveTools->h()+8, w()-16, 130); + tools->begin(); + volume = new gDial (tools->x()+42, tools->y(), 20, 20, "Volume"); + volumeNum = new gInput(volume->x()+volume->w()+4, tools->y(), 46, 20, "dB"); + + boost = new gDial (volumeNum->x()+volumeNum->w()+80, tools->y(), 20, 20, "Boost"); + boostNum = new gInput(boost->x()+boost->w()+4, tools->y(), 46, 20, "dB"); + + normalize = new gClick(boostNum->x()+boostNum->w()+54, tools->y(), 70, 20, "Normalize"); + pan = new gDial (normalize->x()+normalize->w()+40, tools->y(), 20, 20, "Pan"); + panNum = new gInput(pan->x()+pan->w()+4, tools->y(), 45, 20, "%"); + + pitch = new gDial (tools->x()+42, volume->y()+volume->h()+4, 20, 20, "Pitch"); + pitchNum = new gInput(pitch->x()+pitch->w()+4, volume->y()+volume->h()+4, 46, 20); + pitchToBar = new gClick(pitchNum->x()+pitchNum->w()+4, volume->y()+volume->h()+4, 46, 20, "To bar"); + pitchToSong = new gClick(pitchToBar->x()+pitchToBar->w()+4, volume->y()+volume->h()+4, 46, 20, "To song"); + pitchHalf = new gClick(pitchToSong->x()+pitchToSong->w()+4, volume->y()+volume->h()+4, 21, 20, "÷"); + pitchDouble = new gClick(pitchHalf->x()+pitchHalf->w()+4, volume->y()+volume->h()+4, 21, 20, "×"); + pitchReset = new gClick(pitchDouble->x()+pitchDouble->w()+4, volume->y()+volume->h()+4, 46, 20, "Reset"); + reload = new gClick(pitchReset->x()+pitchReset->w()+4, volume->y()+volume->h()+4, 70, 20, "Reload"); + + chanStart = new gInput(tools->x()+52, pitch->y()+pitch->h()+4, 60, 20, "Start"); + chanEnd = new gInput(chanStart->x()+chanStart->w()+40, pitch->y()+pitch->h()+4, 60, 20, "End"); + resetStartEnd = new gClick(chanEnd->x()+chanEnd->w()+4, pitch->y()+pitch->h()+4, 46, 20, "Reset"); + + tools->end(); + tools->resizable(new gBox(panNum->x()+panNum->w()+4, tools->y(), 80, tools->h())); + + /* grid tool setup */ + + 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"); + grid->value(G_Conf.sampleEditorGridVal); + grid->callback(cb_changeGrid, (void*)this); + + snap->value(G_Conf.sampleEditorGridOn); + snap->callback(cb_enableSnap, (void*)this); + + /* TODO - redraw grid if != (off) */ + + char buf[16]; + sprintf(buf, "%d", ch->begin / 2); // divided by 2 because stereo + chanStart->value(buf); + chanStart->type(FL_INT_INPUT); + chanStart->callback(cb_setChanPos, this); + + /* inputs callback: fire when they lose focus or Enter is pressed. */ + + chanStart->when(FL_WHEN_RELEASE | FL_WHEN_ENTER_KEY); + chanEnd ->when(FL_WHEN_RELEASE | FL_WHEN_ENTER_KEY); + + sprintf(buf, "%d", ch->end / 2); // divided by 2 because stereo + chanEnd->value(buf); + chanEnd->type(FL_INT_INPUT); + chanEnd->callback(cb_setChanPos, this); + + resetStartEnd->callback(cb_resetStartEnd, this); + + volume->callback(cb_setVolume, (void*)this); + volume->value(ch->guiChannel->vol->value()); + + float dB = 20*log10(ch->volume); // dB = 20*log_10(linear value) + if (dB > -INFINITY) sprintf(buf, "%.2f", dB); + else sprintf(buf, "-inf"); + volumeNum->value(buf); + volumeNum->align(FL_ALIGN_RIGHT); + volumeNum->callback(cb_setVolumeNum, (void*)this); + + boost->range(1.0f, 10.0f); + boost->callback(cb_setBoost, (void*)this); + if (ch->boost > 10.f) + boost->value(10.0f); + else + boost->value(ch->boost); + boost->when(FL_WHEN_CHANGED | FL_WHEN_RELEASE); + + float boost = 20*log10(ch->boost); // dB = 20*log_10(linear value) + sprintf(buf, "%.2f", boost); + boostNum->value(buf); + boostNum->align(FL_ALIGN_RIGHT); + boostNum->callback(cb_setBoostNum, (void*)this); + + normalize->callback(cb_normalize, (void*)this); + + pan->range(0.0f, 2.0f); + pan->callback(cb_panning, (void*)this); + + pitch->range(0.01f, 4.0f); + pitch->value(ch->pitch); + pitch->callback(cb_setPitch, (void*)this); + pitch->when(FL_WHEN_RELEASE); + + sprintf(buf, "%.4f", ch->pitch); // 4 digits + pitchNum->value(buf); + pitchNum->align(FL_ALIGN_RIGHT); + pitchNum->callback(cb_setPitchNum, (void*)this); + pitchNum->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); + + reload->callback(cb_reload, (void*)this); + + zoomOut->callback(cb_zoomOut, (void*)this); + zoomIn->callback(cb_zoomIn, (void*)this); + + /* logical samples (aka takes) cannot be reloaded. So far. */ + + if (ch->wave->isLogical) + reload->deactivate(); + + if (ch->panRight < 1.0f) { + char buf[8]; + sprintf(buf, "%d L", abs((ch->panRight * 100.0f) - 100)); + pan->value(ch->panRight); + panNum->value(buf); + } + else if (ch->panRight == 1.0f && ch->panLeft == 1.0f) { + pan->value(1.0f); + panNum->value("C"); + } + else { + char buf[8]; + sprintf(buf, "%d R", abs((ch->panLeft * 100.0f) - 100)); + pan->value(2.0f - ch->panLeft); + panNum->value(buf); + } + + panNum->align(FL_ALIGN_RIGHT); + panNum->readonly(1); + panNum->cursor_color(FL_WHITE); + + gu_setFavicon(this); + size_range(640, 480); + resizable(waveTools); + + label(ch->wave->name.c_str()); + + show(); +} + + +/* ------------------------------------------------------------------ */ + + +gdEditor::~gdEditor() +{ + G_Conf.sampleEditorX = x(); + G_Conf.sampleEditorY = y(); + G_Conf.sampleEditorW = w(); + G_Conf.sampleEditorH = h(); + G_Conf.sampleEditorGridVal = grid->value(); + G_Conf.sampleEditorGridOn = snap->value(); +} + + +/* ------------------------------------------------------------------ */ + + +void gdEditor::cb_setChanPos (Fl_Widget *w, void *p) { ((gdEditor*)p)->__cb_setChanPos(); } +void gdEditor::cb_resetStartEnd (Fl_Widget *w, void *p) { ((gdEditor*)p)->__cb_resetStartEnd(); } +void gdEditor::cb_setVolume (Fl_Widget *w, void *p) { ((gdEditor*)p)->__cb_setVolume(); } +void gdEditor::cb_setVolumeNum (Fl_Widget *w, void *p) { ((gdEditor*)p)->__cb_setVolumeNum(); } +void gdEditor::cb_setBoost (Fl_Widget *w, void *p) { ((gdEditor*)p)->__cb_setBoost(); } +void gdEditor::cb_setBoostNum (Fl_Widget *w, void *p) { ((gdEditor*)p)->__cb_setBoostNum(); } +void gdEditor::cb_normalize (Fl_Widget *w, void *p) { ((gdEditor*)p)->__cb_normalize(); } +void gdEditor::cb_panning (Fl_Widget *w, void *p) { ((gdEditor*)p)->__cb_panning(); } +void gdEditor::cb_reload (Fl_Widget *w, void *p) { ((gdEditor*)p)->__cb_reload(); } +void gdEditor::cb_setPitch (Fl_Widget *w, void *p) { ((gdEditor*)p)->__cb_setPitch(); } +void gdEditor::cb_setPitchToBar (Fl_Widget *w, void *p) { ((gdEditor*)p)->__cb_setPitchToBar(); } +void gdEditor::cb_setPitchToSong (Fl_Widget *w, void *p) { ((gdEditor*)p)->__cb_setPitchToSong(); } +void gdEditor::cb_setPitchHalf (Fl_Widget *w, void *p) { ((gdEditor*)p)->__cb_setPitchHalf(); } +void gdEditor::cb_setPitchDouble (Fl_Widget *w, void *p) { ((gdEditor*)p)->__cb_setPitchDouble(); } +void gdEditor::cb_resetPitch (Fl_Widget *w, void *p) { ((gdEditor*)p)->__cb_resetPitch(); } +void gdEditor::cb_setPitchNum (Fl_Widget *w, void *p) { ((gdEditor*)p)->__cb_setPitchNum(); } +void gdEditor::cb_zoomIn (Fl_Widget *w, void *p) { ((gdEditor*)p)->__cb_zoomIn(); } +void gdEditor::cb_zoomOut (Fl_Widget *w, void *p) { ((gdEditor*)p)->__cb_zoomOut(); } +void gdEditor::cb_changeGrid (Fl_Widget *w, void *p) { ((gdEditor*)p)->__cb_changeGrid(); } +void gdEditor::cb_enableSnap (Fl_Widget *w, void *p) { ((gdEditor*)p)->__cb_enableSnap(); } + + +/* ------------------------------------------------------------------ */ + + +void gdEditor::__cb_enableSnap() +{ + waveTools->waveform->setSnap(!waveTools->waveform->getSnap()); +} + + +/* ------------------------------------------------------------------ */ + + +void gdEditor::__cb_setPitchToBar() +{ + glue_setPitch(this, ch, ch->end/(float)G_Mixer.framesPerBar, true); +} + + +/* ------------------------------------------------------------------ */ + + +void gdEditor::__cb_setPitchToSong() +{ + glue_setPitch(this, ch, ch->end/(float)G_Mixer.totalFrames, true); +} + + +/* ------------------------------------------------------------------ */ + + +void gdEditor::__cb_resetPitch() +{ + glue_setPitch(this, ch, 1.0f, true); +} + + +/* ------------------------------------------------------------------ */ + + +void gdEditor::__cb_setChanPos() +{ + glue_setBeginEndChannel( + this, + ch, + atoi(chanStart->value())*2, // glue halves printed values + atoi(chanEnd->value())*2, + true + ); +} + + +/* ------------------------------------------------------------------ */ + + +void gdEditor::__cb_resetStartEnd() +{ + glue_setBeginEndChannel(this, ch, 0, ch->wave->size, true); +} + + +/* ------------------------------------------------------------------ */ + + +void gdEditor::__cb_setVolume() +{ + glue_setVolEditor(this, ch, volume->value(), false); +} + + +/* ------------------------------------------------------------------ */ + + +void gdEditor::__cb_setVolumeNum() +{ + glue_setVolEditor(this, ch, atof(volumeNum->value()), true); +} + + +/* ------------------------------------------------------------------ */ + + +void gdEditor::__cb_setBoost() +{ + if (Fl::event() == FL_DRAG) + glue_setBoost(this, ch, boost->value(), false); + else if (Fl::event() == FL_RELEASE) { + glue_setBoost(this, ch, boost->value(), false); + waveTools->updateWaveform(); + } +} + + +/* ------------------------------------------------------------------ */ + + +void gdEditor::__cb_setBoostNum() +{ + glue_setBoost(this, ch, atof(boostNum->value()), true); + waveTools->updateWaveform(); +} + + +/* ------------------------------------------------------------------ */ + + +void gdEditor::__cb_normalize() +{ + float val = wfx_normalizeSoft(ch->wave); + glue_setBoost(this, ch, val, false); // we pretend that a fake user turns the dial (numeric=false) + if (val < 0.0f) + boost->value(0.0f); + else + if (val > 20.0f) // a dial > than it's max value goes crazy + boost->value(10.0f); + else + boost->value(val); + waveTools->updateWaveform(); +} + + +/* ------------------------------------------------------------------ */ + + +void gdEditor::__cb_panning() +{ + glue_setPanning(this, ch, pan->value()); +} + + +/* ------------------------------------------------------------------ */ + + +void gdEditor::__cb_reload() +{ + if (!gdConfirmWin("Warning", "Reload sample: are you sure?")) + return; + + /* no need for glue_loadChan, there's no gui to refresh */ + + ch->load(ch->wave->pathfile.c_str()); + + glue_setBoost(this, ch, DEFAULT_BOOST, true); + glue_setPitch(this, ch, gDEFAULT_PITCH, true); + glue_setPanning(this, ch, 1.0f); + pan->value(1.0f); // glue_setPanning doesn't do it + pan->redraw(); // glue_setPanning doesn't do it + + waveTools->waveform->stretchToWindow(); + waveTools->updateWaveform(); + + glue_setBeginEndChannel(this, ch, 0, ch->wave->size, true); + + redraw(); +} + + +/* ------------------------------------------------------------------ */ + + +void gdEditor::__cb_setPitch() +{ + glue_setPitch(this, ch, pitch->value(), false); +} + + +/* ------------------------------------------------------------------ */ + + +void gdEditor::__cb_setPitchNum() +{ + glue_setPitch(this, ch, atof(pitchNum->value()), true); +} + + +/* ------------------------------------------------------------------ */ + + +void gdEditor::__cb_setPitchHalf() +{ + glue_setPitch(this, ch, pitch->value()/2, true); +} + + +/* ------------------------------------------------------------------ */ + + +void gdEditor::__cb_setPitchDouble() +{ + glue_setPitch(this, ch, pitch->value()*2, true); +} + + +/* ------------------------------------------------------------------ */ + + +void gdEditor::__cb_zoomIn() +{ + waveTools->waveform->setZoom(-1); + waveTools->redraw(); +} + + +/* ------------------------------------------------------------------ */ + + +void gdEditor::__cb_zoomOut() +{ + waveTools->waveform->setZoom(0); + waveTools->redraw(); +} + + +/* ------------------------------------------------------------------ */ + + +void gdEditor::__cb_changeGrid() +{ + waveTools->waveform->setGridLevel(atoi(grid->text())); +} diff --git a/src/gd_editor.h b/src/gd_editor.h new file mode 100644 index 0000000..9ae3266 --- /dev/null +++ b/src/gd_editor.h @@ -0,0 +1,116 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * gd_editor + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + +#ifndef GD_EDITOR_H +#define GD_EDITOR_H + +#include +#include +#include +#include "ge_window.h" + + +class gdEditor : public gWindow { + +private: + + static void cb_setChanPos (Fl_Widget *w, void *p); + static void cb_resetStartEnd (Fl_Widget *w, void *p); + static void cb_setVolume (Fl_Widget *w, void *p); + static void cb_setVolumeNum (Fl_Widget *w, void *p); + 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); + static void cb_panning (Fl_Widget *w, void *p); + static void cb_reload (Fl_Widget *w, void *p); + 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); + 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); + inline void __cb_setChanPos(); + inline void __cb_resetStartEnd(); + inline void __cb_setVolume(); + inline void __cb_setVolumeNum(); + inline void __cb_setBoost(); + inline void __cb_setBoostNum(); + inline void __cb_normalize(); + inline void __cb_panning(); + inline void __cb_reload(); + 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(); + inline void __cb_zoomIn(); + inline void __cb_zoomOut(); + inline void __cb_changeGrid(); + inline void __cb_enableSnap(); + +public: + + gdEditor(class SampleChannel *ch); + ~gdEditor(); + + class gClick *zoomIn; + class gClick *zoomOut; + class gWaveTools *waveTools; + class gInput *chanStart; + class gInput *chanEnd; + class gClick *resetStartEnd; + class gDial *volume; + class gInput *volumeNum; + class gDial *boost; + class gInput *boostNum; + class gClick *normalize; + class gDial *pan; + class gInput *panNum; + class gClick *reload; + class gDial *pitch; + class gInput *pitchNum; + class gClick *pitchToBar; + class gClick *pitchToSong; + class gClick *pitchHalf; + class gClick *pitchDouble; + class gClick *pitchReset; + class gClick *close; + class gChoice *grid; + class gCheck *snap; + + class SampleChannel *ch; +}; + +#endif diff --git a/src/gd_keyGrabber.cpp b/src/gd_keyGrabber.cpp new file mode 100644 index 0000000..469adf0 --- /dev/null +++ b/src/gd_keyGrabber.cpp @@ -0,0 +1,144 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * gd_keyGrabber + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include "gd_keyGrabber.h" +#include "gg_keyboard.h" +#include "ge_mixed.h" +#include "gd_config.h" +#include "ge_channel.h" +#include "gd_mainWindow.h" +#include "gui_utils.h" +#include "conf.h" +#include "channel.h" +#include "sampleChannel.h" +#include "midiChannel.h" +#include "log.h" + + +extern Conf G_Conf; +extern gdMainWindow *mainWin; + + +gdKeyGrabber::gdKeyGrabber(Channel *ch) + : gWindow(300, 126, "Key configuration"), ch(ch) +{ + set_modal(); + text = new gBox(8, 8, 284, 80, ""); + clear = new gClick(w()-88, text->y()+text->h()+8, 80, 20, "Clear"); + cancel = new gClick(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) +{ + char tmp[2]; sprintf(tmp, "%c", key); + ch->guiChannel->button->copy_label(tmp); + ch->key = key; +} + +/* -------------------------------------------------------------------------- */ + + +void gdKeyGrabber::updateText(int key) +{ + char tmp2[64]; + if (key != 0) + sprintf(tmp2, "Press a key.\n\nCurrent binding: %c", key); + else + sprintf(tmp2, "Press a key.\n\nCurrent binding: [none]"); + text->copy_label(tmp2); +} + + +/* -------------------------------------------------------------------------- */ + + +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 != ' ') + { + gLog("set key '%c' (%d) for channel %d\n", x, x, ch->index); + setButtonLabel(x); + updateText(x); + break; + } + else + gLog("invalid key\n"); + } + } + return(ret); +} diff --git a/src/gd_keyGrabber.h b/src/gd_keyGrabber.h new file mode 100644 index 0000000..4e4b2a3 --- /dev/null +++ b/src/gd_keyGrabber.h @@ -0,0 +1,63 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * gd_keyGrabber + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#ifndef GD_KEYGRABBER_H +#define GD_KEYGRABBER_H + + +#include +#include "ge_window.h" + + +class gdKeyGrabber : public gWindow +{ +private: + + class Channel *ch; + + class gBox *text; + class gClick *clear; + class gClick *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(class Channel *ch); + int handle(int e); +}; + +#endif diff --git a/src/gd_mainWindow.cpp b/src/gd_mainWindow.cpp new file mode 100644 index 0000000..45ebccb --- /dev/null +++ b/src/gd_mainWindow.cpp @@ -0,0 +1,672 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * gd_mainWindow + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + + +#ifdef __linux__ + #include // for mkdir +#endif + +#include "gd_mainWindow.h" +#include "gd_warnings.h" +#include "gd_bpmInput.h" +#include "gd_beatsInput.h" +#include "gd_midiGrabber.h" +#include "gg_keyboard.h" +#include "gd_about.h" +#include "gd_config.h" +#include "gd_browser.h" +#include "graphics.h" +#include "glue.h" +#include "mixer.h" +#include "recorder.h" +#include "mixerHandler.h" +#include "pluginHost.h" +#include "channel.h" +#include "sampleChannel.h" +#include "init.h" +#include "patch.h" +#include "conf.h" + +#ifdef WITH_VST +#include "gd_pluginList.h" +#endif + + +extern Mixer G_Mixer; +extern Patch G_Patch; +extern Conf G_Conf; +extern gdMainWindow *mainWin; +extern bool G_quit; +extern bool G_audio_status; + +#if defined(WITH_VST) +extern PluginHost G_PluginHost; +#endif + + +gdMainWindow::gdMainWindow(int W, int H, const char *title, int argc, char **argv) + : gWindow(W, H, title) +{ + Fl::visible_focus(0); + Fl::background(25, 25, 25); + Fl::set_boxtype(G_BOX, gDrawBox, 1, 1, 2, 2); // custom box G_BOX + + size_range(GUI_WIDTH, GUI_HEIGHT); + + menu = new gMenu(8, -1); + inOut = new gInOut(408, 8); + controller = new gController(8, 39); + timing = new gTiming(632, 39); + beatMeter = new gBeatMeter(100, 83, 609, 20); + keyboard = new gKeyboard(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(menu); + zone1->resizable(new Fl_Box(300, 8, 80, 20)); + zone1->add(inOut); + + /* zone 2 - controller and timing tools */ + + Fl_Group *zone2 = new Fl_Group(8, controller->y(), W-16, controller->h()); + zone2->add(controller); + zone2->resizable(new Fl_Box(controller->x()+controller->w()+4, zone2->y(), 80, 20)); + zone2->add(timing); + + /* 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) { mainWin->__cb_endprogram(); } + + +/* ------------------------------------------------------------------ */ + + +void gdMainWindow::__cb_endprogram() +{ + if (!gdConfirmWin("Warning", "Quit Giada: are you sure?")) + return; + init_shutdown(); + hide(); + delete this; +} + + +/* ------------------------------------------------------------------ */ +/* ------------------------------------------------------------------ */ +/* ------------------------------------------------------------------ */ + + +gInOut::gInOut(int x, int y) + : Fl_Group(x, y, 394, 20) +{ + begin(); + +#if defined(WITH_VST) + masterFxIn = new gFxButton (x, y, 20, 20, fxOff_xpm, fxOn_xpm); + inVol = new gDial (masterFxIn->x()+masterFxIn->w()+4, y, 20, 20); + inMeter = new gSoundMeter(inVol->x()+inVol->w()+4, y+5, 140, 10); + inToOut = new gClick (inMeter->x()+inMeter->w()+4, y+5, 10, 10, ""); + outMeter = new gSoundMeter(inToOut->x()+inToOut->w()+4, y+5, 140, 10); + outVol = new gDial (outMeter->x()+outMeter->w()+4, y, 20, 20); + masterFxOut = new gFxButton (outVol->x()+outVol->w()+4, y, 20, 20, fxOff_xpm, fxOn_xpm); +#else + inVol = new gDial (x+62, y, 20, 20); + inMeter = new gSoundMeter(inVol->x()+inVol->w()+4, y+5, 140, 10); + outMeter = new gSoundMeter(inMeter->x()+inMeter->w()+4, y+5, 140, 10); + outVol = new gDial (outMeter->x()+outMeter->w()+4, y, 20, 20); +#endif + + end(); + + resizable(NULL); // don't resize any widget + + outVol->callback(cb_outVol, (void*)this); + outVol->value(G_Mixer.outVol); + inVol->callback(cb_inVol, (void*)this); + inVol->value(G_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 gInOut::cb_outVol (Fl_Widget *v, void *p) { ((gInOut*)p)->__cb_outVol(); } +void gInOut::cb_inVol (Fl_Widget *v, void *p) { ((gInOut*)p)->__cb_inVol(); } +#ifdef WITH_VST +void gInOut::cb_masterFxOut(Fl_Widget *v, void *p) { ((gInOut*)p)->__cb_masterFxOut(); } +void gInOut::cb_masterFxIn (Fl_Widget *v, void *p) { ((gInOut*)p)->__cb_masterFxIn(); } +void gInOut::cb_inToOut (Fl_Widget *v, void *p) { ((gInOut*)p)->__cb_inToOut(); } +#endif + + +/* ------------------------------------------------------------------ */ + + +void gInOut::__cb_outVol() +{ + glue_setOutVol(outVol->value()); +} + + +/* ------------------------------------------------------------------ */ + + +void gInOut::__cb_inVol() +{ + glue_setInVol(inVol->value()); +} + + +/* ------------------------------------------------------------------ */ + + +#ifdef WITH_VST +void gInOut::__cb_masterFxOut() +{ + gu_openSubWindow(mainWin, new gdPluginList(PluginHost::MASTER_OUT), WID_FX_LIST); +} + +void gInOut::__cb_masterFxIn() +{ + gu_openSubWindow(mainWin, new gdPluginList(PluginHost::MASTER_IN), WID_FX_LIST); +} + +void gInOut::__cb_inToOut() +{ + G_Mixer.inToOut = inToOut->value(); +} +#endif + + +/* ------------------------------------------------------------------ */ + + +void gInOut::refresh() +{ + outMeter->mixerPeak = G_Mixer.peakOut; + inMeter->mixerPeak = G_Mixer.peakIn; + outMeter->redraw(); + inMeter->redraw(); +} + + +/* ------------------------------------------------------------------ */ +/* ------------------------------------------------------------------ */ +/* ------------------------------------------------------------------ */ + + +gMenu::gMenu(int x, int y) + : Fl_Group(x, y, 300, 20) +{ + begin(); + + file = new gClick(x, y, 70, 21, "file"); + edit = new gClick(file->x()+file->w()+4, y, 70, 21, "edit"); + config = new gClick(edit->x()+edit->w()+4, y, 70, 21, "config"); + about = new gClick(config->x()+config->w()+4, y, 70, 21, "about"); + + end(); + + resizable(NULL); // 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 gMenu::cb_about (Fl_Widget *v, void *p) { ((gMenu*)p)->__cb_about(); } +void gMenu::cb_config(Fl_Widget *v, void *p) { ((gMenu*)p)->__cb_config(); } +void gMenu::cb_file (Fl_Widget *v, void *p) { ((gMenu*)p)->__cb_file(); } +void gMenu::cb_edit (Fl_Widget *v, void *p) { ((gMenu*)p)->__cb_edit(); } + + +/* ------------------------------------------------------------------ */ + + +void gMenu::__cb_about() +{ + gu_openSubWindow(mainWin, new gdAbout(), WID_ABOUT); +} + + +/* ------------------------------------------------------------------ */ + + +void gMenu::__cb_config() +{ + gu_openSubWindow(mainWin, new gdConfig(380, 370), WID_CONFIG); +} + + +/* ------------------------------------------------------------------ */ + + +void gMenu::__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_BOX); + b->textsize(11); + b->textcolor(COLOR_TEXT_0); + b->color(COLOR_BG_0); + + 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) { + gWindow *childWin = new gdBrowser("Load Patch", G_Conf.patchPath, 0, BROWSER_LOAD_PATCH); + gu_openSubWindow(mainWin, childWin, WID_FILE_BROWSER); + return; + } + if (strcmp(m->label(), "Save patch...") == 0) { + if (G_Mixer.hasLogicalSamples() || G_Mixer.hasEditedSamples()) + if (!gdConfirmWin("Warning", "You should save a project in order to store\nyour takes and/or processed samples.")) + return; + gWindow *childWin = new gdBrowser("Save Patch", G_Conf.patchPath, 0, BROWSER_SAVE_PATCH); + gu_openSubWindow(mainWin, childWin, WID_FILE_BROWSER); + return; + } + if (strcmp(m->label(), "Save project...") == 0) { + gWindow *childWin = new gdBrowser("Save Project", G_Conf.patchPath, 0, BROWSER_SAVE_PROJECT); + gu_openSubWindow(mainWin, childWin, WID_FILE_BROWSER); + return; + } + if (strcmp(m->label(), "Quit Giada") == 0) { + mainWin->do_callback(); + return; + } +} + + +/* ------------------------------------------------------------------ */ + + +void gMenu::__cb_edit() +{ + Fl_Menu_Item menu[] = { + {"Clear all samples"}, + {"Clear all actions"}, + {"Remove empty columns"}, + {"Reset to init state"}, + {"Setup global MIDI input..."}, + {0} + }; + + /* clear all actions disabled if no recs, clear all samples disabled + * if no samples. */ + + menu[1].deactivate(); + + for (unsigned i=0; ihasActions) { + menu[1].activate(); + break; + } + for (unsigned i=0; itype == CHANNEL_SAMPLE) + if (((SampleChannel*)G_Mixer.channels.at(i))->wave != NULL) { + menu[0].activate(); + break; + } + + Fl_Menu_Button *b = new Fl_Menu_Button(0, 0, 100, 50); + b->box(G_BOX); + b->textsize(11); + b->textcolor(COLOR_TEXT_0); + b->color(COLOR_BG_0); + + 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; + 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; + mainWin->delSubWindow(WID_ACTION_EDITOR); + glue_clearAllRecs(); + return; + } + if (strcmp(m->label(), "Reset to init state") == 0) { + if (!gdConfirmWin("Warning", "Reset to init state: are you sure?")) + return; + gu_closeAllSubwindows(); + glue_resetToInitState(); + return; + } + if (strcmp(m->label(), "Remove empty columns") == 0) { + mainWin->keyboard->organizeColumns(); + return; + } + if (strcmp(m->label(), "Setup global MIDI input...") == 0) { + gu_openSubWindow(mainWin, new gdMidiGrabberMaster(), 0); + return; + } +} + + +/* ------------------------------------------------------------------ */ +/* ------------------------------------------------------------------ */ +/* ------------------------------------------------------------------ */ + + +gController::gController(int x, int y) + : Fl_Group(x, y, 131, 25) +{ + begin(); + + rewind = new gClick(x, y, 25, 25, "", rewindOff_xpm, rewindOn_xpm); + play = new gClick(rewind->x()+rewind->w()+4, y, 25, 25, "", play_xpm, pause_xpm); + recAction = new gClick(play->x()+play->w()+4, y, 25, 25, "", recOff_xpm, recOn_xpm); + recInput = new gClick(recAction->x()+recAction->w()+4, y, 25, 25, "", inputRecOff_xpm, inputRecOn_xpm); + metronome = new gClick(recInput->x()+recInput->w()+4, y+10, 15, 15, "", metronomeOff_xpm, metronomeOn_xpm); + + end(); + + resizable(NULL); // 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 gController::cb_rewind (Fl_Widget *v, void *p) { ((gController*)p)->__cb_rewind(); } +void gController::cb_play (Fl_Widget *v, void *p) { ((gController*)p)->__cb_play(); } +void gController::cb_recAction(Fl_Widget *v, void *p) { ((gController*)p)->__cb_recAction(); } +void gController::cb_recInput (Fl_Widget *v, void *p) { ((gController*)p)->__cb_recInput(); } +void gController::cb_metronome(Fl_Widget *v, void *p) { ((gController*)p)->__cb_metronome(); } + + +/* ------------------------------------------------------------------ */ + + +void gController::__cb_rewind() +{ + glue_rewindSeq(); +} + + +/* ------------------------------------------------------------------ */ + + +void gController::__cb_play() +{ + glue_startStopSeq(); +} + + +/* ------------------------------------------------------------------ */ + + +void gController::__cb_recAction() +{ + glue_startStopActionRec(); +} + + +/* ------------------------------------------------------------------ */ + + +void gController::__cb_recInput() +{ + glue_startStopInputRec(); +} + + +/* ------------------------------------------------------------------ */ + + +void gController::__cb_metronome() +{ + glue_startStopMetronome(); +} + + +/* ------------------------------------------------------------------ */ + + +void gController::updatePlay(int v) +{ + play->value(v); + play->redraw(); +} + + +/* ------------------------------------------------------------------ */ + + +void gController::updateMetronome(int v) +{ + metronome->value(v); + metronome->redraw(); +} + + +/* ------------------------------------------------------------------ */ + + +void gController::updateRecInput(int v) +{ + recInput->value(v); + recInput->redraw(); +} + + +/* ------------------------------------------------------------------ */ + + +void gController::updateRecAction(int v) +{ + recAction->value(v); + recAction->redraw(); +} + + +/* ------------------------------------------------------------------ */ +/* ------------------------------------------------------------------ */ +/* ------------------------------------------------------------------ */ + + +gTiming::gTiming(int x, int y) + : Fl_Group(x, y, 170, 15) +{ + begin(); + + quantizer = new gChoice(x, y, 40, 15, "", false); + bpm = new gClick (quantizer->x()+quantizer->w()+4, y, 40, 15); + meter = new gClick (bpm->x()+bpm->w()+8, y, 40, 15, "4/1"); + multiplier = new gClick (meter->x()+meter->w()+4, y, 15, 15, "", beatsMultiplyOff_xpm, beatsMultiplyOn_xpm); + divider = new gClick (multiplier->x()+multiplier->w()+4, y, 15, 15, "÷", beatsDivideOff_xpm, beatsDivideOn_xpm); + + end(); + + resizable(NULL); // don't resize any widget + + char buf[6]; snprintf(buf, 6, "%f", G_Mixer.bpm); + bpm->copy_label(buf); + + bpm->callback(cb_bpm, (void*)this); + meter->callback(cb_meter, (void*)this); + multiplier->callback(cb_multiplier, (void*)this); + divider->callback(cb_divider, (void*)this); + + quantizer->add("off", 0, cb_quantizer, (void*)this); + quantizer->add("1b", 0, cb_quantizer, (void*)this); + quantizer->add("2b", 0, cb_quantizer, (void*)this); + quantizer->add("3b", 0, cb_quantizer, (void*)this); + quantizer->add("4b", 0, cb_quantizer, (void*)this); + quantizer->add("6b", 0, cb_quantizer, (void*)this); + quantizer->add("8b", 0, cb_quantizer, (void*)this); + quantizer->value(0); // "off" by default +} + + +/* ------------------------------------------------------------------ */ + + +void gTiming::cb_bpm (Fl_Widget *v, void *p) { ((gTiming*)p)->__cb_bpm(); } +void gTiming::cb_meter (Fl_Widget *v, void *p) { ((gTiming*)p)->__cb_meter(); } +void gTiming::cb_quantizer (Fl_Widget *v, void *p) { ((gTiming*)p)->__cb_quantizer(); } +void gTiming::cb_multiplier(Fl_Widget *v, void *p) { ((gTiming*)p)->__cb_multiplier(); } +void gTiming::cb_divider (Fl_Widget *v, void *p) { ((gTiming*)p)->__cb_divider(); } + + +/* ------------------------------------------------------------------ */ + + +void gTiming::__cb_bpm() +{ + gu_openSubWindow(mainWin, new gdBpmInput(bpm->label()), WID_BPM); +} + + +/* ------------------------------------------------------------------ */ + + +void gTiming::__cb_meter() +{ + gu_openSubWindow(mainWin, new gdBeatsInput(), WID_BEATS); +} + + +/* ------------------------------------------------------------------ */ + + +void gTiming::__cb_quantizer() +{ + glue_quantize(quantizer->value()); +} + + +/* ------------------------------------------------------------------ */ + + +void gTiming::__cb_multiplier() +{ + glue_beatsMultiply(); +} + + +/* ------------------------------------------------------------------ */ + + +void gTiming::__cb_divider() +{ + glue_beatsDivide(); +} + + +/* ------------------------------------------------------------------ */ + + +void gTiming::setBpm(const char *v) +{ + bpm->copy_label(v); +} + + +void gTiming::setBpm(float v) +{ + char buf[6]; + sprintf(buf, "%.01f", v); // only 1 decimal place (e.g. 120.0) + bpm->copy_label(buf); +} + + +/* ------------------------------------------------------------------ */ + + +void gTiming::setMeter(int beats, int bars) +{ + char buf[8]; + sprintf(buf, "%d/%d", beats, bars); + meter->copy_label(buf); +} diff --git a/src/gd_mainWindow.h b/src/gd_mainWindow.h new file mode 100644 index 0000000..0361d5d --- /dev/null +++ b/src/gd_mainWindow.h @@ -0,0 +1,210 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * gd_mainWindow + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + + +#ifndef GD_MAINWINDOW_H +#define GD_MAINWINDOW_H + + +#include +#include +#include "ge_mixed.h" +#include "ge_window.h" + + +/* ------------------------------------------------------------------ */ + + +class gdMainWindow : public gWindow +{ +private: + + static void cb_endprogram (Fl_Widget *v, void *p); + inline void __cb_endprogram(); + +public: + + class gKeyboard *keyboard; + class gBeatMeter *beatMeter; + class gMenu *menu; + class gInOut *inOut; + class gController *controller; + class gTiming *timing; + + gdMainWindow(int w, int h, const char *title, int argc, char **argv); +}; + + +/* ------------------------------------------------------------------ */ + + +class gInOut : public Fl_Group +{ +private: + + class gSoundMeter *outMeter; + class gSoundMeter *inMeter; + class gBeatMeter *beatMeter; + class gDial *outVol; + class gDial *inVol; +#ifdef WITH_VST + class gFxButton *masterFxOut; + class gFxButton *masterFxIn; + class gClick *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: + + gInOut(int x, int y); + + void refresh(); + + inline void setOutVol(float v) { outVol->value(v); } + inline void setInVol (float v) { inVol->value(v); } +#ifdef WITH_VST + inline void setMasterFxOutFull(bool v) { masterFxOut->full = v; masterFxOut->redraw(); } + inline void setMasterFxInFull(bool v) { masterFxIn->full = v; masterFxIn->redraw(); } +#endif +}; + + +/* ------------------------------------------------------------------ */ + + +class gMenu : public Fl_Group +{ +private: + + class gClick *file; + class gClick *edit; + class gClick *config; + class gClick *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: + + gMenu(int x, int y); +}; + + +/* ------------------------------------------------------------------ */ + + +class gController : public Fl_Group +{ +private: + + class gClick *rewind; + class gClick *play; + class gClick *recAction; + class gClick *recInput; + class gClick *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: + + gController(int x, int y); + + void updatePlay (int v); + void updateMetronome(int v); + void updateRecInput (int v); + void updateRecAction(int v); +}; + + +/* ------------------------------------------------------------------ */ + + +class gTiming : public Fl_Group +{ +private: + + class gClick *bpm; + class gClick *meter; + class gChoice *quantizer; + class gClick *multiplier; + class gClick *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: + + gTiming(int x, int y); + + void setBpm(const char *v); + void setBpm(float v); + void setMeter(int beats, int bars); +}; + + +#endif diff --git a/src/gd_midiGrabber.cpp b/src/gd_midiGrabber.cpp new file mode 100644 index 0000000..18a9c3c --- /dev/null +++ b/src/gd_midiGrabber.cpp @@ -0,0 +1,261 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * gd_midiGrabber + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + + +#include "gd_midiGrabber.h" +#include "ge_mixed.h" +#include "gui_utils.h" +#include "kernelMidi.h" +#include "conf.h" +#include "sampleChannel.h" +#include "log.h" + + +extern Conf G_Conf; + + +gdMidiGrabber::gdMidiGrabber(int w, int h, const char *title) + : gWindow(w, h, title) +{ +} + + +/* ------------------------------------------------------------------ */ + + +gdMidiGrabber::~gdMidiGrabber() { + kernelMidi::stopMidiLearn(); +} + + +/* ------------------------------------------------------------------ */ + + +void gdMidiGrabber::stopMidiLearn(gLearner *learner) { + kernelMidi::stopMidiLearn(); + learner->updateValue(); +} + + +/* ------------------------------------------------------------------ */ + + +void gdMidiGrabber::__cb_learn(uint32_t *param, uint32_t msg, gLearner *l) { + *param = msg; + stopMidiLearn(l); + gLog("[gdMidiGrabber] MIDI learn done - message=0x%X\n", msg); +} + + +/* ------------------------------------------------------------------ */ + + +void gdMidiGrabber::cb_learn(uint32_t msg, void *d) { + cbData *data = (cbData*) d; + gdMidiGrabber *grabber = (gdMidiGrabber*) data->grabber; + gLearner *learner = data->learner; + uint32_t *param = learner->param; + grabber->__cb_learn(param, msg, learner); + free(data); +} + + +/* ------------------------------------------------------------------ */ + + +void gdMidiGrabber::cb_close(Fl_Widget *w, void *p) { ((gdMidiGrabber*)p)->__cb_close(); } + + +/* ------------------------------------------------------------------ */ + + +void gdMidiGrabber::__cb_close() { + do_callback(); +} + + +/* ------------------------------------------------------------------ */ +/* ------------------------------------------------------------------ */ +/* ------------------------------------------------------------------ */ + + +gdMidiGrabberChannel::gdMidiGrabberChannel(Channel *ch) + : gdMidiGrabber(300, 206, "MIDI Input Setup"), + ch(ch) +{ + char title[64]; + sprintf(title, "MIDI Input Setup (channel %d)", ch->index+1); + label(title); + + set_modal(); + + enable = new gCheck(8, 8, 120, 20, "enable MIDI input"); + new gLearner(8, 30, w()-16, "key press", cb_learn, &ch->midiInKeyPress); + new gLearner(8, 54, w()-16, "key release", cb_learn, &ch->midiInKeyRel); + new gLearner(8, 78, w()-16, "key kill", cb_learn, &ch->midiInKill); + new gLearner(8, 102, w()-16, "mute", cb_learn, &ch->midiInMute); + new gLearner(8, 126, w()-16, "solo", cb_learn, &ch->midiInSolo); + new gLearner(8, 150, w()-16, "volume", cb_learn, &ch->midiInVolume); + int yy = 178; + + if (ch->type == CHANNEL_SAMPLE) { + size(300, 254); + new gLearner(8, 174, w()-16, "pitch", cb_learn, &((SampleChannel*)ch)->midiInPitch); + new gLearner(8, 198, w()-16, "read actions", cb_learn, &((SampleChannel*)ch)->midiInReadActions); + yy = 226; + } + + ok = new gButton(w()-88, yy, 80, 20, "Ok"); + ok->callback(cb_close, (void*)this); + + enable->value(ch->midiIn); + enable->callback(cb_enable, (void*)this); + + gu_setFavicon(this); + show(); +} + + +/* ------------------------------------------------------------------ */ + + +void gdMidiGrabberChannel::cb_enable(Fl_Widget *w, void *p) { ((gdMidiGrabberChannel*)p)->__cb_enable(); } + + +/* ------------------------------------------------------------------ */ + + +void gdMidiGrabberChannel::__cb_enable() { + ch->midiIn = enable->value(); +} + + +/* ------------------------------------------------------------------ */ +/* ------------------------------------------------------------------ */ +/* ------------------------------------------------------------------ */ + + +gdMidiGrabberMaster::gdMidiGrabberMaster() + : gdMidiGrabber(300, 256, "MIDI Input Setup (global)") +{ + set_modal(); + + new gLearner(8, 8, w()-16, "rewind", &cb_learn, &G_Conf.midiInRewind); + new gLearner(8, 32, w()-16, "play/stop", &cb_learn, &G_Conf.midiInStartStop); + new gLearner(8, 56, w()-16, "action recording", &cb_learn, &G_Conf.midiInActionRec); + new gLearner(8, 80, w()-16, "input recording", &cb_learn, &G_Conf.midiInInputRec); + new gLearner(8, 104, w()-16, "metronome", &cb_learn, &G_Conf.midiInMetronome); + new gLearner(8, 128, w()-16, "input volume", &cb_learn, &G_Conf.midiInVolumeIn); + new gLearner(8, 152, w()-16, "output volume", &cb_learn, &G_Conf.midiInVolumeOut); + new gLearner(8, 176, w()-16, "sequencer ×2", &cb_learn, &G_Conf.midiInBeatDouble); + new gLearner(8, 200, w()-16, "sequencer ÷2", &cb_learn, &G_Conf.midiInBeatHalf); + ok = new gButton(w()-88, 228, 80, 20, "Ok"); + + ok->callback(cb_close, (void*)this); + + gu_setFavicon(this); + show(); +} + + +/* ------------------------------------------------------------------ */ +/* ------------------------------------------------------------------ */ +/* ------------------------------------------------------------------ */ + + +gLearner::gLearner(int X, int Y, int W, const char *l, kernelMidi::cb_midiLearn *cb, uint32_t *param) + : Fl_Group(X, Y, W, 20), + callback(cb), + param (param) +{ + begin(); + text = new gBox(x(), y(), 156, 20, l); + value = new gClick(text->x()+text->w()+4, y(), 80, 20, "(not set)"); + button = new gButton(value->x()+value->w()+4, y(), 40, 20, "learn"); + end(); + + text->box(G_BOX); + text->align(FL_ALIGN_LEFT | FL_ALIGN_INSIDE); + + value->box(G_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 gLearner::updateValue() { + char buf[16]; + if (*param != 0x0) + snprintf(buf, 9, "0x%X", *param); + else + snprintf(buf, 16, "(not set)"); + value->copy_label(buf); + button->value(0); +} + + +/* ------------------------------------------------------------------ */ + + +void gLearner::cb_button(Fl_Widget *v, void *p) { ((gLearner*)p)->__cb_button(); } +void gLearner::cb_value(Fl_Widget *v, void *p) { ((gLearner*)p)->__cb_value(); } + + +/* ------------------------------------------------------------------ */ + + +void gLearner::__cb_value() { + if (Fl::event_button() == FL_RIGHT_MOUSE) { + *param = 0x0; + updateValue(); + } + /// TODO - elif (LEFT_MOUSE) : insert values by hand +} + + +/* ------------------------------------------------------------------ */ + + +void gLearner::__cb_button() { + if (button->value() == 1) { + cbData *data = (cbData*) malloc(sizeof(cbData)); + data->grabber = (gdMidiGrabber*) parent(); // parent = gdMidiGrabberChannel + data->learner = this; + kernelMidi::startMidiLearn(callback, (void*)data); + } + else + kernelMidi::stopMidiLearn(); +} + diff --git a/src/gd_midiGrabber.h b/src/gd_midiGrabber.h new file mode 100644 index 0000000..4da9e72 --- /dev/null +++ b/src/gd_midiGrabber.h @@ -0,0 +1,150 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * gd_midiGrabber + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + + +#ifndef GD_MIDIGRABBER_H +#define GD_MIDIGRABBER_H + + +#include "ge_window.h" +#include "kernelMidi.h" +#include "utils.h" +#include "ge_mixed.h" + + +class gLearner : public Fl_Group { + +private: + + /* callback + * cb to pass to kernelMidi. Requires two parameters: + * uint32_t msg - MIDI message + * void *data - extra data */ + + kernelMidi::cb_midiLearn *callback; + + class gBox *text; + class gClick *value; + class gButton *button; + + static void cb_button(Fl_Widget *v, void *p); + static void cb_value (Fl_Widget *v, void *p); + inline void __cb_button(); + inline void __cb_value(); + +public: + + /* param + * pointer to ch->midiIn[value] */ + + uint32_t *param; + + gLearner(int x, int y, int w, const char *l, kernelMidi::cb_midiLearn *cb, uint32_t *param); + + void updateValue(); +}; + + +/* ------------------------------------------------------------------ */ + + +class gdMidiGrabber : public gWindow { + +protected: + + gClick *ok; + + void stopMidiLearn(gLearner *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, gLearner *l); + + static void cb_close (Fl_Widget *w, void *p); + inline void __cb_close(); + +public: + + gdMidiGrabber(int w, int h, const char *title); + ~gdMidiGrabber(); +}; + + +/* ------------------------------------------------------------------ */ + + +class gdMidiGrabberChannel : public gdMidiGrabber { + +private: + + class Channel *ch; + + gCheck *enable; + + + //gVector items; for future use, with vst parameters + + static void cb_enable (Fl_Widget *w, void *p); + inline void __cb_enable(); + +public: + + gdMidiGrabberChannel(class Channel *ch); +}; + + +/* ------------------------------------------------------------------ */ + + +class gdMidiGrabberMaster : public gdMidiGrabber { + +public: + + gdMidiGrabberMaster(); +}; + + +/* ------------------------------------------------------------------ */ + + +/* cbData + * struct we pass to kernelMidi as extra parameter. Local scope made + * with unnamed namespace. Infos: + * http://stackoverflow.com/questions/4422507/superiority-of-unnamed-namespace-over-static */ + +namespace { + struct cbData { + gdMidiGrabber *grabber; + gLearner *learner; + }; +} + + +#endif diff --git a/src/gd_midiOutputSetup.cpp b/src/gd_midiOutputSetup.cpp new file mode 100644 index 0000000..6cc0024 --- /dev/null +++ b/src/gd_midiOutputSetup.cpp @@ -0,0 +1,127 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * gd_midiOutputSetup + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + + +#include "gd_midiOutputSetup.h" +#include "ge_mixed.h" +#include "gg_keyboard.h" +#include "ge_channel.h" +#include "channel.h" +#include "conf.h" +#include "midiChannel.h" +#include "gui_utils.h" + + +extern Conf G_Conf; + + +gdMidiOutputSetup::gdMidiOutputSetup(MidiChannel *ch) + : gWindow(300, 64, "Midi Output Setup"), ch(ch) +{ + begin(); + enableOut = new gCheck(x()+8, y()+8, 150, 20, "Enable MIDI output"); + chanListOut = new gChoice(w()-108, y()+8, 100, 20); + + save = new gButton(w()-88, chanListOut->y()+chanListOut->h()+8, 80, 20, "Save"); + cancel = new gButton(w()-88-save->w()-8, save->y(), 80, 20, "Cancel"); + end(); + + fillChanMenu(chanListOut); + + if (ch->midiOut) + enableOut->value(1); + else + chanListOut->deactivate(); + + chanListOut->value(ch->midiOutChan); + + enableOut->callback(cb_enableChanList, (void*)this); + save->callback(cb_save, (void*)this); + cancel->callback(cb_cancel, (void*)this); + + set_modal(); + gu_setFavicon(this); + show(); +} + + +/* ------------------------------------------------------------------ */ + + +void gdMidiOutputSetup::cb_save (Fl_Widget *w, void *p) { ((gdMidiOutputSetup*)p)->__cb_save(); } +void gdMidiOutputSetup::cb_cancel (Fl_Widget *w, void *p) { ((gdMidiOutputSetup*)p)->__cb_cancel(); } +void gdMidiOutputSetup::cb_enableChanList(Fl_Widget *w, void *p) { ((gdMidiOutputSetup*)p)->__cb_enableChanList(); } + + +/* ------------------------------------------------------------------ */ + + +void gdMidiOutputSetup::__cb_enableChanList() { + enableOut->value() ? chanListOut->activate() : chanListOut->deactivate(); +} + + +/* ------------------------------------------------------------------ */ + + +void gdMidiOutputSetup::__cb_save() { + ch->midiOut = enableOut->value(); + ch->midiOutChan = chanListOut->value(); + ch->guiChannel->update(); + do_callback(); +} + + +/* ------------------------------------------------------------------ */ + + +void gdMidiOutputSetup::__cb_cancel() { do_callback(); } + + +/* ------------------------------------------------------------------ */ + + +void gdMidiOutputSetup::fillChanMenu(gChoice *m) { + m->add("Channel 1"); + m->add("Channel 2"); + m->add("Channel 3"); + m->add("Channel 4"); + m->add("Channel 5"); + m->add("Channel 6"); + m->add("Channel 7"); + m->add("Channel 8"); + m->add("Channel 9"); + m->add("Channel 10"); + m->add("Channel 11"); + m->add("Channel 12"); + m->add("Channel 13"); + m->add("Channel 14"); + m->add("Channel 15"); + m->add("Channel 16"); + m->value(0); +} diff --git a/src/gd_midiOutputSetup.h b/src/gd_midiOutputSetup.h new file mode 100644 index 0000000..9acf603 --- /dev/null +++ b/src/gd_midiOutputSetup.h @@ -0,0 +1,63 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * gd_midiOutputSetup + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + + +#ifndef GD_MIDI_OUTPUT_SETUP_H +#define GD_MIDI_OUTPUT_SETUP_H + + +#include +#include "ge_window.h" + + +class gdMidiOutputSetup : public gWindow { + +private: + + static void cb_save (Fl_Widget *w, void *p); + static void cb_cancel (Fl_Widget *w, void *p); + static void cb_enableChanList(Fl_Widget *w, void *p); + inline void __cb_save (); + inline void __cb_cancel (); + inline void __cb_enableChanList(); + + void fillChanMenu(class gChoice *m); + + class gCheck *enableOut; + class gChoice *chanListOut; + class gButton *save; + class gButton *cancel; + + class MidiChannel *ch; + +public: + + gdMidiOutputSetup(class MidiChannel *ch); +}; + +#endif diff --git a/src/gd_pluginList.cpp b/src/gd_pluginList.cpp new file mode 100644 index 0000000..ade532d --- /dev/null +++ b/src/gd_pluginList.cpp @@ -0,0 +1,393 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * gd_pluginList + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#ifdef WITH_VST + +#include "gd_pluginList.h" +#include "gd_pluginWindow.h" +#include "gd_pluginWindowGUI.h" +#include "gd_browser.h" +#include "gd_mainWindow.h" +#include "ge_mixed.h" +#include "gui_utils.h" +#include "utils.h" +#include "conf.h" +#include "graphics.h" +#include "pluginHost.h" +#include "mixer.h" +#include "channel.h" +#include "ge_channel.h" + + +extern Conf G_Conf; +extern PluginHost G_PluginHost; +extern gdMainWindow *mainWin; + + +gdPluginList::gdPluginList(int stackType, Channel *ch) + : gWindow(468, 204), ch(ch), stackType(stackType) +{ + + if (G_Conf.pluginListX) + resize(G_Conf.pluginListX, G_Conf.pluginListY, w(), h()); + + list = new Fl_Scroll(8, 8, 476, 188); + list->type(Fl_Scroll::VERTICAL); + list->scrollbar.color(COLOR_BG_0); + list->scrollbar.selection_color(COLOR_BG_1); + list->scrollbar.labelcolor(COLOR_BD_1); + list->scrollbar.slider(G_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 { + char tmp[32]; + sprintf(tmp, "Channel %d Plugins", ch->index+1); + copy_label(tmp); + } + + gu_setFavicon(this); + show(); +} + + +/* -------------------------------------------------------------------------- */ + + +gdPluginList::~gdPluginList() { + G_Conf.pluginListX = x(); + G_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). */ + + gWindow *child = (gWindow*) v; + if (child->getParent() != NULL) + (child->getParent())->delSubWindow(child); + + /* finally refresh plugin list: void *p is a pointer to gdPluginList. + * This callback works even when you click 'x' to close the window... + * well, who cares */ + + ((gdPluginList*)p)->refreshList(); + ((gdPluginList*)p)->redraw(); +} + + +/* -------------------------------------------------------------------------- */ + + +void gdPluginList::__cb_addPlugin() { + + /* the usual callback that gWindow 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 gdBrowser. It does exactly what we need. */ + + gdBrowser *b = new gdBrowser("Browse Plugin", G_Conf.pluginPath, ch, BROWSER_LOAD_PLUGIN, stackType); + addSubWindow(b); + b->callback(cb_refreshList, (void*)this); // 'this' refers to gdPluginList + +} + + +/* -------------------------------------------------------------------------- */ + + +void gdPluginList::refreshList() { + + /* delete the previous list */ + + list->clear(); + list->scroll_to(0, 0); + + /* add new buttons, as many as the plugin in pluginHost::stack + 1, + * the 'add new' button. Warning: if ch == NULL we are working with + * master in/master out stacks. */ + + int numPlugins = G_PluginHost.countPlugins(stackType, ch); + int i = 0; + + while (ix(), list->y()-list->yposition()+(i*24), 800); + list->add(gdp); + i++; + } + + int addPlugY = numPlugins == 0 ? 90 : list->y()-list->yposition()+(i*24); + addPlugin = new gClick(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) { + mainWin->inOut->setMasterFxOutFull(G_PluginHost.countPlugins(stackType, ch) > 0); + } + else + if (stackType == PluginHost::MASTER_IN) { + mainWin->inOut->setMasterFxInFull(G_PluginHost.countPlugins(stackType, ch) > 0); + } + else { + ch->guiChannel->fx->full = G_PluginHost.countPlugins(stackType, ch) > 0; + ch->guiChannel->fx->redraw(); + } +} + + +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ + + +gdPlugin::gdPlugin(gdPluginList *gdp, Plugin *p, int X, int Y, int W) + : Fl_Group(X, Y, W, 20), pParent(gdp), pPlugin (p) +{ + begin(); + button = new gButton(8, y(), 220, 20); + program = new gChoice(button->x()+button->w()+4, y(), 132, 20); + bypass = new gButton(program->x()+program->w()+4, y(), 20, 20); + shiftUp = new gButton(bypass->x()+bypass->w()+4, y(), 20, 20, "", fxShiftUpOff_xpm, fxShiftUpOn_xpm); + shiftDown = new gButton(shiftUp->x()+shiftUp->w()+4, y(), 20, 20, "", fxShiftDownOff_xpm, fxShiftDownOn_xpm); + remove = new gButton(shiftDown->x()+shiftDown->w()+4, y(), 20, 20, "", fxRemoveOff_xpm, fxRemoveOn_xpm); + end(); + + if (pPlugin->status != 1) { // bad state + char name[256]; + sprintf(name, "* %s *", gBasename(pPlugin->pathfile).c_str()); + button->copy_label(name); + } + else { + char name[256]; + pPlugin->getProduct(name); + if (strcmp(name, " ")==0) + pPlugin->getName(name); + + button->copy_label(name); + button->callback(cb_openPluginWindow, (void*)this); + + program->callback(cb_setProgram, (void*)this); + + /* loading vst programs */ + /* FIXME - max programs = 128 (unknown source) */ + + for (int i=0; i<64; i++) { + char out[kVstMaxProgNameLen]; + pPlugin->getProgramName(i, out); + for (int j=0; j 0) + program->add(out); + } + if (program->size() == 0) { + program->add("-- no programs --\0"); + program->deactivate(); + } + if (pPlugin->getProgram() == -1) + program->value(0); + else + program->value(pPlugin->getProgram()); + + bypass->callback(cb_setBypass, (void*)this); + bypass->type(FL_TOGGLE_BUTTON); + bypass->value(pPlugin->bypass ? 0 : 1); + } + + shiftUp->callback(cb_shiftUp, (void*)this); + shiftDown->callback(cb_shiftDown, (void*)this); + remove->callback(cb_removePlugin, (void*)this); +} + + +/* -------------------------------------------------------------------------- */ + + +void gdPlugin::cb_removePlugin (Fl_Widget *v, void *p) { ((gdPlugin*)p)->__cb_removePlugin(); } +void gdPlugin::cb_openPluginWindow(Fl_Widget *v, void *p) { ((gdPlugin*)p)->__cb_openPluginWindow(); } +void gdPlugin::cb_setBypass (Fl_Widget *v, void *p) { ((gdPlugin*)p)->__cb_setBypass(); } +void gdPlugin::cb_shiftUp (Fl_Widget *v, void *p) { ((gdPlugin*)p)->__cb_shiftUp(); } +void gdPlugin::cb_shiftDown (Fl_Widget *v, void *p) { ((gdPlugin*)p)->__cb_shiftDown(); } +void gdPlugin::cb_setProgram (Fl_Widget *v, void *p) { ((gdPlugin*)p)->__cb_setProgram(); } + + +/* -------------------------------------------------------------------------- */ + + +void gdPlugin::__cb_shiftUp() { + + /*nothing to do if there's only one plugin */ + + if (G_PluginHost.countPlugins(pParent->stackType, pParent->ch) == 1) + return; + + int pluginIndex = G_PluginHost.getPluginIndex(pPlugin->getId(), pParent->stackType, pParent->ch); + + if (pluginIndex == 0) // first of the stack, do nothing + return; + + G_PluginHost.swapPlugin(pluginIndex, pluginIndex-1, pParent->stackType, pParent->ch); + pParent->refreshList(); +} + + +/* -------------------------------------------------------------------------- */ + + +void gdPlugin::__cb_shiftDown() { + + /*nothing to do if there's only one plugin */ + + if (G_PluginHost.countPlugins(pParent->stackType, pParent->ch) == 1) + return; + + unsigned pluginIndex = G_PluginHost.getPluginIndex(pPlugin->getId(), pParent->stackType, pParent->ch); + unsigned stackSize = (G_PluginHost.getStack(pParent->stackType, pParent->ch))->size; + + if (pluginIndex == stackSize-1) // last one in the stack, do nothing + return; + + G_PluginHost.swapPlugin(pluginIndex, pluginIndex+1, pParent->stackType, pParent->ch); + pParent->refreshList(); +} + + +/* -------------------------------------------------------------------------- */ + + +void gdPlugin::__cb_removePlugin() { + + /* os x hack: show window before deleting it */ + +#ifdef __APPLE__ + gdPluginWindowGUImac* w = (gdPluginWindowGUImac*) pParent->getChild(pPlugin->getId()+1); + if (w) + w->show(); +#endif + + /* any subwindow linked to the plugin must be destroyed */ + + pParent->delSubWindow(pPlugin->getId()+1); + G_PluginHost.freePlugin(pPlugin->getId(), pParent->stackType, pParent->ch); + pParent->refreshList(); +} + + +/* -------------------------------------------------------------------------- */ + + +void gdPlugin::__cb_openPluginWindow() { + + /* the new pluginWindow has id = id_plugin + 1, because id=0 is reserved + * for the window 'add plugin'. */ + + /* TODO - at the moment you can open a window for each plugin in the stack. + * This is not consistent with the rest of the gui. You can avoid this by + * calling + * + * gu_openSubWindow(this, new gdPluginWindow(pPlugin), WID_FX); + * + * instead of the following code. + * + * EDIT 2 - having only 1 plugin window would be very uncomfortable */ + + if (!pParent->hasWindow(pPlugin->getId()+1)) { + gWindow *w; + if (pPlugin->hasGui()) +#ifdef __APPLE__ + w = new gdPluginWindowGUImac(pPlugin); +#else + w = new gdPluginWindowGUI(pPlugin); +#endif + else + w = new gdPluginWindow(pPlugin); + w->setId(pPlugin->getId()+1); + pParent->addSubWindow(w); + } +} + + +/* -------------------------------------------------------------------------- */ + + +void gdPlugin::__cb_setBypass() { + pPlugin->bypass = !pPlugin->bypass; +} + + +/* -------------------------------------------------------------------------- */ + + +void gdPlugin::__cb_setProgram() { + pPlugin->setProgram(program->value()); +} + + +#endif // #ifdef WITH_VST diff --git a/src/gd_pluginList.h b/src/gd_pluginList.h new file mode 100644 index 0000000..33ac4a1 --- /dev/null +++ b/src/gd_pluginList.h @@ -0,0 +1,107 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * gd_pluginList + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#ifdef WITH_VST + +#ifndef __GD_PLUGINLIST_H__ +#define __GD_PLUGINLIST_H__ + +#include +#include +#include "ge_window.h" + + +class gdPluginList : public gWindow { + +private: + + class gClick *addPlugin; + Fl_Scroll *list; + + //gVector subWindows; + + static void cb_addPlugin (Fl_Widget *v, void *p); + inline void __cb_addPlugin (); + +public: + + class Channel *ch; // ch == NULL ? masterOut + int stackType; + + gdPluginList(int stackType, class Channel *ch=NULL); + ~gdPluginList(); + + /* special callback, passed to browser. When closed (i.e. plugin + * has been selected) the same browser will refresh this window. */ + + static void cb_refreshList(Fl_Widget*, void*); + + void refreshList(); +}; + + +/* -------------------------------------------------------------------------- */ + + +class gdPlugin : public Fl_Group { + +private: + + class gdPluginList *pParent; + class Plugin *pPlugin; + + static void cb_removePlugin (Fl_Widget *v, void *p); + static void cb_openPluginWindow (Fl_Widget *v, void *p); + static void cb_setBypass (Fl_Widget *v, void *p); + static void cb_shiftUp (Fl_Widget *v, void *p); + static void cb_shiftDown (Fl_Widget *v, void *p); + static void cb_setProgram (Fl_Widget *v, void *p); + inline void __cb_removePlugin (); + inline void __cb_openPluginWindow (); + inline void __cb_setBypass (); + inline void __cb_shiftUp (); + inline void __cb_shiftDown (); + inline void __cb_setProgram (); + +public: + + class gButton *button; + class gChoice *program; + class gButton *bypass; + class gButton *shiftUp; + class gButton *shiftDown; + class gButton *remove; + + gdPlugin(gdPluginList *gdp, class Plugin *p, int x, int y, int w); + +}; + +#endif + +#endif // #ifdef WITH_VST diff --git a/src/gd_pluginWindow.cpp b/src/gd_pluginWindow.cpp new file mode 100644 index 0000000..f9119c0 --- /dev/null +++ b/src/gd_pluginWindow.cpp @@ -0,0 +1,137 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * gd_pluginWindow + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + + +#ifdef WITH_VST + +#include +#include "gd_pluginWindow.h" +#include "pluginHost.h" +#include "ge_mixed.h" +#include "gui_utils.h" + + +extern PluginHost G_PluginHost; + + +Parameter::Parameter(int id, Plugin *p, int X, int Y, int W) + : Fl_Group(X,Y,W-24,20), id(id), pPlugin(p) +{ + begin(); + + label = new gBox(x(), y(), 60, 20); + char name[kVstMaxParamStrLen]; + pPlugin->getParamName(id, name); + label->copy_label(name); + label->align(FL_ALIGN_LEFT | FL_ALIGN_INSIDE); + + slider = new gSlider(label->x()+label->w()+8, y(), W-200, 20); + slider->value(pPlugin->getParam(id)); + slider->callback(cb_setValue, (void *)this); + + value = new gBox(slider->x()+slider->w()+8, y(), 100, 20); + char disp[kVstMaxParamStrLen]; + char labl[kVstMaxParamStrLen]; + char str [256]; + pPlugin->getParamDisplay(id, disp); + pPlugin->getParamLabel(id, labl); + sprintf(str, "%s %s", disp, labl); + value->copy_label(str); + value->align(FL_ALIGN_LEFT | FL_ALIGN_INSIDE); + value->box(G_BOX); + + resizable(slider); + + end(); +} + + +/* ------------------------------------------------------------------ */ + + +void Parameter::cb_setValue(Fl_Widget *v, void *p) { ((Parameter*)p)->__cb_setValue(); } + + +/* ------------------------------------------------------------------ */ + + +void Parameter::__cb_setValue() { + + pPlugin->setParam(id, slider->value()); + + char disp[256]; + char labl[256]; + char str [256]; + + pPlugin->getParamDisplay(id, disp); + pPlugin->getParamLabel(id, labl); + sprintf(str, "%s %s", disp, labl); + + value->copy_label(str); + value->redraw(); +} + + +/* ------------------------------------------------------------------ */ + + +gdPluginWindow::gdPluginWindow(Plugin *pPlugin) + : gWindow(400, 156), pPlugin(pPlugin) // 350 +{ + set_non_modal(); + + gLiquidScroll *list = new gLiquidScroll(8, 8, w()-16, h()-16); + list->type(Fl_Scroll::VERTICAL_ALWAYS); + list->begin(); + + int numParams = pPlugin->getNumParams(); + for (int i=0; ix(), list->y()+(i*24), list->w()); + list->end(); + + end(); + + char name[256]; + pPlugin->getProduct(name); + if (strcmp(name, " ")==0) + pPlugin->getName(name); + label(name); + + size_range(400, (24*1)+12); + resizable(list); + + gu_setFavicon(this); + show(); + +} + + +gdPluginWindow::~gdPluginWindow() {} + + +#endif // #ifdef WITH_VST diff --git a/src/gd_pluginWindow.h b/src/gd_pluginWindow.h new file mode 100644 index 0000000..03d49bf --- /dev/null +++ b/src/gd_pluginWindow.h @@ -0,0 +1,76 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * gd_pluginWindow + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + +#ifdef WITH_VST + +#ifndef __GD_PLUGINWINDOW_H__ +#define __GD_PLUGINWINDOW_H__ + + +#include +#include +#include "ge_window.h" + + +class gdPluginWindow : public gWindow { + +private: + class Plugin *pPlugin; + +public: + int id; + + gdPluginWindow(Plugin *pPlugin); + ~gdPluginWindow(); +}; + + +/* ------------------------------------------------------------------ */ + + +class Parameter : public Fl_Group { + +private: + int id; + class Plugin *pPlugin; + + static void cb_setValue(Fl_Widget *v, void *p); + inline void __cb_setValue(); + +public: + class gBox *label; + class gSlider *slider; + class gBox *value; + + Parameter(int id, class Plugin *p, int x, int y, int w); +}; + + +#endif + +#endif // #ifdef WITH_VST diff --git a/src/gd_pluginWindowGUI.cpp b/src/gd_pluginWindowGUI.cpp new file mode 100644 index 0000000..6fd0a07 --- /dev/null +++ b/src/gd_pluginWindowGUI.cpp @@ -0,0 +1,196 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * gd_pluginWindowGUI + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + + +#ifdef WITH_VST + + +#include "gd_pluginWindowGUI.h" +#include "pluginHost.h" +#include "ge_mixed.h" +#include "gui_utils.h" +#include "log.h" + + +extern PluginHost G_PluginHost; + + +gdPluginWindowGUI::gdPluginWindowGUI(Plugin *pPlugin) + : gWindow(450, 300), pPlugin(pPlugin) +{ + + /* some effects like to have us get their rect before opening them */ + + ERect *rect; + pPlugin->getRect(&rect); + + gu_setFavicon(this); + set_non_modal(); + resize(x(), y(), pPlugin->getGuiWidth(), pPlugin->getGuiWidth()); + show(); + + gLog("[gdPluginWindowGUI] open window, w=%d h=%d\n", + pPlugin->getGuiWidth(), pPlugin->getGuiWidth()); + + /* Fl::check(): Waits until "something happens" and then returns. It's + * mandatory on linux, otherwise X can't find 'this' window. */ + + Fl::check(); + pPlugin->openGui((void*)fl_xid(this)); + + char name[256]; + pPlugin->getProduct(name); + copy_label(name); + + /* add a pointer to this window to plugin */ + + pPlugin->window = this; + + pPlugin->idle(); +} + + +/* ------------------------------------------------------------------ */ + + +gdPluginWindowGUI::~gdPluginWindowGUI() { + pPlugin->closeGui(); +} + + +/* ------------------------------------------------------------------ */ +/* ------------------------------------------------------------------ */ +/* ------------------------------------------------------------------ */ + + +#if defined(__APPLE__) + + +pascal OSStatus gdPluginWindowGUImac::windowHandler(EventHandlerCallRef ehc, EventRef e, void *data) { + return ((gdPluginWindowGUImac*)data)->__wh(ehc, e); +} + + +/* ------------------------------------------------------------------ */ + + +pascal OSStatus gdPluginWindowGUImac::__wh(EventHandlerCallRef inHandlerCallRef, EventRef inEvent) { + OSStatus result = eventNotHandledErr; // let the Carbon Event Manager close the window + UInt32 eventClass = GetEventClass(inEvent); + UInt32 eventKind = GetEventKind(inEvent); + + switch (eventClass) { + case kEventClassWindow: { + switch (eventKind) { + case kEventWindowClose: { + gLog("[pluginWindowMac] <<< CALLBACK >>> kEventWindowClose for gWindow=%p, window=%p\n", (void*)this, (void*)carbonWindow); + show(); + break; + } + case kEventWindowClosed: { + gLog("[pluginWindowMac] <<< CALLBACK >>> kEventWindowClosed for gWindow=%p, window=%p\n", (void*)this, (void*)carbonWindow); + open = false; + result = noErr; + break; + } + } + break; + } + } + return result; +} + + +/* ------------------------------------------------------------------ */ + + +gdPluginWindowGUImac::gdPluginWindowGUImac(Plugin *pPlugin) + : gWindow(450, 300), pPlugin(pPlugin), carbonWindow(NULL) +{ + + /* some effects like to have us get their rect before opening them */ + + ERect *rect; + pPlugin->getRect(&rect); + + /* window initialization */ + + Rect wRect; + + wRect.top = rect->top; + wRect.left = rect->left; + wRect.bottom = rect->bottom; + wRect.right = rect->right; + + int winclass = kDocumentWindowClass; + int winattr = kWindowStandardHandlerAttribute | + kWindowCloseBoxAttribute | + kWindowCompositingAttribute | + kWindowAsyncDragAttribute; + + // winattr &= GetAvailableWindowAttributes(winclass); // make sure that the window will open + + OSStatus status = CreateNewWindow(winclass, winattr, &wRect, &carbonWindow); + if (status != noErr) { + gLog("[pluginWindowMac] Unable to create window! Status=%d\n", (int) status); + return; + } + else + gLog("[pluginWindowMac] created window=%p\n", (void*)carbonWindow); + + /* install event handler, called when window is closed */ + + static EventTypeSpec eventTypes[] = { + { kEventClassWindow, kEventWindowClose }, + { kEventClassWindow, kEventWindowClosed } + }; + InstallWindowEventHandler(carbonWindow, windowHandler, GetEventTypeCount(eventTypes), eventTypes, this, NULL); + + /* open window, center it, show it and start the handler */ + + pPlugin->openGui((void*)carbonWindow); + RepositionWindow(carbonWindow, NULL, kWindowCenterOnMainScreen); + ShowWindow(carbonWindow); + open = true; +} + + + +/* ------------------------------------------------------------------ */ + + +gdPluginWindowGUImac::~gdPluginWindowGUImac() { + gLog("[pluginWindowMac] [[[ destructor ]]] gWindow=%p deleted, window=%p deleted\n", (void*)this, (void*)carbonWindow); + pPlugin->closeGui(); + if (open) + DisposeWindow(carbonWindow); +} + +#endif + +#endif // #ifdef WITH_VST diff --git a/src/gd_pluginWindowGUI.h b/src/gd_pluginWindowGUI.h new file mode 100644 index 0000000..1675e73 --- /dev/null +++ b/src/gd_pluginWindowGUI.h @@ -0,0 +1,84 @@ + +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * gd_pluginWindowGUI + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + + +#ifdef WITH_VST + + +#ifndef __GD_PLUGINWINDOW_GUI_H__ +#define __GD_PLUGINWINDOW_GUI_H__ + + +#include +#include +#include "ge_window.h" +#if defined(__APPLE__) + #include +#endif + + +class gdPluginWindowGUI : public gWindow { +private: + + class Plugin *pPlugin; + +public: + + gdPluginWindowGUI(Plugin *pPlugin); + ~gdPluginWindowGUI(); +}; + + +/* ------------------------------------------------------------------ */ + +#if defined(__APPLE__) + +class gdPluginWindowGUImac : public gWindow { + +private: + + static pascal OSStatus windowHandler(EventHandlerCallRef ehc, EventRef e, void *data); + inline pascal OSStatus __wh(EventHandlerCallRef ehc, EventRef e); + + class Plugin *pPlugin; + WindowRef carbonWindow; + bool open; + +public: + + gdPluginWindowGUImac(Plugin *pPlugin); + ~gdPluginWindowGUImac(); +}; + +#endif + + +#endif // include guard + +#endif // #ifdef WITH_VST diff --git a/src/gd_warnings.cpp b/src/gd_warnings.cpp new file mode 100644 index 0000000..31a5dbc --- /dev/null +++ b/src/gd_warnings.cpp @@ -0,0 +1,78 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * gd_warnings + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + + +#include "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(); + gBox *box = new gBox(10, 10, 280, 40, c); + gClick *b = new gClick(210, 60, 80, 20, "Close"); + modal->end(); + box->labelsize(11); + 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 gBox(10, 10, 280, 40, msg); + gClick *ok = new gClick(212, 62, 80, 20, "Ok"); + gClick *ko = new gClick(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; +} diff --git a/src/gd_warnings.h b/src/gd_warnings.h new file mode 100644 index 0000000..ca9c633 --- /dev/null +++ b/src/gd_warnings.h @@ -0,0 +1,43 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * gd_warnings + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + +#ifndef GD_WARNINGS_H +#define GD_WARNINGS_H + +#include +#include +#include +#include "ge_mixed.h" +#include "gui_utils.h" + + +void gdAlert(const char *c); + +int gdConfirmWin(const char *title, const char *msg); + +#endif diff --git a/src/ge_actionChannel.cpp b/src/ge_actionChannel.cpp new file mode 100644 index 0000000..a98d394 --- /dev/null +++ b/src/ge_actionChannel.cpp @@ -0,0 +1,676 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ge_actionChannel + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + + +#include +#include "glue.h" +#include "ge_actionChannel.h" +#include "gd_mainWindow.h" +#include "gd_actionEditor.h" +#include "gg_keyboard.h" +#include "conf.h" +#include "channel.h" +#include "sampleChannel.h" +#include "log.h" + + +extern gdMainWindow *mainWin; +extern Mixer G_Mixer; +extern Conf G_Conf; + + +/* ------------------------------------------------------------------ */ + + +gActionChannel::gActionChannel(int x, int y, gdActionEditor *pParent, SampleChannel *ch) + : gActionWidget(x, y, 200, 40, pParent), ch(ch), selected(NULL) +{ + size(pParent->totalWidth, h()); + + /* add actions when the window opens. Their position is zoom-based; + * each frame is / 2 because we don't care about stereo infos. */ + + for (unsigned i=0; ichan == pParent->chan->index) { + + /* don't show actions > than the grey area */ + + if (recorder::frames.at(i) > G_Mixer.totalFrames) + continue; + + /* skip the killchan actions in a singlepress channel. They cannot be recorded + * in such mode, but they can exist if you change from another mode to singlepress */ + + if (ra->type == ACTION_KILLCHAN && ch->mode == SINGLE_PRESS) + continue; + + /* also filter out ACTION_KEYREL: it's up to gAction to find the other piece + * (namely frame_b) */ + + if (ra->type & (ACTION_KEYPRESS | ACTION_KILLCHAN)) { + int ax = x+(ra->frame/pParent->zoom); + gAction *a = new gAction( + ax, // x + y+4, // y + h()-8, // h + ra->frame, // frame_a + i, // n. of recordings + pParent, // pointer to the pParent window + ch, // pointer to SampleChannel + false, // record = false: don't record it, we are just displaying it! + ra->type); // type of action + add(a); + } + } + } + } + end(); // mandatory when you add widgets to a fl_group, otherwise mega malfunctions +} + + +/* ------------------------------------------------------------------ */ + + +gAction *gActionChannel::getSelectedAction() { + for (int i=0; ix(); + int action_w = ((gAction*)child(i))->w(); + if (Fl::event_x() >= action_x && Fl::event_x() <= action_x + action_w) + return (gAction*)child(i); + } + return NULL; +} + + +/* ------------------------------------------------------------------ */ + + +void gActionChannel::updateActions() { + + /* when zooming, don't delete and re-add actions, just MOVE them. This + * function shifts the action by a zoom factor. Those singlepress are + * stretched, as well */ + + gAction *a; + for (int i=0; iframe_a / pParent->zoom); + + if (ch->mode == SINGLE_PRESS) { + int newW = ((a->frame_b - a->frame_a) / pParent->zoom); + if (newW < gAction::MIN_WIDTH) + newW = gAction::MIN_WIDTH; + a->resize(newX, a->y(), newW, a->h()); + } + else + a->resize(newX, a->y(), gAction::MIN_WIDTH, a->h()); + } +} + + +/* ------------------------------------------------------------------ */ + + +void gActionChannel::draw() { + + /* draw basic boundaries (+ beat bars) and hide the unused area. Then + * draw the children (the actions) */ + + baseDraw(); + + /* print label */ + + fl_color(COLOR_BG_1); + fl_font(FL_HELVETICA, 12); + if (active()) + fl_draw("start/stop", x()+4, y(), w(), h(), (Fl_Align) (FL_ALIGN_LEFT | FL_ALIGN_CENTER)); /// FIXME h() is too much! + else + fl_draw("start/stop (disabled)", x()+4, y(), w(), h(), (Fl_Align) (FL_ALIGN_LEFT | FL_ALIGN_CENTER)); /// FIXME h() is too much! + + draw_children(); +} + + +/* ------------------------------------------------------------------ */ + + +int gActionChannel::handle(int e) { + + int ret = Fl_Group::handle(e); + + /* do nothing if the widget is deactivated. It could happen for loopmode + * channels */ + + if (!active()) + return 1; + + switch (e) { + + case FL_DRAG: { + if (selected != NULL) { // if you don't drag an empty area + + /* if onLeftEdge o onRightEdge are true it means that you're resizing + * an action. Otherwise move the widget. */ + + if (selected->onLeftEdge || selected->onRightEdge) { + + /* some checks: a) cannot resize an action < N pixels, b) no beyond zero, + * c) no beyond bar maxwidth. Checks for overlap are done in FL_RELEASE */ + + if (selected->onRightEdge) { + + int aw = Fl::event_x()-selected->x(); + int ah = selected->h(); + + if (Fl::event_x() < selected->x()+gAction::MIN_WIDTH) + aw = gAction::MIN_WIDTH; + else + if (Fl::event_x() > pParent->coverX) + aw = pParent->coverX-selected->x(); + + selected->size(aw, ah); + } + else { + + int ax = Fl::event_x(); + int ay = selected->y(); + int aw = selected->x()-Fl::event_x()+selected->w(); + int ah = selected->h(); + + if (Fl::event_x() < x()) { + ax = x(); + aw = selected->w()+selected->x()-x(); + } + else + if (Fl::event_x() > selected->x()+selected->w()-gAction::MIN_WIDTH) { + ax = selected->x()+selected->w()-gAction::MIN_WIDTH; + aw = gAction::MIN_WIDTH; + } + selected->resize(ax, ay, aw, ah); + } + } + + /* move the widget around */ + + else { + int real_x = Fl::event_x() - actionPickPoint; + if (real_x < x()) // don't go beyond the left border + selected->position(x(), selected->y()); + else + if (real_x+selected->w() > pParent->coverX+x()) // don't go beyond the right border + selected->position(pParent->coverX+x()-selected->w(), selected->y()); + else { + if (pParent->gridTool->isOn()) { + int snpx = pParent->gridTool->getSnapPoint(real_x-x()) + x() -1; + selected->position(snpx, selected->y()); + } + else + selected->position(real_x, selected->y()); + } + } + redraw(); + } + ret = 1; + break; + } + + case FL_PUSH: { + + if (Fl::event_button1()) { + + /* avoid at all costs two overlapping actions. We use 'selected' because + * the selected action can be reused in FL_DRAG, in case you want to move + * it. */ + + selected = getSelectedAction(); + + if (selected == NULL) { + + /* avoid click on grey area */ + + if (Fl::event_x() >= pParent->coverX) { + ret = 1; + break; + } + + /* snap function, if enabled */ + + int ax = Fl::event_x(); + int fx = (ax - x()) * pParent->zoom; + if (pParent->gridTool->isOn()) { + ax = pParent->gridTool->getSnapPoint(ax-x()) + x() -1; + fx = pParent->gridTool->getSnapFrame(ax-x()); + + /* with snap=on an action can fall onto another */ + + if (actionCollides(fx)) { + ret = 1; + break; + } + } + + gAction *a = new gAction( + ax, // x + y()+4, // y + h()-8, // h + fx, // frame_a + recorder::frames.size-1, // n. of actions recorded + pParent, // pParent window pointer + ch, // pointer to SampleChannel + true, // record = true: record it! + pParent->getActionType()); // type of action + add(a); + mainWin->keyboard->setChannelWithActions((gSampleChannel*)ch->guiChannel); // mainWindow update + redraw(); + ret = 1; + } + else { + actionOriginalX = selected->x(); + actionOriginalW = selected->w(); + actionPickPoint = Fl::event_x() - selected->x(); + ret = 1; // for dragging + } + } + else + if (Fl::event_button3()) { + gAction *a = getSelectedAction(); + if (a != NULL) { + a->delAction(); + remove(a); + delete a; + mainWin->keyboard->setChannelWithActions((gSampleChannel*)pParent->chan->guiChannel); + redraw(); + ret = 1; + } + } + break; + } + case FL_RELEASE: { + + if (selected == NULL) { + ret = 1; + break; + } + + /* noChanges = true when you click on an action without doing anything + * (dragging or moving it). */ + + bool noChanges = false; + if (actionOriginalX == selected->x()) + noChanges = true; + if (ch->mode == SINGLE_PRESS && + actionOriginalX+actionOriginalW != selected->x()+selected->w()) + noChanges = false; + + if (noChanges) { + ret = 1; + selected = NULL; + break; + } + + /* step 1: check if the action doesn't overlap with another one. + * In case of overlap the moved action returns to the original X + * value ("actionOriginalX"). */ + + bool overlap = false; + for (int i=0; ix(); + int action_w = ((gAction*)child(i))->w(); + if (ch->mode == SINGLE_PRESS) { + + /* when 2 segments overlap? + * start = the highest value between the two starting points + * end = the lowest value between the two ending points + * if start < end then there's an overlap of end-start pixels. */ + + int start = action_x >= selected->x() ? action_x : selected->x(); + int end = action_x+action_w < selected->x()+selected->w() ? action_x+action_w : selected->x()+selected->w(); + if (start < end) { + selected->resize(actionOriginalX, selected->y(), actionOriginalW, selected->h()); + redraw(); + overlap = true; // one overlap: stop checking + } + } + else { + if (selected->x() == action_x) { + selected->position(actionOriginalX, selected->y()); + redraw(); + overlap = true; // one overlap: stop checking + } + } + } + + /* step 2: no overlap? then update the coordinates of the action, ie + * delete the previous rec and create a new one. + * Anyway the selected action becomes NULL, because when you release + * the mouse button the dragging process ends. */ + + if (!overlap) { + if (pParent->gridTool->isOn()) { + int f = pParent->gridTool->getSnapFrame(selected->absx()); + selected->moveAction(f); + } + else + selected->moveAction(); + } + selected = NULL; + ret = 1; + break; + } + } + + return ret; +} + + +/* ------------------------------------------------------------------ */ + + +bool gActionChannel::actionCollides(int frame) { + + /* if SINGLE_PRESS we check that the tail (frame_b) of the action doesn't + * overlap the head (frame) of the new one. First the general case, yet. */ + + bool collision = false; + + for (int i=0; iframe_a == frame) + collision = true; + + if (ch->mode == SINGLE_PRESS) { + for (int i=0; iframe_b && frame >= c->frame_a) + collision = true; + } + } + + return collision; +} + + +/* ------------------------------------------------------------------ */ +/* ------------------------------------------------------------------ */ +/* ------------------------------------------------------------------ */ + + +const int gAction::MIN_WIDTH = 8; + + +/* ------------------------------------------------------------------ */ + + +/** TODO - index is useless? + * TODO - pass a record::action pointer and let gAction compute values */ + +gAction::gAction(int X, int Y, int H, int frame_a, unsigned index, gdActionEditor *parent, SampleChannel *ch, bool record, char type) +: Fl_Box (X, Y, MIN_WIDTH, H), + selected (false), + index (index), + parent (parent), + ch (ch), + type (type), + frame_a (frame_a), + onRightEdge(false), + onLeftEdge (false) +{ + /* bool 'record' defines how to understand the action. + * record = false: don't record it, just show it. It happens when you + * open the editor with some actions to be shown. + * + * record = true: record it AND show it. It happens when you click on + * an empty area in order to add a new actions. First you record it + * (addAction()) then you show it (FLTK::Draw()) */ + + if (record) + addAction(); + + /* in order to show a singlepress action we must compute the frame_b. We + * do that after the possible recording, otherwise we don't know which + * key_release is associated. */ + + if (ch->mode == SINGLE_PRESS && type == ACTION_KEYPRESS) { + recorder::action *a2 = NULL; + recorder::getNextAction(ch->index, ACTION_KEYREL, frame_a, &a2); + if (a2) { + frame_b = a2->frame; + w((frame_b - frame_a)/parent->zoom); + } + else + gLog("[gActionChannel] frame_b not found! [%d:???]\n", frame_a); + + /* a singlepress action narrower than 8 pixel is useless. So check it. + * Warning: if an action is 8 px narrow, it has no body space to drag + * it. It's up to the user to zoom in and drag it. */ + + if (w() < MIN_WIDTH) + size(MIN_WIDTH, h()); + } +} + + +/* ------------------------------------------------------------------ */ + + +void gAction::draw() { + + int color; + if (selected) /// && gActionChannel !disabled + color = COLOR_BD_1; + else + color = COLOR_BG_2; + + if (ch->mode == SINGLE_PRESS) { + fl_rectf(x(), y(), w(), h(), (Fl_Color) color); + } + else { + if (type == ACTION_KILLCHAN) + fl_rect(x(), y(), MIN_WIDTH, h(), (Fl_Color) color); + else { + fl_rectf(x(), y(), MIN_WIDTH, h(), (Fl_Color) color); + if (type == ACTION_KEYPRESS) + fl_rectf(x()+3, y()+h()-11, 2, 8, COLOR_BD_0); + else + if (type == ACTION_KEYREL) + fl_rectf(x()+3, y()+3, 2, 8, COLOR_BD_0); + } + } + +} + + +/* ------------------------------------------------------------------ */ + + +int gAction::handle(int e) { + + /* ret = 0 sends the event to the parent window. */ + + int ret = 0; + + switch (e) { + + case FL_ENTER: { + selected = true; + ret = 1; + redraw(); + break; + } + case FL_LEAVE: { + fl_cursor(FL_CURSOR_DEFAULT, FL_WHITE, FL_BLACK); + selected = false; + ret = 1; + redraw(); + break; + } + case FL_MOVE: { + + /* handling of the two margins, left & right. 4 pixels are good enough */ + + if (ch->mode == SINGLE_PRESS) { + onLeftEdge = false; + onRightEdge = false; + if (Fl::event_x() >= x() && Fl::event_x() < x()+4) { + onLeftEdge = true; + fl_cursor(FL_CURSOR_WE, FL_WHITE, FL_BLACK); + } + else + if (Fl::event_x() >= x()+w()-4 && Fl::event_x() <= x()+w()) { + onRightEdge = true; + fl_cursor(FL_CURSOR_WE, FL_WHITE, FL_BLACK); + } + else + fl_cursor(FL_CURSOR_DEFAULT, FL_WHITE, FL_BLACK); + } + } + } + + return ret; +} + + +/* ------------------------------------------------------------------ */ + + +void gAction::addAction() { + + /* always check frame parity */ + + if (frame_a % 2 != 0) + frame_a++; + + /* anatomy of an action + * ____[#######]_____ (a) is the left margin, ACTION_KEYPRESS. (b) is + * a b the right margin, the ACTION_KEYREL. This is the + * theory behind the singleshot.press actions; for any other kind the + * (b) is just a graphical and meaningless point. */ + + if (ch->mode == SINGLE_PRESS) { + recorder::rec(parent->chan->index, ACTION_KEYPRESS, frame_a); + recorder::rec(parent->chan->index, ACTION_KEYREL, frame_a+4096); + //gLog("action added, [%d, %d]\n", frame_a, frame_a+4096); + } + else { + recorder::rec(parent->chan->index, parent->getActionType(), frame_a); + //gLog("action added, [%d]\n", frame_a); + } + + recorder::sortActions(); + + index++; // important! +} + + +/* ------------------------------------------------------------------ */ + + +void gAction::delAction() { + + /* if SINGLE_PRESS you must delete both the keypress and the keyrelease + * actions. */ + + if (ch->mode == SINGLE_PRESS) { + recorder::deleteAction(parent->chan->index, frame_a, ACTION_KEYPRESS, false); + recorder::deleteAction(parent->chan->index, frame_b, ACTION_KEYREL, false); + } + else + recorder::deleteAction(parent->chan->index, frame_a, type, false); + + /* restore the initial cursor shape, in case you delete an action and + * the double arrow (for resizing) is displayed */ + + fl_cursor(FL_CURSOR_DEFAULT, FL_WHITE, FL_BLACK); +} + + +/* ------------------------------------------------------------------ */ + + +void gAction::moveAction(int frame_a) { + + /* easy one: delete previous action and record the new ones. As usual, + * SINGLE_PRESS requires two jobs. If frame_a is valid, use that frame + * value. */ + + delAction(); + + if (frame_a != -1) + this->frame_a = frame_a; + else + this->frame_a = xToFrame_a(); + + + /* always check frame parity */ + + if (this->frame_a % 2 != 0) + this->frame_a++; + + recorder::rec(parent->chan->index, type, this->frame_a); + + if (ch->mode == SINGLE_PRESS) { + frame_b = xToFrame_b(); + recorder::rec(parent->chan->index, ACTION_KEYREL, frame_b); + } + + recorder::sortActions(); +} + + +/* ------------------------------------------------------------------ */ + + +int gAction::absx() { + return x() - parent->ac->x(); +} + + +/* ------------------------------------------------------------------ */ + + +int gAction::xToFrame_a() { + return (absx()) * parent->zoom; +} + + +/* ------------------------------------------------------------------ */ + + +int gAction::xToFrame_b() { + return (absx() + w()) * parent->zoom; +} diff --git a/src/ge_actionChannel.h b/src/ge_actionChannel.h new file mode 100644 index 0000000..bd0fe24 --- /dev/null +++ b/src/ge_actionChannel.h @@ -0,0 +1,135 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ge_actionChannel + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + +#ifndef GE_ACTIONCHANNEL_H +#define GE_ACTIONCHANNEL_H + +#include +#include +#include "ge_actionWidget.h" +#include "gui_utils.h" +#include "mixer.h" +#include "recorder.h" + + +class gAction : public Fl_Box { + +private: + + bool selected; + unsigned index; + class gdActionEditor *parent; // pointer to parent (gActionEditor) + class SampleChannel *ch; + char type; // type of action + +public: + gAction(int x, int y, int h, int frame_a, unsigned index, + gdActionEditor *parent, class SampleChannel *ch, bool record, + char type); + void draw(); + int handle(int e); + void addAction(); + void delAction(); + + /* moveAction + * shift the action on the x-axis and update Recorder. If frame_a != -1 + * use the new frame in input (used while snapping) */ + + void moveAction(int frame_a=-1); + + /* absx + * x() is relative to scrolling position. absx() returns the absolute + * x value of the action, from the leftmost edge. */ + + int absx(); + + /* xToFrame_a,b + * return the real frames of x() position */ + + int xToFrame_a(); + int xToFrame_b(); + + int frame_a; // initial frame (KEYPRESS for singlemode.press) + int frame_b; // terminal frame (KEYREL for singlemode.press, null for others) + + bool onRightEdge; + bool onLeftEdge; + + static const int MIN_WIDTH; +}; + + +/* ------------------------------------------------------------------ */ + + +class gActionChannel : public gActionWidget { + +private: + + class SampleChannel *ch; + + /* getSelectedAction + * get the action under the mouse. NULL if nothing found. */ + + gAction *getSelectedAction(); + + /* selected + * pointer to the selected action. Useful when dragging around. */ + + gAction *selected; + + /* actionOriginalX, actionOriginalW + * x and w of the action, when moved. Useful for checking if the action + * overlaps another one: in that case the moved action returns to + * actionOriginalX (and to actionOriginalW if resized). */ + + int actionOriginalX; + int actionOriginalW; + + /* actionPickPoint + * the precise x point in which the action has been picked with the mouse, + * before a dragging action. */ + + int actionPickPoint; + + + /* actionCollides + * true if an action collides with another. Used while adding new points + * with snap active.*/ + + bool actionCollides(int frame); + +public: + gActionChannel(int x, int y, gdActionEditor *pParent, class SampleChannel *ch); + void draw(); + int handle(int e); + void updateActions(); +}; + + +#endif diff --git a/src/ge_actionWidget.cpp b/src/ge_actionWidget.cpp new file mode 100644 index 0000000..a06eab9 --- /dev/null +++ b/src/ge_actionWidget.cpp @@ -0,0 +1,101 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ge_actionWidget + * + * pParent class of any widget inside the action editor. + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + + +#include +#include "ge_actionWidget.h" +#include "gd_actionEditor.h" +#include "mixer.h" +#include "ge_mixed.h" + + +extern Mixer G_Mixer; + + +gActionWidget::gActionWidget(int x, int y, int w, int h, gdActionEditor *pParent) + : Fl_Group(x, y, w, h), pParent(pParent) {} + + +/* ------------------------------------------------------------------ */ + + +gActionWidget::~gActionWidget() {} + + +/* ------------------------------------------------------------------ */ + + +void gActionWidget::baseDraw(bool clear) { + + /* clear the screen */ + + if (clear) + fl_rectf(x(), y(), w(), h(), COLOR_BG_MAIN); + + /* draw the container */ + + fl_color(COLOR_BD_0); + fl_rect(x(), y(), w(), h()); + + /* grid drawing, if > 1 */ + + if (pParent->gridTool->getValue() > 1) { + + fl_color(fl_rgb_color(54, 54, 54)); + fl_line_style(FL_DASH, 0, NULL); + + for (int i=0; i<(int) pParent->gridTool->points.size; i++) { + int px = pParent->gridTool->points.at(i)+x()-1; + fl_line(px, y()+1, px, y()+h()-2); + } + fl_line_style(0); + } + + /* bars and beats drawing */ + + fl_color(COLOR_BD_0); + for (int i=0; i<(int) pParent->gridTool->beats.size; i++) { + int px = pParent->gridTool->beats.at(i)+x()-1; + fl_line(px, y()+1, px, y()+h()-2); + } + + fl_color(COLOR_BG_2); + for (int i=0; i<(int) pParent->gridTool->bars.size; i++) { + int px = pParent->gridTool->bars.at(i)+x()-1; + fl_line(px, y()+1, px, y()+h()-2); + } + + /* cover unused area. Avoid drawing cover if width == 0 (i.e. beats + * are 32) */ + + int coverWidth = pParent->totalWidth-pParent->coverX; + if (coverWidth != 0) + fl_rectf(pParent->coverX+x(), y()+1, coverWidth, h()-2, COLOR_BG_1); +} diff --git a/src/ge_actionWidget.h b/src/ge_actionWidget.h new file mode 100644 index 0000000..554f569 --- /dev/null +++ b/src/ge_actionWidget.h @@ -0,0 +1,53 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ge_actionWidget + * + * parent class of any widget inside the action editor. + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + + +#ifndef __GE_ACTIONWIDGET_H__ +#define __GE_ACTIONWIDGET_H__ + +#include +#include +#include "const.h" + + +class gActionWidget : public Fl_Group { + +protected: + class gdActionEditor *pParent; + void baseDraw(bool clear=true); + +public: + virtual void updateActions() = 0; + + gActionWidget(int x, int y, int w, int h, gdActionEditor *pParent); + ~gActionWidget(); +}; + +#endif diff --git a/src/ge_browser.cpp b/src/ge_browser.cpp new file mode 100644 index 0000000..15f6836 --- /dev/null +++ b/src/ge_browser.cpp @@ -0,0 +1,307 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * gd_browser + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + + +#include +#include "ge_browser.h" +#include "const.h" +#include "utils.h" +#include "log.h" + + +gBrowser::gBrowser(int x, int y, int w, int h, const char *L) + : Fl_Hold_Browser(x, y, w, h, L) +{ + box(G_BOX); + textsize(11); + textcolor(COLOR_TEXT_0); + selection_color(COLOR_BG_1); + color(COLOR_BG_0); + + this->scrollbar.color(COLOR_BG_0); + this->scrollbar.selection_color(COLOR_BG_1); + this->scrollbar.labelcolor(COLOR_BD_1); + this->scrollbar.slider(G_BOX); + + this->hscrollbar.color(COLOR_BG_0); + this->hscrollbar.selection_color(COLOR_BG_1); + this->hscrollbar.labelcolor(COLOR_BD_1); + this->hscrollbar.slider(G_BOX); +} + + +/* ------------------------------------------------------------------ */ + + +gBrowser::~gBrowser() {} + + +/* ------------------------------------------------------------------ */ + + +void gBrowser::init(const char *init_path) { + + gLog("[gBrowser] init path = '%s'\n", init_path); + + if (init_path == NULL || !gIsDir(init_path)) { +#if defined(__linux__) || defined(__APPLE__) + path_obj->value("/home"); +#elif defined(_WIN32) + + /* SHGetFolderPath is deprecated. We should use SHGetKnownFolderPath + * but that would break compatibility with XP. On Vista, GetFolderPath + * is a wrapper of GetKnownFolderPath, so no problem. */ + + char winRoot[1024]; + SHGetFolderPath(NULL, CSIDL_COMMON_DESKTOPDIRECTORY, NULL, 0, winRoot); // si parte dal Desktop + path_obj->value(winRoot); +#endif + gLog("[gBrowser] init_path null or invalid, using default\n"); + } + else + path_obj->value(init_path); + + refresh(); + sort(); +} + + +/* ------------------------------------------------------------------ */ + + +void gBrowser::refresh() { + DIR *dp; + struct dirent *ep; + dp = opendir(path_obj->value()); + if (dp != NULL) { + while ((ep = readdir(dp))) { + + /* skip: + * - "." e ".." + * - hidden files */ + + if (strcmp(ep->d_name, ".") != 0 && strcmp(ep->d_name, "..") != 0) { + if (ep->d_name[0] != '.') { + + /* is it a folder? add square brackets. Is it a file? Append + * a '/' (on Windows seems useless, though) */ + + std::string file = path_obj->value(); + file.insert(file.size(), gGetSlash()); + file += ep->d_name; + + if (gIsDir(file.c_str())) { + char name[PATH_MAX]; + sprintf(name, "@b[%s]", ep->d_name); + add(name); + } + else + if (gIsProject(file.c_str())) { + char name[PATH_MAX]; + sprintf(name, "@i@b%s", ep->d_name); + add(name); + } + else + add(ep->d_name); + } + } + } + closedir(dp); + } + else + gLog("[gBrowser] Couldn't open the directory '%s'\n", path_obj->value()); +} + + +/* ------------------------------------------------------------------ */ + + +void gBrowser::sort() { + for (int t=1; t<=size(); t++) + for (int r=t+1; r<=size(); r++) + if (strcmp(text(t), text(r)) > 0) + swap(t,r); +} + + +/* ------------------------------------------------------------------ */ + + +void gBrowser::up_dir() { + + /* updir = remove last folder from the path. Start from strlen(-1) to + * skip the trailing slash */ + + int i = strlen(path_obj->value())-1; + + /* on Windows an updir from the path "X:\" (3 chars long) must redirect + * to the list of available devices. */ + +#if defined(_WIN32) + if (i <= 3 || !strcmp(path_obj->value(), "All drives")) { + path_obj->value("All drives"); + showDrives(); + return; + } + else { + while (i >= 0) { + if (path_obj->value()[i] == '\\') + break; + i--; + } + + /* delete the last part of the string, from i to len-i, ie everything + * after the "/" */ + + std::string tmp = path_obj->value(); + tmp.erase(i, tmp.size()-i); + + /* if tmp.size == 2 we have something like 'C:'. Add a trailing + * slash */ + + if (tmp.size() == 2) + tmp += "\\"; + + path_obj->value(tmp.c_str()); + refresh(); + } +#elif defined(__linux__) || defined (__APPLE__) + while (i >= 0) { + if (path_obj->value()[i] == '/') + break; + i--; + } + + /* i == 0 means '/', the root dir. It's meaningless to go updir */ + + if (i==0) + path_obj->value("/"); + else { + + /* delete the last part of the string, from i to len-i, ie everything + * after the "/" */ + + std::string tmp = path_obj->value(); + tmp.erase(i, tmp.size()-i); + path_obj->value(tmp.c_str()); + } + refresh(); +#endif +} + + +/* ------------------------------------------------------------------ */ + + +void gBrowser::down_dir(const char *path) { + path_obj->value(path); + refresh(); +} + + +/* ------------------------------------------------------------------ */ + + +const char *gBrowser::get_selected_item() { + + /* click on an empty line */ + + if (text(value()) == NULL) + return NULL; + + selected_item = text(value()); + + /* @ = formatting marks. + * @b = bold, i.e. a directory. Erease '@b[' and ']' */ + + if (selected_item[0] == '@') { + if (selected_item[1] == 'b') { + selected_item.erase(0, 3); + selected_item.erase(selected_item.size()-1, 1); + } + else + if (selected_item[1] == 'i') + selected_item.erase(0, 4); + } + +#if defined(__linux__) || defined(__APPLE__) + + /* add path to file name, to get an absolute path. Avoid double + * slashes like '//' */ + + if (strcmp("/", path_obj->value())) + selected_item.insert(0, "/"); + + selected_item.insert(0, path_obj->value()); + return selected_item.c_str(); +#elif defined(_WIN32) + + /* if path is 'All drives' we are in the devices list and the user + * has clicked on a device such as 'X:\' */ + + if (strcmp(path_obj->value(), "All drives") == 0) + return selected_item.c_str(); + else { + + /* add '\' if the path is like 'X:\' */ + + if (strlen(path_obj->value()) > 3) /// shouln't it be == 3? + selected_item.insert(0, "\\"); + + selected_item.insert(0, path_obj->value()); + return selected_item.c_str(); + } +#endif +} + + +/* ------------------------------------------------------------------ */ + + +#ifdef _WIN32 +void gBrowser::showDrives() { + + /* GetLogicalDriveStrings fills drives like that: + * + * a:\[null]b:\[null]c:\[null]...[null][null] + * + * where [null] stands for \0. */ + + char drives[64]; + char *i = drives; // pointer to 0th element in drives + GetLogicalDriveStrings(64, drives); + + /* code stolen from the web, still unknown. (Jan 09, 2012). */ + + while (*i) { + add(i); + i = &i[strlen(i) + 1]; + } +} + +#endif diff --git a/src/ge_browser.h b/src/ge_browser.h new file mode 100644 index 0000000..8445319 --- /dev/null +++ b/src/ge_browser.h @@ -0,0 +1,68 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ge_browser + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + +#ifndef GE_BROWSER_H +#define GE_BROWSER_H + +#include +#include +#include +#include "ge_mixed.h" + +class gBrowser : public Fl_Hold_Browser { +public: + gBrowser(int x, int y, int w, int h, const char *L=0); + ~gBrowser(); + void init(const char *init_path=NULL); + void refresh(); + void sort(); + void up_dir(); + void down_dir(const char *path); + const char *get_selected_item(); + + /* path_obj + * the actual path*/ + + class gInput *path_obj; + + /* selected_item + * choosen item */ + + std::string selected_item; + +#ifdef _WIN32 +private: + + /* showDrives [WIN32 only] + * lists all the available drivers */ + + void showDrives(); +#endif +}; + +#endif diff --git a/src/ge_channel.cpp b/src/ge_channel.cpp new file mode 100644 index 0000000..d244ad6 --- /dev/null +++ b/src/ge_channel.cpp @@ -0,0 +1,287 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ge_channel + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include "ge_channel.h" +#include "ge_sampleChannel.h" +#include "gd_mainWindow.h" +#include "gd_keyGrabber.h" +#include "gd_midiGrabber.h" +#include "gd_editor.h" +#include "gd_actionEditor.h" +#include "gd_warnings.h" +#include "gd_browser.h" +#include "gd_midiOutputSetup.h" +#include "gg_keyboard.h" +#include "pluginHost.h" +#include "mixer.h" +#include "conf.h" +#include "patch.h" +#include "graphics.h" +#include "channel.h" +#include "wave.h" +#include "sampleChannel.h" +#include "midiChannel.h" +#include "glue.h" +#include "gui_utils.h" + +#ifdef WITH_VST +#include "gd_pluginList.h" +#endif + + +extern Mixer G_Mixer; +extern Conf G_Conf; +extern Patch G_Patch; +extern gdMainWindow *mainWin; + + +gChannel::gChannel(int X, int Y, int W, int H, int type) + : Fl_Group(X, Y, W, H, NULL), type(type) {} + + +/* -------------------------------------------------------------------------- */ + + +int gChannel::getColumnIndex() +{ + return ((gColumn*)parent())->getIndex(); +} + + +/* -------------------------------------------------------------------------- */ + + +void gChannel::blink() +{ + if (gu_getBlinker() > 6) { + mainButton->bgColor0 = COLOR_BG_2; + mainButton->bdColor = COLOR_BD_1; + mainButton->txtColor = COLOR_TEXT_1; + } + else { + mainButton->bgColor0 = COLOR_BG_0; + mainButton->bdColor = COLOR_BD_0; + mainButton->txtColor = COLOR_TEXT_0; + } +} + + +/* -------------------------------------------------------------------------- */ + + +void gChannel::setColorsByStatus(int playStatus, int recStatus) +{ + switch (playStatus) { + case STATUS_OFF: + mainButton->bgColor0 = COLOR_BG_0; + mainButton->bdColor = COLOR_BD_0; + mainButton->txtColor = COLOR_TEXT_0; + break; + case STATUS_PLAY: + mainButton->bgColor0 = COLOR_BG_2; + mainButton->bdColor = COLOR_BD_1; + mainButton->txtColor = COLOR_TEXT_1; + break; + case STATUS_WAIT: + blink(); + break; + case STATUS_ENDING: + mainButton->bgColor0 = COLOR_BD_0; + break; + } + + switch (recStatus) { + case REC_WAITING: + blink(); + break; + case REC_ENDING: + mainButton->bgColor0 = COLOR_BD_0; + break; + } +} + + +/* -------------------------------------------------------------------------- */ + + +int gChannel::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; +} + + + + +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ + + +gStatus::gStatus(int x, int y, int w, int h, SampleChannel *ch, const char *L) +: Fl_Box(x, y, w, h, L), ch(ch) {} + +void gStatus::draw() +{ + fl_rect(x(), y(), w(), h(), COLOR_BD_0); // reset border + fl_rectf(x()+1, y()+1, w()-2, h()-2, COLOR_BG_0); // reset background + + if (ch != NULL) { + if (ch->status & (STATUS_WAIT | STATUS_ENDING | REC_ENDING | REC_WAITING) || + ch->recStatus & (REC_WAITING | REC_ENDING)) + { + fl_rect(x(), y(), w(), h(), COLOR_BD_1); + } + else + if (ch->status == STATUS_PLAY) + fl_rect(x(), y(), w(), h(), COLOR_BD_1); + else + fl_rectf(x()+1, y()+1, w()-2, h()-2, COLOR_BG_0); // status empty + + + if (G_Mixer.chanInput == ch) + fl_rectf(x()+1, y()+1, w()-2, h()-2, COLOR_BG_3); // take in progress + else + if (recorder::active && recorder::canRec(ch)) + fl_rectf(x()+1, y()+1, w()-2, h()-2, COLOR_BG_4); // 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->end - ch->begin); + fl_rectf(x()+1, y()+1, pos, h()-2, COLOR_BG_2); + } +} + + +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ + + +gModeBox::gModeBox(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_BOX); + textsize(11); + textcolor(COLOR_TEXT_0); + color(COLOR_BG_0); + + add("Loop . basic", 0, cb_change_chanmode, (void *)LOOP_BASIC); + add("Loop . once", 0, cb_change_chanmode, (void *)LOOP_ONCE); + add("Loop . once . bar", 0, cb_change_chanmode, (void *)LOOP_ONCE_BAR); + add("Loop . repeat", 0, cb_change_chanmode, (void *)LOOP_REPEAT); + add("Oneshot . basic", 0, cb_change_chanmode, (void *)SINGLE_BASIC); + add("Oneshot . press", 0, cb_change_chanmode, (void *)SINGLE_PRESS); + add("Oneshot . retrig", 0, cb_change_chanmode, (void *)SINGLE_RETRIG); + add("Oneshot . endless", 0, cb_change_chanmode, (void *)SINGLE_ENDLESS); +} + + +/* -------------------------------------------------------------------------- */ + + +void gModeBox::draw() { + fl_rect(x(), y(), w(), h(), COLOR_BD_0); // border + switch (ch->mode) { + case LOOP_BASIC: + fl_draw_pixmap(loopBasic_xpm, x()+1, y()+1); + break; + case LOOP_ONCE: + fl_draw_pixmap(loopOnce_xpm, x()+1, y()+1); + break; + case LOOP_ONCE_BAR: + fl_draw_pixmap(loopOnceBar_xpm, x()+1, y()+1); + break; + case LOOP_REPEAT: + fl_draw_pixmap(loopRepeat_xpm, x()+1, y()+1); + break; + case SINGLE_BASIC: + fl_draw_pixmap(oneshotBasic_xpm, x()+1, y()+1); + break; + case SINGLE_PRESS: + fl_draw_pixmap(oneshotPress_xpm, x()+1, y()+1); + break; + case SINGLE_RETRIG: + fl_draw_pixmap(oneshotRetrig_xpm, x()+1, y()+1); + break; + case SINGLE_ENDLESS: + fl_draw_pixmap(oneshotEndless_xpm, x()+1, y()+1); + break; + } +} + + +/* -------------------------------------------------------------------------- */ + + +void gModeBox::cb_change_chanmode(Fl_Widget *v, void *p) { ((gModeBox*)v)->__cb_change_chanmode((intptr_t)p); } + + +/* -------------------------------------------------------------------------- */ + + +void gModeBox::__cb_change_chanmode(int mode) +{ + ch->mode = mode; + + /* what to do when the channel is playing and you change the mode? + * Nothing, since v0.5.3. Just refresh the action editor window, in + * case it's open */ + + gu_refreshActionEditor(); +} + + +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ + + +gMainButton::gMainButton(int x, int y, int w, int h, const char *l) + : gClick(x, y, w, h, l) {} diff --git a/src/ge_channel.h b/src/ge_channel.h new file mode 100644 index 0000000..113f537 --- /dev/null +++ b/src/ge_channel.h @@ -0,0 +1,162 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ge_channel + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#ifndef GE_CHANNEL_H +#define GE_CHANNEL_H + + +#include +#include +#include +#include "ge_mixed.h" + + +class gChannel : public Fl_Group +{ +protected: + + /* define some breakpoints for dynamic resize */ + +#ifdef WITH_VST + static const int BREAK_READ_ACTIONS = 212; + static const int BREAK_MODE_BOX = 188; + static const int BREAK_FX = 164; + static const int BREAK_DELTA = 120; +#else + static const int BREAK_READ_ACTIONS = 188; + static const int BREAK_MODE_BOX = 164; + static const int BREAK_FX = 140; + static const int BREAK_DELTA = 96; +#endif + static const int BREAK_UNIT = 24; + + /* blink + * blink button when channel is in wait/ending status. */ + + void blink(); + + /* setColorByStatus + * update colors depending on channel status. */ + + void setColorsByStatus(int playStatus, int recStatus); + + /* handleKey + * method wrapped by virtual handle(int e). */ + + int handleKey(int e, int key); + +public: + + gChannel(int x, int y, int w, int h, int type); + + /* 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; + + /* keypress + * what to do when the corresponding key is pressed. */ + + virtual int keyPress(int event) = 0; + + /* getColumnIndex + * return the numeric index of the column in which this channel is + * located. */ + + int getColumnIndex(); + + class gButton *button; + class gStatus *status; + class gMainButton *mainButton; + class gDial *vol; + class gClick *mute; + class gClick *solo; +#ifdef WITH_VST + class gFxButton *fx; +#endif + + int type; +}; + + +/* -------------------------------------------------------------------------- */ + + +class gStatus : public Fl_Box +{ +public: + gStatus(int X, int Y, int W, int H, class SampleChannel *ch, const char *L=0); + void draw(); + class SampleChannel *ch; +}; + + +/* -------------------------------------------------------------------------- */ + + +class gModeBox : public Fl_Menu_Button +{ +private: + static void cb_change_chanmode(Fl_Widget *v, void *p); + inline void __cb_change_chanmode(int mode); + + class SampleChannel *ch; + +public: + gModeBox(int x, int y, int w, int h, class SampleChannel *ch, const char *l=0); + void draw(); +}; + + +/* -------------------------------------------------------------------------- */ + + +/* gMainButton + * base main button for MIDI and Sample Channels. */ + +class gMainButton : public gClick +{ +public: + gMainButton(int x, int y, int w, int h, const char *l=0); + virtual int handle(int e) = 0; +}; + + +#endif diff --git a/src/ge_column.cpp b/src/ge_column.cpp new file mode 100644 index 0000000..9acabd6 --- /dev/null +++ b/src/ge_column.cpp @@ -0,0 +1,304 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ge_column + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include "ge_column.h" +#include "gd_mainWindow.h" +#include "gd_warnings.h" +#include "gg_keyboard.h" +#include "ge_channel.h" +#include "ge_sampleChannel.h" +#include "ge_midiChannel.h" +#include "mixer.h" +#include "conf.h" +#include "log.h" +#include "patch.h" +#include "glue.h" +#include "channel.h" +#include "sampleChannel.h" +#include "midiChannel.h" + +#ifdef WITH_VST + #include "gd_pluginList.h" +#endif + + +extern Mixer G_Mixer; +extern Conf G_Conf; +extern Patch G_Patch; +extern gdMainWindow *mainWin; + + +gColumn::gColumn(int X, int Y, int W, int H, int index, gKeyboard *parent) + : Fl_Group(X, Y, W, H), parent(parent), index(index) +{ + /* gColumn does a bit of a mess: we pass a pointer to its parent (gKeyboard) and + the gColumn itself deals with the creation of another widget, outside gColumn + and inside gKeyboard, which handles the vertical resize bar (gResizerBar). + The resizer cannot stay inside gColumn: it needs a broader view on the other + side widgets. The view can be obtained from gKeyboard only (the upper level). + Unfortunately, parent() can be NULL: at this point (i.e the constructor) + gColumn is still detached from any parent. We use a custom gKeyboard *parent + instead. */ + + begin(); + addChannelBtn = new gClick(x(), y(), w(), 20, "Add new channel"); + end(); + + resizer = new gResizerBar(x()+w(), y(), 16, h(), false); + resizer->setMinSize(140); + parent->add(resizer); + + addChannelBtn->callback(cb_addChannel, (void*)this); +} + + +/* -------------------------------------------------------------------------- */ + + +gColumn::~gColumn() +{ + /* FIXME - this could actually cause a memory leak. resizer is + just removed, not deleted. But we cannot delete it right now. */ + + parent->remove(resizer); +} + + +/* -------------------------------------------------------------------------- */ + + +int gColumn::handle(int e) +{ + switch (e) { + 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 + gVector paths; + gSplit(Fl::event_text(), "\n", &paths); + bool fails = false; + int result = 0; + for (unsigned i=0; iguiChannel); + fails = true; + } + } + if (fails) { + if (paths.size > 1) + gdAlert("Some files were not loaded successfully."); + else + 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 gColumn::resize(int X, int Y, int W, int H) +{ + /* resize all children */ + + int ch = children(); + for (int i=0; iresize(X, Y + (i * (c->h() + 4)), W, c->h()); + } + + /* resize group itself */ + + x(X); y(Y); w(W); h(H); + + /* resize resizerBar */ + + resizer->size(16, H); +} + + +/* -------------------------------------------------------------------------- */ + + +void gColumn::refreshChannels() +{ + for (int i=1; irefresh(); +} + + +/* -------------------------------------------------------------------------- */ + + +void gColumn::draw() +{ + fl_color(fl_rgb_color(27, 27, 27)); + fl_rectf(x(), y(), w(), h()); + + /* call draw and then redraw in order to avoid channel corruption when + scrolling horizontally */ + + for (int i=0; idraw(); + child(i)->redraw(); + } +} + + +/* -------------------------------------------------------------------------- */ + + +void gColumn::cb_addChannel(Fl_Widget *v, void *p) { ((gColumn*)p)->__cb_addChannel(); } + + +/* -------------------------------------------------------------------------- */ + + +gChannel *gColumn::addChannel(class Channel *ch) +{ + gChannel *gch = NULL; + + if (ch->type == CHANNEL_SAMPLE) + gch = (gSampleChannel*) new gSampleChannel( + x(), + y() + children() * 24, + 600, // (1) see notes below + 20, + (SampleChannel*) ch); + else + gch = (gMidiChannel*) new gMidiChannel( + x(), + y() + children() * 24, + w(), + 20, + (MidiChannel*) ch); + + /* (1) we create a new sample channel with a fake width, instead of w() (i.e. + the column width), in case the column is too narrow to display all widgets. + This workaround prevents the widgets to disappear if they have an initial + negative width. MidiChannel does not need such hack because it already fits + nicely in a collapsed column. */ + + add(gch); + resize(x(), y(), w(), (children() * 24) + 66); // evil space for drag n drop + gch->redraw(); // avoid corruption + parent->redraw(); // redraw Keyboard + return gch; +} + + +/* -------------------------------------------------------------------------- */ + + +void gColumn::deleteChannel(gChannel *gch) +{ + gch->hide(); + remove(gch); + delete gch; + + /* reposition all other channels and resize this group */ + /** TODO + * reposition is useless when called by gColumn::clear(). Add a new + * parameter to skip the operation */ + + for (int i=0; iposition(gch->x(), y()+(i*24)); + } + size(w(), children() * 24 + 66); // evil space for drag n drop + redraw(); +} + + +/* -------------------------------------------------------------------------- */ + + +void gColumn::__cb_addChannel() +{ + gLog("[gColumn::__cb_addChannel] index = %d\n", index); + int type = openTypeMenu(); + if (type) + glue_addChannel(index, type); +} + + +/* -------------------------------------------------------------------------- */ + + +int gColumn::openTypeMenu() +{ + Fl_Menu_Item rclick_menu[] = { + {"Sample channel"}, + {"MIDI channel"}, + {0} + }; + + Fl_Menu_Button *b = new Fl_Menu_Button(0, 0, 100, 50); + b->box(G_BOX); + b->textsize(11); + b->textcolor(COLOR_TEXT_0); + b->color(COLOR_BG_0); + + const Fl_Menu_Item *m = rclick_menu->popup(Fl::event_x(), Fl::event_y(), 0, 0, b); + if (!m) return 0; + + if (strcmp(m->label(), "Sample channel") == 0) + return CHANNEL_SAMPLE; + if (strcmp(m->label(), "MIDI channel") == 0) + return CHANNEL_MIDI; + return 0; +} + + +/* -------------------------------------------------------------------------- */ + + +void gColumn::clear(bool full) +{ + if (full) + Fl_Group::clear(); + else { + while (children() >= 2) { // skip "add new channel" btn + int i = children()-1; + deleteChannel((gChannel*)child(i)); + } + } +} diff --git a/src/ge_column.h b/src/ge_column.h new file mode 100644 index 0000000..84ebc8a --- /dev/null +++ b/src/ge_column.h @@ -0,0 +1,98 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ge_column + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#ifndef GE_COLUMN_H +#define GE_COLUMN_H + + +#include +#include + + +class gColumn : public Fl_Group +{ +private: + + static void cb_addChannel (Fl_Widget *v, void *p); + inline void __cb_addChannel(); + + int openTypeMenu(); + + class gClick *addChannelBtn; + class gResizerBar *resizer; + class gKeyboard *parent; + + int index; + +public: + + gColumn(int x, int y, int w, int h, int index, class gKeyboard *parent); + ~gColumn(); + + /* addChannel + * add a new channel in this column and set the internal pointer + * to channel to 'ch'. */ + + class gChannel *addChannel(class Channel *ch); + + /* handle */ + + int handle(int e); + + /* resize + * custom resize behavior. */ + + void resize(int x, int y, int w, int h); + + /* deleteChannel + * remove the channel 'gch' from this column. */ + + void deleteChannel(gChannel *gch); + + /* refreshChannels + * update channels' graphical statues. Called on each GUI cycle. */ + + void refreshChannels(); + + /* clear + * remove all channels from the column. If full==true, delete also the + * "add new channel" button. This method ovverrides the inherited one + * from Fl_Group. */ + + void clear(bool full=false); + + void draw(); + + inline int getIndex() { return index; } + inline void setIndex(int i) { index = i; } + inline bool isEmpty() { return children() == 1; } +}; + + +#endif diff --git a/src/ge_envelopeChannel.cpp b/src/ge_envelopeChannel.cpp new file mode 100644 index 0000000..442f6ba --- /dev/null +++ b/src/ge_envelopeChannel.cpp @@ -0,0 +1,399 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ge_envelopeWidget + * + * Parent class of any envelope controller, from volume to VST parameter + * automations. + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + + +#include +#include "ge_envelopeChannel.h" +#include "gd_actionEditor.h" +#include "gd_mainWindow.h" +#include "gg_keyboard.h" +#include "channel.h" +#include "recorder.h" +#include "mixer.h" + + +extern Mixer G_Mixer; +extern gdMainWindow *mainWin; + + +gEnvelopeChannel::gEnvelopeChannel(int x, int y, gdActionEditor *pParent, int type, int range, const char *l) + : gActionWidget(x, y, 200, 80, pParent), l(l), type(type), range(range), + selectedPoint(-1), draggedPoint(-1) +{ + size(pParent->totalWidth, h()); +} + + +/* ------------------------------------------------------------------ */ + + +gEnvelopeChannel::~gEnvelopeChannel() { + clearPoints(); +} + + +/* ------------------------------------------------------------------ */ + + +void gEnvelopeChannel::addPoint(int frame, int iValue, float fValue, int px, int py) { + point p; + p.frame = frame; + p.iValue = iValue; + p.fValue = fValue; + p.x = px; + p.y = py; + points.add(p); +} + + +/* ------------------------------------------------------------------ */ + + +void gEnvelopeChannel::updateActions() { + for (unsigned i=0; izoom; +} + + +/* ------------------------------------------------------------------ */ + + +void gEnvelopeChannel::draw() { + + baseDraw(); + + /* print label */ + + fl_color(COLOR_BG_1); + fl_font(FL_HELVETICA, 12); + fl_draw(l, x()+4, y(), 80, h(), (Fl_Align) (FL_ALIGN_LEFT)); + + int pxOld = x()-3; + int pyOld = y()+1; + int pxNew = 0; + int pyNew = 0; + + fl_color(COLOR_BG_2); + + for (unsigned i=0; i 0) + fl_line(pxOld+3, pyOld+3, pxNew+3, pyNew+3); + + pxOld = pxNew; + pyOld = pyNew; + } +} + + +/* ------------------------------------------------------------------ */ + + +int gEnvelopeChannel::handle(int e) { + + /* Adding an action: no further checks required, just record it on frame + * mx*pParent->zoom. Deleting action is trickier: find the active + * point and derive from it the corresponding frame. */ + + int ret = 0; + int mx = Fl::event_x()-x(); // mouse x + int my = Fl::event_y()-y(); // mouse y + + switch (e) { + + case FL_ENTER: { + ret = 1; + break; + } + + case FL_MOVE: { + selectedPoint = getSelectedPoint(); + redraw(); + ret = 1; + break; + } + + case FL_LEAVE: { + draggedPoint = -1; + selectedPoint = -1; + redraw(); + ret = 1; + break; + } + + case FL_PUSH: { + + /* left click on point: drag + * right click on point: delete + * left click on void: add */ + + if (Fl::event_button1()) { + + if (selectedPoint != -1) { + draggedPoint = selectedPoint; + } + else { + + /* top & border fix */ + + if (my > h()-8) my = h()-8; + if (mx > pParent->coverX-x()) mx = pParent->coverX-x(); + + if (range == RANGE_FLOAT) { + + /* if this is the first point ever, add other two points at the beginning + * and the end of the range */ + + if (points.size == 0) { + addPoint(0, 0, 1.0f, 0, 1); + recorder::rec(pParent->chan->index, type, 0, 0, 1.0f); + addPoint(G_Mixer.totalFrames, 0, 1.0f, pParent->coverX, 1); + recorder::rec(pParent->chan->index, type, G_Mixer.totalFrames, 0, 1.0f); + } + + /* line between 2 points y = (x-a) / (b-a); a = h() - 8; b = 1 */ + + int frame = mx * pParent->zoom; + float value = (my - h() + 8) / (float) (1 - h() + 8); + addPoint(frame, 0, value, mx, my); + recorder::rec(pParent->chan->index, type, frame, 0, value); + recorder::sortActions(); + sortPoints(); + } + else { + /// TODO + } + mainWin->keyboard->setChannelWithActions((gSampleChannel*)pParent->chan->guiChannel); // update mainWindow + redraw(); + } + } + else { + + /* right click on point 0 or point size-1 deletes the entire envelope */ + + if (selectedPoint != -1) { + if (selectedPoint == 0 || (unsigned) selectedPoint == points.size-1) { + recorder::clearAction(pParent->chan->index, type); + points.clear(); + } + else { + recorder::deleteAction(pParent->chan->index, points.at(selectedPoint).frame, type, false); + recorder::sortActions(); + points.del(selectedPoint); + } + mainWin->keyboard->setChannelWithActions((gSampleChannel*)pParent->chan->guiChannel); // update mainWindow + redraw(); + } + } + + ret = 1; + break; + } + + case FL_RELEASE: { + if (draggedPoint != -1) { + + if (points.at(draggedPoint).x == previousXPoint) { + //gLog("nothing to do\n"); + } + else { + int newFrame = points.at(draggedPoint).x * pParent->zoom; + + /* x edge correction */ + + if (newFrame < 0) + newFrame = 0; + else if (newFrame > G_Mixer.totalFrames) + newFrame = G_Mixer.totalFrames; + + /* vertical line check */ + + int vp = verticalPoint(points.at(draggedPoint)); + if (vp == 1) newFrame -= 256; + else if (vp == -1) newFrame += 256; + + /* delete previous point and record a new one */ + + recorder::deleteAction(pParent->chan->index, points.at(draggedPoint).frame, type, false); + + if (range == RANGE_FLOAT) { + float value = (points.at(draggedPoint).y - h() + 8) / (float) (1 - h() + 8); + recorder::rec(pParent->chan->index, type, newFrame, 0, value); + } + else { + /// TODO + } + + recorder::sortActions(); + points.at(draggedPoint).frame = newFrame; + draggedPoint = -1; + selectedPoint = -1; + } + } + ret = 1; + break; + } + + case FL_DRAG: { + + if (draggedPoint != -1) { + + /* y constraint */ + + if (my > h()-8) + points.at(draggedPoint).y = h()-8; + else + if (my < 1) + points.at(draggedPoint).y = 1; + else + points.at(draggedPoint).y = my; + + /* x constraint + * constrain the point between two ends (leftBorder-point, point-point, + * point-rightBorder). First & last points cannot be shifted on x */ + + if (draggedPoint == 0) + points.at(draggedPoint).x = x()-8; + else + if ((unsigned) draggedPoint == points.size-1) + points.at(draggedPoint).x = pParent->coverX; + else { + int prevPoint = points.at(draggedPoint-1).x; + int nextPoint = points.at(draggedPoint+1).x; + if (mx <= prevPoint) + points.at(draggedPoint).x = prevPoint; + else + if (mx >= nextPoint) + points.at(draggedPoint).x = nextPoint; + //else + // points.at(draggedPoint).x = mx; + else { + if (pParent->gridTool->isOn()) + points.at(draggedPoint).x = pParent->gridTool->getSnapPoint(mx)-1; + else + points.at(draggedPoint).x = mx; + } + } + redraw(); + } + + ret = 1; + break; + } + } + + return ret; +} + + +/* ------------------------------------------------------------------ */ + + +int gEnvelopeChannel::verticalPoint(const point &p) { + for (unsigned i=0; i points.at(i).x) + points.swap(j, i); +} + + +/* ------------------------------------------------------------------ */ + + +int gEnvelopeChannel::getSelectedPoint() { + + /* point is a 7x7 dot */ + + for (unsigned i=0; i= points.at(i).x+x()-4 && + Fl::event_x() <= points.at(i).x+x()+4 && + Fl::event_y() >= points.at(i).y+y() && + Fl::event_y() <= points.at(i).y+y()+7) + return i; + } + return -1; +} + + +/* ------------------------------------------------------------------ */ + + +void gEnvelopeChannel::fill() { + points.clear(); + for (unsigned i=0; itype == type && a->chan == pParent->chan->index) { + if (range == RANGE_FLOAT) + addPoint( + a->frame, // frame + 0, // int value (unused) + a->fValue, // float value + a->frame / pParent->zoom, // x + ((1-h()+8)*a->fValue)+h()-8); // y = (b-a)x + a (line between two points) + // else: TODO + } + } + +} diff --git a/src/ge_envelopeChannel.h b/src/ge_envelopeChannel.h new file mode 100644 index 0000000..7a1aa24 --- /dev/null +++ b/src/ge_envelopeChannel.h @@ -0,0 +1,115 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ge_envelopeWidget + * + * parent class of any envelope controller, from volume to VST parameter + * automations. + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + +#ifndef __GE_ENVELOPECHANNEL_H__ +#define __GE_ENVELOPECHANNEL_H__ + +#include +#include +#include "ge_actionWidget.h" +#include "utils.h" + + +class gEnvelopeChannel : public gActionWidget { + + const char *l; // internal label + int type; // type of action + int range; + + /* point + * a single dot in the graph. x = relative frame, y = relative value */ + + struct point { + int frame; + int iValue; + float fValue; + int x; + int y; + }; + + /* points + * array of points, filled by fillPoints() */ + + gVector points; + + /* selectedPoint + * which point we are selecting? */ + + int selectedPoint; + + /* draggedPoint + * which point we are dragging? */ + + int draggedPoint; + + /* previousXPoint + * x coordinate of point at time t-1. Used to check effective shifts */ + + int previousXPoint; + + void draw(); + + int handle(int e); + + int getSelectedPoint(); + + void sortPoints(); + + /* verticalPoint + * check if two points form a vertical line. In that case the frame value + * would be the same and recorder would go crazy, so shift by a small value + * of frames to create a minimal fadein/fadeout level. Return 0: no + * vertical points; return 1: vertical with the next one, return -1: vertical + * with the previous one. */ + + int verticalPoint(const point &p); + +public: + gEnvelopeChannel(int x, int y, gdActionEditor *pParent, int type, int range, const char *l); + ~gEnvelopeChannel(); + + /* addPoint + * add a point made of frame+value to internal points[]. */ + + void addPoint(int frame, int iValue=0, float fValue=0.0f, int x=-1, int y=-1); + + void updateActions(); + + /* fill + * parse recorder's stack and fill the widget with points. It's up to + * the caller to call this method as initialization. */ + + void fill(); + + inline void clearPoints() { points.clear(); } +}; + +#endif diff --git a/src/ge_midiChannel.cpp b/src/ge_midiChannel.cpp new file mode 100644 index 0000000..d9dfbf4 --- /dev/null +++ b/src/ge_midiChannel.cpp @@ -0,0 +1,342 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ge_midiChannel + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include "ge_midiChannel.h" +#include "ge_channel.h" +#include "ge_sampleChannel.h" +#include "gd_mainWindow.h" +#include "gd_keyGrabber.h" +#include "gd_midiGrabber.h" +#include "gd_editor.h" +#include "gd_actionEditor.h" +#include "gd_warnings.h" +#include "gd_browser.h" +#include "gd_keyGrabber.h" +#include "gd_midiOutputSetup.h" +#include "gg_keyboard.h" +#include "pluginHost.h" +#include "mixer.h" +#include "conf.h" +#include "patch.h" +#include "graphics.h" +#include "channel.h" +#include "wave.h" +#include "sampleChannel.h" +#include "midiChannel.h" +#include "glue.h" +#include "gui_utils.h" + +#ifdef WITH_VST +#include "gd_pluginList.h" +#endif + + +extern Mixer G_Mixer; +extern Conf G_Conf; +extern Patch G_Patch; +extern gdMainWindow *mainWin; + + +gMidiChannel::gMidiChannel(int X, int Y, int W, int H, class MidiChannel *ch) + : gChannel(X, Y, W, H, CHANNEL_MIDI), ch(ch) +{ + begin(); + +#if defined(WITH_VST) + int delta = 120; // (5 widgets * 20) + (5 paddings * 4) +#else + int delta = 96; // (4 widgets * 20) + (4 paddings * 4) +#endif + + button = new gButton(x(), y(), 20, 20); + mainButton = new gMidiMainButton(button->x()+button->w()+4, y(), w() - delta, 20, "-- MIDI --"); + mute = new gClick(mainButton->x()+mainButton->w()+4, y(), 20, 20, "", muteOff_xpm, muteOn_xpm); + solo = new gClick(mute->x()+mute->w()+4, y(), 20, 20, "", soloOff_xpm, soloOn_xpm); +#if defined(WITH_VST) + fx = new gFxButton(solo->x()+solo->w()+4, y(), 20, 20, fxOff_xpm, fxOn_xpm); + vol = new gDial(fx->x()+fx->w()+4, y(), 20, 20); +#else + vol = new gDial(solo->x()+solo->w()+4, y(), 20, 20); +#endif + + end(); + + resizable(mainButton); + + update(); + + button->callback(cb_button, (void*)this); + button->when(FL_WHEN_CHANGED); // do callback on keypress && on keyrelease + +#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; +} + + +/* -------------------------------------------------------------------------- */ + + +void gMidiChannel::cb_button (Fl_Widget *v, void *p) { ((gMidiChannel*)p)->__cb_button(); } +void gMidiChannel::cb_mute (Fl_Widget *v, void *p) { ((gMidiChannel*)p)->__cb_mute(); } +void gMidiChannel::cb_solo (Fl_Widget *v, void *p) { ((gMidiChannel*)p)->__cb_solo(); } +void gMidiChannel::cb_openMenu (Fl_Widget *v, void *p) { ((gMidiChannel*)p)->__cb_openMenu(); } +void gMidiChannel::cb_changeVol (Fl_Widget *v, void *p) { ((gMidiChannel*)p)->__cb_changeVol(); } +#ifdef WITH_VST +void gMidiChannel::cb_openFxWindow(Fl_Widget *v, void *p) { ((gMidiChannel*)p)->__cb_openFxWindow(); } +#endif + + +/* -------------------------------------------------------------------------- */ + + +void gMidiChannel::__cb_mute() +{ + glue_setMute(ch); +} + + +/* -------------------------------------------------------------------------- */ + + +void gMidiChannel::__cb_solo() +{ + solo->value() ? glue_setSoloOn(ch) : glue_setSoloOff(ch); +} + + +/* -------------------------------------------------------------------------- */ + + +void gMidiChannel::__cb_changeVol() +{ + glue_setChanVol(ch, vol->value()); +} + + +/* -------------------------------------------------------------------------- */ + + +#ifdef WITH_VST +void gMidiChannel::__cb_openFxWindow() +{ + gu_openSubWindow(mainWin, new gdPluginList(PluginHost::CHANNEL, ch), WID_FX_LIST); +} +#endif + +/* -------------------------------------------------------------------------- */ + + +void gMidiChannel::__cb_button() +{ + if (button->value()) + glue_keyPress(ch, Fl::event_ctrl(), Fl::event_shift()); +} + + +/* -------------------------------------------------------------------------- */ + + +void gMidiChannel::__cb_openMenu() +{ + Fl_Menu_Item rclick_menu[] = { + {"Edit actions..."}, // 0 + {"Clear actions", 0, 0, 0, FL_SUBMENU}, // 1 + {"All"}, // 2 + {0}, // 3 + {"Setup keyboard input..."}, // 5 + {"Setup MIDI input..."}, // 6 + {"Setup MIDI output..."}, // 7 + {"Delete channel"}, // 8 + {0} + }; + + /* no 'clear actions' if there are no actions */ + + if (!ch->hasActions) + rclick_menu[1].deactivate(); + + Fl_Menu_Button *b = new Fl_Menu_Button(0, 0, 100, 50); + b->box(G_BOX); + b->textsize(11); + b->textcolor(COLOR_TEXT_0); + b->color(COLOR_BG_0); + + const Fl_Menu_Item *m = rclick_menu->popup(Fl::event_x(), Fl::event_y(), 0, 0, b); + if (!m) return; + + if (strcmp(m->label(), "Delete channel") == 0) { + if (!gdConfirmWin("Warning", "Delete channel: are you sure?")) + return; + glue_deleteChannel(ch); + return; + } + + if (strcmp(m->label(), "Setup keyboard input...") == 0) { + gu_openSubWindow(mainWin, new gdKeyGrabber(ch), 0); + //new gdKeyGrabber(ch); + return; + } + + if (strcmp(m->label(), "All") == 0) { + if (!gdConfirmWin("Warning", "Clear all actions: are you sure?")) + return; + recorder::clearChan(ch->index); + gu_refreshActionEditor(); // refresh a.editor window, it could be open + return; + } + + if (strcmp(m->label(), "Edit actions...") == 0) { + gu_openSubWindow(mainWin, new gdActionEditor(ch), WID_ACTION_EDITOR); + return; + } + + if (strcmp(m->label(), "Setup MIDI output...") == 0) { + gu_openSubWindow(mainWin, new gdMidiOutputSetup(ch), 0); + return; + } + + if (strcmp(m->label(), "Setup MIDI input...") == 0) { + gu_openSubWindow(mainWin, new gdMidiGrabberChannel(ch), 0); + return; + } +} + + +/* -------------------------------------------------------------------------- */ + + +void gMidiChannel::refresh() +{ + setColorsByStatus(ch->status, ch->recStatus); + mainButton->redraw(); +} + + +/* -------------------------------------------------------------------------- */ + + +void gMidiChannel::reset() +{ + mainButton->bgColor0 = COLOR_BG_0; + mainButton->bdColor = COLOR_BD_0; + mainButton->txtColor = COLOR_TEXT_0; + mainButton->label("-- MIDI --"); + mainButton->redraw(); +} + + +/* -------------------------------------------------------------------------- */ + + +void gMidiChannel::update() +{ + if (ch->midiOut) { + char tmp[32]; + sprintf(tmp, "-- MIDI (channel %d) --", ch->midiOutChan+1); + mainButton->copy_label(tmp); + } + else + mainButton->label("-- MIDI --"); + + vol->value(ch->volume); + mute->value(ch->mute); + solo->value(ch->solo); + +#ifdef WITH_VST + fx->full = ch->plugins.size > 0; +#endif +} + + +/* -------------------------------------------------------------------------- */ + + +void gMidiChannel::resize(int X, int Y, int W, int H) +{ + gChannel::resize(X, Y, W, H); + + /* this stuff makes sense only with FX button available. Do nothing + * otherwise */ + +#ifdef WITH_VST + if (w() < BREAK_FX) { + fx->hide(); + + mainButton->size(w() - (BREAK_DELTA - BREAK_UNIT), mainButton->h()); + } + else { + fx->show(); + mainButton->size(w() - BREAK_DELTA, mainButton->h()); + } + mute->resize(mainButton->x()+mainButton->w()+4, y(), 20, 20); + solo->resize(mute->x()+mute->w()+4, y(), 20, 20); + + gChannel::init_sizes(); +#endif +} + + +/* -------------------------------------------------------------------------- */ + + +int gMidiChannel::keyPress(int e) +{ + return handleKey(e, ch->key); +} + + +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ + + +gMidiMainButton::gMidiMainButton(int x, int y, int w, int h, const char *l) + : gMainButton(x, y, w, h, l) {} + + +/* -------------------------------------------------------------------------- */ + + +int gMidiMainButton::handle(int e) +{ + // MIDI drag-n-drop does nothing so far. + return gClick::handle(e); +} diff --git a/src/ge_midiChannel.h b/src/ge_midiChannel.h new file mode 100644 index 0000000..af07fb3 --- /dev/null +++ b/src/ge_midiChannel.h @@ -0,0 +1,89 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ge_midiChannel + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#ifndef GE_MIDI_CHANNEL_H +#define GE_MIDI_CHANNEL_H + + +#include +#include +#include +#include "ge_channel.h" +#include "ge_mixed.h" + + +class gMidiChannel : public gChannel +{ +private: + + static void cb_button (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_openMenu (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 + + inline void __cb_mute (); + inline void __cb_solo (); + inline void __cb_changeVol (); + inline void __cb_button (); + inline void __cb_openMenu (); + inline void __cb_readActions (); +#ifdef WITH_VST + inline void __cb_openFxWindow(); +#endif + +public: + + gMidiChannel(int x, int y, int w, int h, class MidiChannel *ch); + + void reset (); + void update (); + void refresh (); + int keyPress(int event); + void resize (int x, int y, int w, int h); + + class MidiChannel *ch; +}; + + +/* -------------------------------------------------------------------------- */ + + +class gMidiMainButton : public gMainButton +{ +public: + gMidiMainButton(int x, int y, int w, int h, const char *l=0); + int handle(int e); +}; + + +#endif diff --git a/src/ge_mixed.cpp b/src/ge_mixed.cpp new file mode 100644 index 0000000..81200c8 --- /dev/null +++ b/src/ge_mixed.cpp @@ -0,0 +1,666 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ge_mixed + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include +#include "ge_mixed.h" +#include "gd_mainWindow.h" +#include "const.h" +#include "mixer.h" +#include "graphics.h" +#include "recorder.h" +#include "gui_utils.h" +#include "channel.h" +#include "sampleChannel.h" + + +extern Mixer G_Mixer; +extern unsigned G_beats; +extern bool G_audio_status; +extern gdMainWindow *mainWin; + + +void __cb_window_closer(Fl_Widget *v, void *p) +{ + delete (Fl_Window*)p; +} + + +/* -------------------------------------------------------------------------- */ + + +gButton::gButton(int X, int Y, int W, int H, const char *L, const char **imgOff, const char **imgOn) + : gClick(X, Y, W, H, L, imgOff, imgOn) {} + + +/* -------------------------------------------------------------------------- */ + + +gClick::gClick(int x, int y, int w, int h, const char *L, const char **imgOff, const char **imgOn) +: gBaseButton(x, y, w, h, L), + imgOff(imgOff), + imgOn(imgOn), + bgColor0(COLOR_BG_0), + bgColor1(COLOR_BG_1), + bdColor(COLOR_BD_0), + txtColor(COLOR_TEXT_0) {} + +void gClick::draw() +{ + if (!active()) txtColor = bdColor; + else txtColor = COLOR_TEXT_0; + + fl_rect(x(), y(), w(), h(), bdColor); // borders + if (value()) { // -- clicked + if (imgOn != NULL) + 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 != NULL) + fl_draw_pixmap(imgOff, x()+1, y()+1); + } + if (!active()) + fl_color(FL_INACTIVE_COLOR); + + fl_color(txtColor); + fl_font(FL_HELVETICA, 11); + fl_draw(label(), x(), y(), w(), h(), FL_ALIGN_CENTER); +} + + +/* -------------------------------------------------------------------------- */ + + +gClickRepeat::gClickRepeat(int x, int y, int w, int h, const char *L, const char **imgOff, const char **imgOn) +: Fl_Repeat_Button(x, y, w, h, L), imgOff(imgOff), imgOn(imgOn) {} + +void gClickRepeat::draw() +{ + if (value()) { // -- clicked + fl_rectf(x(), y(), w(), h(), COLOR_BG_1); // bg + if (imgOn != NULL) + fl_draw_pixmap(imgOn, x()+1, y()+1); + } + else { // -- not clicked + fl_rectf(x(), y(), w(), h(), COLOR_BG_0); // bg + fl_rect(x(), y(), w(), h(), COLOR_BD_0); // border + if (imgOff != NULL) + fl_draw_pixmap(imgOff, x()+1, y()+1); + } + if (!active()) + fl_color(FL_INACTIVE_COLOR); + + fl_color(COLOR_TEXT_0); + fl_font(FL_HELVETICA, 11); + fl_draw(label(), x(), y(), w(), h(), FL_ALIGN_CENTER); +} + + +/* -------------------------------------------------------------------------- */ + + +gInput::gInput(int x, int y, int w, int h, const char *L) +: Fl_Input(x, y, w, h, L) +{ + //Fl::set_boxtype(G_BOX, gDrawBox, 1, 1, 2, 2); + box(G_BOX); + labelsize(11); + labelcolor(COLOR_TEXT_0); + color(COLOR_BG_DARK); + textcolor(COLOR_TEXT_0); + cursor_color(COLOR_TEXT_0); + selection_color(COLOR_BD_0); + textsize(11); + +} + + +/* -------------------------------------------------------------------------- */ + + +gDial::gDial(int x, int y, int w, int h, const char *L) +: Fl_Dial(x, y, w, h, L) +{ + labelsize(11); + labelcolor(COLOR_TEXT_0); + align(FL_ALIGN_LEFT); + type(FL_FILL_DIAL); + angles(0, 360); + color(COLOR_BG_0); // background + selection_color(COLOR_BG_1); // selection +} + +void gDial::draw() +{ + double angle = (angle2()-angle1())*(value()-minimum())/(maximum()-minimum()) + angle1(); + + fl_color(COLOR_BG_0); + fl_pie(x(), y(), w(), h(), 270-angle1(), angle > angle1() ? 360+270-angle : 270-360-angle); + + fl_color(COLOR_BD_0); + fl_arc(x(), y(), w(), h(), 0, 360); + fl_pie(x(), y(), w(), h(), 270-angle, 270-angle1()); +} + +/* -------------------------------------------------------------------------- */ + + +gBox::gBox(int x, int y, int w, int h, const char *L, Fl_Align al) +: Fl_Box(x, y, w, h, L) +{ + labelsize(11); + box(FL_NO_BOX); + labelcolor(COLOR_TEXT_0); + if (al != 0) + align(al | FL_ALIGN_INSIDE); +} + + +/* -------------------------------------------------------------------------- */ + + +gCheck::gCheck(int x, int y, int w, int h, const char *L) +: Fl_Check_Button(x, y, w, h, L) {} + +void gCheck::draw() +{ + int color = !active() ? FL_INACTIVE_COLOR : COLOR_BD_0; + + 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, 11); + fl_color(COLOR_TEXT_0); + fl_draw(label(), x()+20, y(), w(), h(), (Fl_Align) (FL_ALIGN_LEFT | FL_ALIGN_TOP)); +} + + +/* -------------------------------------------------------------------------- */ + + +gRadio::gRadio(int x, int y, int w, int h, const char *L) +: Fl_Radio_Button(x, y, w, h, L) {} + +void gRadio::draw() +{ + int color = !active() ? FL_INACTIVE_COLOR : COLOR_BD_0; + + 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, 11); + fl_color(COLOR_TEXT_0); + fl_draw(label(), x()+20, y(), w(), h(), (Fl_Align) (FL_ALIGN_LEFT | FL_ALIGN_TOP)); +} + + +/* -------------------------------------------------------------------------- */ + + +gProgress::gProgress(int x, int y, int w, int h, const char *L) +: Fl_Progress(x, y, w, h, L) { + color(COLOR_BG_0, COLOR_BD_0); + box(G_BOX); + +} + + +/* -------------------------------------------------------------------------- */ + + +gSoundMeter::gSoundMeter(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), + peak_old(0.0f), + db_level(0.0f), + db_level_old(0.0f) {} + +void gSoundMeter::draw() +{ + fl_rect(x(), y(), w(), h(), COLOR_BD_0); + + /* 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 */ + + db_level = 20 * log10(peak); + if (db_level < db_level_old) + if (db_level_old > -DB_MIN_SCALE) + db_level = db_level_old - 2.0f; + + db_level_old = db_level; + + /* graphical part */ + + float px_level = 0.0f; + if (db_level < 0.0f) + px_level = ((w()/DB_MIN_SCALE) * db_level) + w(); + else + px_level = w(); + + fl_rectf(x()+1, y()+1, w()-2, h()-2, COLOR_BG_0); + fl_rectf(x()+1, y()+1, (int) px_level, h()-2, clip || !G_audio_status ? COLOR_ALERT : COLOR_BD_0); +} + +/* -------------------------------------------------------------------------- */ + +gBeatMeter::gBeatMeter(int x, int y, int w, int h, const char *L) + : Fl_Box(x, y, w, h, L) {} + +void gBeatMeter::draw() +{ + int cursorW = w() / MAX_BEATS; + int greyX = G_Mixer.beats * cursorW; + + fl_rect(x(), y(), w(), h(), COLOR_BD_0); // border + fl_rectf(x()+1, y()+1, w()-2, h()-2, FL_BACKGROUND_COLOR); // bg + fl_rectf(x()+(G_Mixer.actualBeat*cursorW)+3, y()+3, cursorW-5, h()-6, COLOR_BG_2); // cursor + + /* beat cells */ + + fl_color(COLOR_BD_0); + for (int i=1; i<=G_Mixer.beats; i++) + fl_line(x()+cursorW*i, y()+1, x()+cursorW*i, y()+h()-2); + + /* bar line */ + + fl_color(COLOR_BG_2); + int delta = G_Mixer.beats / G_Mixer.bars; + for (int i=1; i= w()-16) { + tmp.resize(size); + size--; + } + tmp += "..."; + fl_draw(tmp.c_str(), x(), y(), w(), h(), FL_ALIGN_CENTER); + } + + } +} + + +/* -------------------------------------------------------------------------- */ + + +void gDrawBox(int x, int y, int w, int h, Fl_Color c) +{ + fl_color(c); + fl_rectf(x, y, w, h); + fl_color(COLOR_BD_0); + fl_rect(x, y, w, h); +} + + +/* -------------------------------------------------------------------------- */ + + +gLiquidScroll::gLiquidScroll(int x, int y, int w, int h, const char *l) + : Fl_Scroll(x, y, w, h, l) +{ + type(Fl_Scroll::VERTICAL); + scrollbar.color(COLOR_BG_0); + scrollbar.selection_color(COLOR_BG_1); + scrollbar.labelcolor(COLOR_BD_1); + scrollbar.slider(G_BOX); +} + + +void gLiquidScroll::resize(int X, int Y, int W, int H) +{ + int nc = children()-2; // skip hscrollbar and vscrollbar + for ( int t=0; tresize(c->x(), c->y(), W-24, c->h()); // W-24: leave room for scrollbar + } + init_sizes(); // tell scroll children changed in size + Fl_Scroll::resize(X,Y,W,H); +} + + +/* -------------------------------------------------------------------------- */ + + +gSlider::gSlider(int x, int y, int w, int h, const char *l) + : Fl_Slider(x, y, w, h, l) +{ + type(FL_HOR_FILL_SLIDER); + + labelsize(11); + align(FL_ALIGN_LEFT); + labelcolor(COLOR_TEXT_0); + + box(G_BOX); + color(COLOR_BG_0); + selection_color(COLOR_BD_0); +} + + +/* -------------------------------------------------------------------------- */ + + +gResizerBar::gResizerBar(int X,int Y,int W,int H, bool vertical) + : Fl_Box(X,Y,W,H), vertical(vertical) +{ + last_y = 0; + min_h = 30; + if (vertical) { + orig_h = H; + labelsize(H); + } + else { + orig_h = W; + labelsize(W); + } + align(FL_ALIGN_CENTER|FL_ALIGN_INSIDE); + labelfont(FL_COURIER); + visible_focus(0); +} + +/* +gResizerBar::~gResizerBar() +{ + gLog("------ resizerbar %p destroyed\n", (void*)this); +} +*/ + +void gResizerBar::HandleDrag(int diff) +{ + Fl_Scroll *grp = (Fl_Scroll*)parent(); + int top; + int bot; + if (vertical) { + top = y(); + bot = y()+h(); + } + else { + top = x(); + bot = x()+w(); + } + + // First pass: find widget directly above us with common edge + // Possibly clamp 'diff' if widget would get too small.. + + for (int t=0; tchildren(); t++) { + Fl_Widget *wd = grp->child(t); + if (vertical) { + if ((wd->y()+wd->h()) == top) { // found widget directly above? + if ((wd->h()+diff) < min_h) + diff = wd->h() - min_h; // 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) < min_h) + diff = wd->w() - min_h; // clamp + wd->resize(wd->x(), wd->y(), wd->w()+diff, wd->h()); // change height + break; // done with first pass + } + } + } + + // Second pass: find widgets below us, move based on clamped diff + + for (int t=0; tchildren(); t++) { + Fl_Widget *wd = grp->child(t); + if (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 (vertical) + resize(x(), y()+diff, w(), h()); + else + resize(x()+diff, y(), w(), h()); + + grp->init_sizes(); + grp->redraw(); +} + + +int gResizerBar::handle(int e) +{ + int ret = 0; + int this_y; + if (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(vertical ? FL_CURSOR_NS : FL_CURSOR_WE); + break; + case FL_LEAVE: + ret = 1; + fl_cursor(FL_CURSOR_DEFAULT); + break; + case FL_PUSH: + ret = 1; + last_y = this_y; + break; + case FL_DRAG: + HandleDrag(this_y-last_y); + last_y = this_y; + ret = 1; + break; + default: break; + } + return(Fl_Box::handle(e) | ret); +} + + +void gResizerBar::resize(int X,int Y,int W,int H) +{ + if (vertical) + Fl_Box::resize(X,Y,W,orig_h); // height of resizer stays constant size + else + Fl_Box::resize(X,Y,orig_h,H); +} + + +/* -------------------------------------------------------------------------- */ + + +gScroll::gScroll(int x, int y, int w, int h, int t) + : Fl_Scroll(x, y, w, h) +{ + type(t); + + scrollbar.color(COLOR_BG_0); + scrollbar.selection_color(COLOR_BG_1); + scrollbar.labelcolor(COLOR_BD_1); + scrollbar.slider(G_BOX); + + hscrollbar.color(COLOR_BG_0); + hscrollbar.selection_color(COLOR_BG_1); + hscrollbar.labelcolor(COLOR_BD_1); + hscrollbar.slider(G_BOX); +} + + +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ + + +gBaseButton::gBaseButton(int x, int y, int w, int h, const char *l) + : Fl_Button(x, y, w, h, l) +{ + initLabel = l ? l : ""; +} + + +/* -------------------------------------------------------------------------- */ + + +void gBaseButton::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 gBaseButton::label(const char *l) +{ + Fl_Button::label(l); + initLabel = l; + trimLabel(); +} + +const char *gBaseButton::label() +{ + return Fl_Button::label(); +} + + +/* -------------------------------------------------------------------------- */ + + +void gBaseButton::resize(int X, int Y, int W, int H) +{ + trimLabel(); + Fl_Button::resize(X, Y, W, H); +} + + +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ + + +gFxButton::gFxButton(int x, int y, int w, int h, const char **imgOff, const char **imgOn) + : gClick(x, y, w, h, NULL, imgOff, imgOn), full(false) {} + + +/* -------------------------------------------------------------------------- */ + + +void gFxButton::draw() +{ + gClick::draw(); + if (full) + fl_draw_pixmap(imgOn, x()+1, y()+1, COLOR_BD_0); +} diff --git a/src/ge_mixed.h b/src/ge_mixed.h new file mode 100644 index 0000000..26942d5 --- /dev/null +++ b/src/ge_mixed.h @@ -0,0 +1,355 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ge_mixed + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#ifndef GE_MIXED_H +#define GE_MIXED_H + +#include +#include +#include // for intptr_t +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 + #include // for SHGetFolderPath +#endif + + +/* cb_window_closer + * callback for when closing windows. Deletes the widget (delete). */ + +void __cb_window_closer(Fl_Widget *v, void *p); + + +/* -------------------------------------------------------------------------- */ + + +class gBaseButton : public Fl_Button +{ +private: + std::string initLabel; + + void trimLabel(); + +public: + gBaseButton(int x, int y, int w, int h, const char *l=0); + void resize(int x, int y, int w, int h); + void label(const char *l); + const char *label(); +}; + + +/* -------------------------------------------------------------------------- */ + + +/* gClick + * a normal button. */ + +class gClick : public gBaseButton +{ +public: + gClick(int x, int y, int w, int h, const char *L=0, const char **imgOff=NULL, const char **imgOn=NULL); + void draw(); + const char **imgOff; + const char **imgOn; + Fl_Color bgColor0; // background not clicked + Fl_Color bgColor1; // background clicked + Fl_Color bdColor; // border + Fl_Color txtColor; // testo +}; + + +/* -------------------------------------------------------------------------- */ + + +class gClickRepeat : public Fl_Repeat_Button +{ +public: + gClickRepeat(int x, int y, int w, int h, const char *L=0, const char **imgOff=NULL, const char **imgOn=NULL); + void draw(); + const char **imgOff; + const char **imgOn; +}; + + +/* -------------------------------------------------------------------------- */ + + +/* gButton + * exactly as gClick but with a unique id inside of it. Used for the buttons in + * channels and for FXs. */ + +class gButton : public gClick +{ +public: + gButton(int X,int Y,int W,int H,const char *L=0, const char **imgOff=NULL, const char **imgOn=NULL); + int key; + int id; +}; + + +/* -------------------------------------------------------------------------- */ + + +class gInput : public Fl_Input +{ +public: + gInput(int x, int y, int w, int h, const char *L=0); +}; + + +/* -------------------------------------------------------------------------- */ + + +class gDial : public Fl_Dial +{ +public: + gDial(int x, int y, int w, int h, const char *L=0); + void draw(); +}; + + +/* -------------------------------------------------------------------------- */ + + +class gBox : public Fl_Box +{ +public: + gBox(int x, int y, int w, int h, const char *L=0, Fl_Align al=FL_ALIGN_CENTER); +}; + + +/* -------------------------------------------------------------------------- */ + + +class gCheck : public Fl_Check_Button +{ +public: + gCheck(int x, int y, int w, int h, const char *L=0); + void draw(); +}; + + +/* -------------------------------------------------------------------------- */ + + +class gRadio : public Fl_Radio_Button +{ +public: + gRadio(int x, int y, int w, int h, const char *L=0); + void draw(); +}; + + +/* -------------------------------------------------------------------------- */ + + +class gProgress : public Fl_Progress +{ +public: + gProgress(int x, int y, int w, int h, const char *L=0); +}; + + +/* -------------------------------------------------------------------------- */ + + +class gSoundMeter : public Fl_Box +{ +public: + gSoundMeter(int X,int Y,int W,int H,const char *L=0); + void draw(); + bool clip; + float mixerPeak; // peak from mixer +private: + float peak; + float peak_old; + float db_level; + float db_level_old; +}; + + +/* -------------------------------------------------------------------------- */ + + +class gBeatMeter : public Fl_Box +{ +public: + gBeatMeter(int X,int Y,int W,int H,const char *L=0); + void draw(); +}; + + +/* -------------------------------------------------------------------------- */ + + +class gChoice : public Fl_Choice +{ +public: + + gChoice(int X,int Y,int W,int H,const char *L=0, bool angle=true); + void draw(); + + inline void show(const char *c) {value(find_index(c)); } + + bool angle; + int id; +}; + + +/* -------------------------------------------------------------------------- */ + + +/* gDrawBox + * custom boxes in FLTK. */ + +#define G_BOX FL_FREE_BOXTYPE +void gDrawBox(int x, int y, int w, int h, Fl_Color c); + + +/* -------------------------------------------------------------------------- */ + + +/* 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/ */ + +class gLiquidScroll : public Fl_Scroll +{ +public: + gLiquidScroll(int x, int y, int w, int h, const char *l=0); + void resize(int x, int y, int w, int h); +}; + + +/* -------------------------------------------------------------------------- */ + + +/* gScroll + * custom scroll with nice scrollbars and something else. */ + +class gScroll : public Fl_Scroll +{ +public: + gScroll(int x, int y, int w, int h, int type=Fl_Scroll::BOTH); +}; + + +/* -------------------------------------------------------------------------- */ + +/* gResizerBar + * '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 */ + +/* TODO - use more general variable names + * (last_y -> last_?, min_h -> min_?, ...) */ + +class gResizerBar : public Fl_Box +{ +private: + bool vertical; + int orig_h; + int last_y; + int min_h; // min height for widget above us + + void HandleDrag(int diff); + +public: + + /* 'vertical' defines the bar movement. Vertical=true: the bar moves + * vertically (up and down). */ + + gResizerBar(int x, int y, int w, int h, bool vertical=true); + //~gResizerBar(); + + inline void setMinSize(int val) { min_h = val; } + inline int getMinSize() { return min_h; } + + int handle(int e); + void resize(int x, int y, int w, int h); +}; + + +/* -------------------------------------------------------------------------- */ + + +class gSlider : public Fl_Slider +{ +public: + gSlider(int x, int y, int w, int h, const char *l=0); + int id; +}; + + +/* -------------------------------------------------------------------------- */ + + +/* gFxButton + * a simple gClick with 'full' parameter (i.e. has plugins). If 'full' is true, + * draw something somewhere. */ + +class gFxButton : public gClick +{ +public: + gFxButton(int x, int y, int w, int h, const char **imgOff=NULL, const char **imgOn=NULL); + void draw(); + bool full; +}; + + +#endif diff --git a/src/ge_muteChannel.cpp b/src/ge_muteChannel.cpp new file mode 100644 index 0000000..01d1391 --- /dev/null +++ b/src/ge_muteChannel.cpp @@ -0,0 +1,411 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ge_muteChannel + * a widget that represents mute actions inside the action editor. + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + + +#include "ge_muteChannel.h" +#include "gd_actionEditor.h" +#include "ge_actionWidget.h" +#include "gd_mainWindow.h" +#include "gg_keyboard.h" +#include "recorder.h" +#include "mixer.h" +#include "glue.h" +#include "channel.h" +#include "log.h" + + +extern gdMainWindow *mainWin; +extern Mixer G_Mixer; + + +gMuteChannel::gMuteChannel(int x, int y, gdActionEditor *pParent) + : gActionWidget(x, y, 200, 80, pParent), draggedPoint(-1), selectedPoint(-1) +{ + size(pParent->totalWidth, h()); + extractPoints(); +} + + +/* ------------------------------------------------------------------ */ + + +void gMuteChannel::draw() { + + baseDraw(); + + /* print label */ + + fl_color(COLOR_BG_1); + fl_font(FL_HELVETICA, 12); + fl_draw("mute", x()+4, y(), w(), h(), (Fl_Align) (FL_ALIGN_LEFT | FL_ALIGN_CENTER)); + + /* draw "on" and "off" labels. Must stay in background */ + + fl_color(COLOR_BG_1); + fl_font(FL_HELVETICA, 9); + fl_draw("on", x()+4, y(), w(), h(), (Fl_Align) (FL_ALIGN_LEFT | FL_ALIGN_TOP)); + fl_draw("off", x()+4, y()+h()-14, w(), h(), (Fl_Align) (FL_ALIGN_LEFT | FL_ALIGN_TOP)); + + /* draw on-off points. On = higher rect, off = lower rect. It always + * starts with a note_off */ + + fl_color(COLOR_BG_2); + + int pxOld = x()+1; + int pxNew = 0; + int py = y()+h()-5; + int pyDot = py-6; + + for (unsigned i=0; icoverX+x()-1, py); +} + + +/* ------------------------------------------------------------------ */ + + +void gMuteChannel::extractPoints() { + + points.clear(); + + /* actions are already sorted by recorder::sortActions() */ + + for (unsigned i=0; ichan == pParent->chan->index) { + if (recorder::global.at(i).at(j)->type & (ACTION_MUTEON | ACTION_MUTEOFF)) { + point p; + p.frame = recorder::frames.at(i); + p.type = recorder::global.at(i).at(j)->type; + p.x = p.frame / pParent->zoom; + points.add(p); + //gLog("[gMuteChannel::extractPoints] point found, type=%d, frame=%d\n", p.type, p.frame); + } + } + } + } +} + + +/* ------------------------------------------------------------------ */ + + +void gMuteChannel::updateActions() { + for (unsigned i=0; izoom; +} + + +/* ------------------------------------------------------------------ */ + + +int gMuteChannel::handle(int e) { + + int ret = 0; + int mouseX = Fl::event_x()-x(); + + switch (e) { + + case FL_ENTER: { + ret = 1; + break; + } + + case FL_MOVE: { + selectedPoint = getSelectedPoint(); + redraw(); + ret = 1; + break; + } + + case FL_LEAVE: { + draggedPoint = -1; + selectedPoint = -1; + redraw(); + ret = 1; + break; + } + + case FL_PUSH: { + + /* left click on point: drag + * right click on point: delete + * left click on void: add */ + + if (Fl::event_button1()) { + + if (selectedPoint != -1) { + draggedPoint = selectedPoint; + previousXPoint = points.at(selectedPoint).x; + } + else { + + /* click on the grey area leads to nowhere */ + + if (mouseX > pParent->coverX) { + ret = 1; + break; + } + + /* click in the middle of a long mute_on (between two points): new actions + * must be added in reverse: first mute_off then mute_on. Let's find the + * next point from here. */ + + unsigned nextPoint = points.size; + for (unsigned i=0; izoom; + int frame_b = frame_a+2048; + + if (pParent->gridTool->isOn()) { + frame_a = pParent->gridTool->getSnapFrame(mouseX); + frame_b = pParent->gridTool->getSnapFrame(mouseX + pParent->gridTool->getCellSize()); + + /* with snap=on a point can fall onto another */ + + if (pointCollides(frame_a) || pointCollides(frame_b)) { + ret = 1; + break; + } + } + + /* ensure frame parity */ + + if (frame_a % 2 != 0) frame_a++; + if (frame_b % 2 != 0) frame_b++; + + /* avoid overflow: frame_b must be within the sequencer range. In that + * case shift the ON-OFF block */ + + if (frame_b >= G_Mixer.totalFrames) { + frame_b = G_Mixer.totalFrames; + frame_a = frame_b-2048; + } + + if (nextPoint % 2 != 0) { + recorder::rec(pParent->chan->index, ACTION_MUTEOFF, frame_a); + recorder::rec(pParent->chan->index, ACTION_MUTEON, frame_b); + } + else { + recorder::rec(pParent->chan->index, ACTION_MUTEON, frame_a); + recorder::rec(pParent->chan->index, ACTION_MUTEOFF, frame_b); + } + recorder::sortActions(); + + mainWin->keyboard->setChannelWithActions((gSampleChannel*)pParent->chan->guiChannel); // update mainWindow + extractPoints(); + redraw(); + } + } + else { + + /* delete points pair */ + + if (selectedPoint != -1) { + + unsigned a; + unsigned b; + + if (points.at(selectedPoint).type == ACTION_MUTEOFF) { + a = selectedPoint-1; + b = selectedPoint; + } + else { + a = selectedPoint; + b = selectedPoint+1; + } + + //gLog("selected: a=%d, b=%d >>> frame_a=%d, frame_b=%d\n", + // a, b, points.at(a).frame, points.at(b).frame); + + recorder::deleteAction(pParent->chan->index, points.at(a).frame, points.at(a).type, false); // false = don't check vals + recorder::deleteAction(pParent->chan->index, points.at(b).frame, points.at(b).type, false); // false = don't check vals + recorder::sortActions(); + + mainWin->keyboard->setChannelWithActions((gSampleChannel*)pParent->chan->guiChannel); // update mainWindow + extractPoints(); + redraw(); + } + } + ret = 1; + break; + } + + case FL_RELEASE: { + + if (draggedPoint != -1) { + + if (points.at(draggedPoint).x == previousXPoint) { + //gLog("nothing to do\n"); + } + else { + + int newFrame = points.at(draggedPoint).x * pParent->zoom; + + recorder::deleteAction( + pParent->chan->index, + points.at(draggedPoint).frame, + points.at(draggedPoint).type, + false); // don't check values + + recorder::rec( + pParent->chan->index, + points.at(draggedPoint).type, + newFrame); + + recorder::sortActions(); + + points.at(draggedPoint).frame = newFrame; + } + } + draggedPoint = -1; + selectedPoint = -1; + + ret = 1; + break; + } + + case FL_DRAG: { + + if (draggedPoint != -1) { + + /* constrain the point between two ends (leftBorder-point, + * point-point, point-rightBorder) */ + + int prevPoint; + int nextPoint; + + if (draggedPoint == 0) { + prevPoint = 0; + nextPoint = points.at(draggedPoint+1).x - 1; + if (pParent->gridTool->isOn()) + nextPoint -= pParent->gridTool->getCellSize(); + } + else + if ((unsigned) draggedPoint == points.size-1) { + prevPoint = points.at(draggedPoint-1).x + 1; + nextPoint = pParent->coverX-x(); + if (pParent->gridTool->isOn()) + prevPoint += pParent->gridTool->getCellSize(); + } + else { + prevPoint = points.at(draggedPoint-1).x + 1; + nextPoint = points.at(draggedPoint+1).x - 1; + if (pParent->gridTool->isOn()) { + prevPoint += pParent->gridTool->getCellSize(); + nextPoint -= pParent->gridTool->getCellSize(); + } + } + + if (mouseX <= prevPoint) + points.at(draggedPoint).x = prevPoint; + else + if (mouseX >= nextPoint) + points.at(draggedPoint).x = nextPoint; + else + if (pParent->gridTool->isOn()) + points.at(draggedPoint).x = pParent->gridTool->getSnapPoint(mouseX)-1; + else + points.at(draggedPoint).x = mouseX; + + redraw(); + } + ret = 1; + break; + } + } + + + return ret; +} + + +/* ------------------------------------------------------------------ */ + + +bool gMuteChannel::pointCollides(int frame) { + for (unsigned i=0; i= points.at(i).x+x()-3 && + Fl::event_x() <= points.at(i).x+x()+3) + return i; + } + return -1; +} diff --git a/src/ge_muteChannel.h b/src/ge_muteChannel.h new file mode 100644 index 0000000..5cb0ca1 --- /dev/null +++ b/src/ge_muteChannel.h @@ -0,0 +1,103 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ge_muteChannel + * a widget representing mute actions inside the action editor. + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + + +#ifndef GE_MUTECHANNEL_H +#define GE_MUTECHANNEL_H + +#include +#include +#include +#include "ge_actionWidget.h" +#include "utils.h" + + +class gMuteChannel : public gActionWidget { + +private: + + /* point + * a single dot in the graph. */ + + struct point { + int frame; + char type; + int x; + }; + + /* points + * array of on/off points, in frames */ + + gVector points; + + /* draggedPoint + * which point we are dragging? */ + + int draggedPoint; + + /* selectedPoint + * which point we are selecting? */ + + int selectedPoint; + + /* previousXPoint + * x coordinate of point at time t-1. Used to check effective shifts */ + + int previousXPoint; + + /* extractPoints + * va a leggere l'array di azioni di Recorder ed estrae tutti i punti + * interessanti mute_on o mute_off. Li mette poi nel gVector points. */ + void extractPoints(); + + /* getSelectedPoint + * ritorna l'indice di points[] in base al punto selezionato (quello + * con il mouse hover). Ritorna -1 se non trova niente. */ + int getSelectedPoint(); + + /* pointCollides + * true if a point collides with another. Used while adding new points + * with snap active.*/ + + bool pointCollides(int frame); + +public: + + gMuteChannel(int x, int y, class gdActionEditor *pParent); + void draw(); + int handle(int e); + + /* updateActions + * calculates new points affected by the zoom. Call this one after + * each zoom update. */ + + void updateActions(); +}; + +#endif diff --git a/src/ge_pianoRoll.cpp b/src/ge_pianoRoll.cpp new file mode 100644 index 0000000..51e8b95 --- /dev/null +++ b/src/ge_pianoRoll.cpp @@ -0,0 +1,730 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ge_pianoRoll + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + + +#include +#include "ge_pianoRoll.h" +#include "gd_mainWindow.h" +#include "gd_actionEditor.h" +#include "channel.h" +#include "midiChannel.h" +#include "const.h" +#include "kernelMidi.h" +#include "log.h" +#include "conf.h" + + +extern gdMainWindow *mainWin; +extern Mixer G_Mixer; +extern Conf G_Conf; + + +gPianoRollContainer::gPianoRollContainer(int x, int y, class gdActionEditor *pParent) + : Fl_Scroll(x, y, 200, 422), pParent(pParent) +{ + size(pParent->totalWidth, G_Conf.pianoRollH); + pianoRoll = new gPianoRoll(x, y, pParent->totalWidth, pParent); +} + + +/* ------------------------------------------------------------------ */ + + +gPianoRollContainer::~gPianoRollContainer() { + clear(); + G_Conf.pianoRollH = h(); + G_Conf.pianoRollY = pianoRoll->y(); +} + + +/* ------------------------------------------------------------------ */ + + +void gPianoRollContainer::updateActions() { + pianoRoll->updateActions(); +} + + +/* ------------------------------------------------------------------ */ + + +void gPianoRollContainer::draw() { + + pianoRoll->size(this->w(), pianoRoll->h()); /// <--- not optimal + + /* clear background */ + + fl_rectf(x(), y(), w(), h(), COLOR_BG_MAIN); + + /* clip pianoRoll to pianoRollContainer size */ + + fl_push_clip(x(), y(), w(), h()); + draw_child(*pianoRoll); + fl_pop_clip(); + + fl_color(COLOR_BD_0); + fl_line_style(0); + fl_rect(x(), y(), pParent->totalWidth, h()); +} + + +/* ------------------------------------------------------------------ */ +/* ------------------------------------------------------------------ */ +/* ------------------------------------------------------------------ */ + + +gPianoRoll::gPianoRoll(int X, int Y, int W, class gdActionEditor *pParent) + : gActionWidget(X, Y, W, 40, pParent) +{ + resizable(NULL); // don't resize children (i.e. pianoItem) + size(W, (MAX_NOTES+1) * CELL_H); // 128 MIDI channels * 15 px height + + if (G_Conf.pianoRollY == -1) + position(x(), y()-(h()/2)); // center + else + position(x(), G_Conf.pianoRollY); + + drawSurface1(); + drawSurface2(); + + /* add actions when the window is opened. Position is zoom-based. MIDI + * actions come always in pair: start + end. */ + + recorder::sortActions(); + + recorder::action *a2 = NULL; + recorder::action *prev = NULL; + + for (unsigned i=0; i than the grey area */ + /** FIXME - can we move this to the outer cycle? */ + + if (recorder::frames.at(i) > G_Mixer.totalFrames) + continue; + + recorder::action *a1 = recorder::global.at(i).at(j); + + if (a1->chan != pParent->chan->index) + continue; + + if (a1->type == ACTION_MIDI) { + + /* if this action is == to previous one: skip it, we have already + * checked it */ + + if (a1 == prev) { + //gLog("[gPianoRoll] ACTION_MIDI found, but skipping - was previous\n"); + continue; + } + + /* extract MIDI infos from a1: if is note off skip it, we are looking + * for note on only */ + + int a1_type = kernelMidi::getB1(a1->iValue); + int a1_note = kernelMidi::getB2(a1->iValue); + int a1_velo = kernelMidi::getB3(a1->iValue); + + if (a1_type == 0x80) { + //gLog("[gPianoRoll] ACTION_MIDI found, but skipping - was note off\n"); + continue; + } + + /* search for the next action. Must have: same channel, ACTION_MIDI, greater + * than a1->frame and with MIDI properties of note_off (0x80), same note + * of a1, same velocity of a1 */ + + recorder::getNextAction( + a1->chan, + ACTION_MIDI, + a1->frame, + &a2, + kernelMidi::getIValue(0x80, a1_note, a1_velo)); + + /* next action note off found: add a new gPianoItem to piano roll */ + + if (a2) { + //gLog("[gPianoRoll] ACTION_MIDI pair found, frame_a=%d frame_b=%d, note_a=%d, note_b=%d, type_a=%d, type_b=%d\n", + // a1->frame, a2->frame, kernelMidi::getNoteValue(a1->iValue), kernelMidi::getNoteValue(a2->iValue), + // kernelMidi::getNoteOnOff(a1->iValue), kernelMidi::getNoteOnOff(a2->iValue)); + new gPianoItem(0, 0, x(), y()+3, a1, a2, pParent); + prev = a2; + a2 = NULL; + } + else + gLog("[gPianoRoll] recorder didn't find action!\n"); + + } + } + } + + end(); +} + + +/* ------------------------------------------------------------------ */ + + +void gPianoRoll::drawSurface1() { + + surface1 = fl_create_offscreen(40, 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, 40, h(), COLOR_BG_MAIN); + + fl_line_style(FL_DASH, 0, NULL); + fl_font(FL_HELVETICA, 11); + + int octave = 9; + + for (int i=1; i<=MAX_NOTES+1; i++) { + + /* print key note label. C C# D D# E F F# G G# A A# B */ + + char note[6]; + int step = i % 12; + + switch (step) { + case 1: + fl_rectf(0, i*CELL_H, 40, CELL_H, 30, 30, 30); + sprintf(note, "%dG", octave); + break; + case 2: + sprintf(note, "%dF#", octave); + break; + case 3: + sprintf(note, "%dF", octave); + break; + case 4: + fl_rectf(0, i*CELL_H, 40, CELL_H, 30, 30, 30); + sprintf(note, "%dE", octave); + break; + case 5: + sprintf(note, "%dD#", octave); + break; + case 6: + fl_rectf(0, i*CELL_H, 40, CELL_H, 30, 30, 30); + sprintf(note, "%dD", octave); + break; + case 7: + sprintf(note, "%dC#", octave); + break; + case 8: + sprintf(note, "%dC", octave); + break; + case 9: + fl_rectf(0, i*CELL_H, 40, CELL_H, 30, 30, 30); + sprintf(note, "%dB", octave); + break; + case 10: + sprintf(note, "%dA#", octave); + break; + case 11: + fl_rectf(0, i*CELL_H, 40, CELL_H, 30, 30, 30); + sprintf(note, "%dA", octave); + break; + case 0: + sprintf(note, "%dG#", octave); + octave--; + break; + } + + fl_color(fl_rgb_color(54, 54, 54)); + fl_draw(note, 4, ((i-1)*CELL_H)+1, 30, CELL_H, (Fl_Align) (FL_ALIGN_LEFT | FL_ALIGN_CENTER)); + + /* print horizontal line */ + + if (i < 128) + fl_line(0, i*CELL_H, 40, +i*CELL_H); + } + + fl_line_style(0); + fl_end_offscreen(); +} + + +/* ------------------------------------------------------------------ */ + + +void gPianoRoll::drawSurface2() { + surface2 = fl_create_offscreen(40, h()); + fl_begin_offscreen(surface2); + fl_rectf(0, 0, 40, h(), COLOR_BG_MAIN); + fl_color(fl_rgb_color(54, 54, 54)); + fl_line_style(FL_DASH, 0, NULL); + for (int i=1; i<=MAX_NOTES+1; i++) { + int step = i % 12; + switch (step) { + case 1: + case 4: + case 6: + case 9: + case 11: + fl_rectf(0, i*CELL_H, 40, CELL_H, 30, 30, 30); + break; + } + if (i < 128) { + fl_color(fl_rgb_color(54, 54, 54)); + fl_line(0, i*CELL_H, 40, +i*CELL_H); + } + } + fl_line_style(0); + fl_end_offscreen(); +} + + +/* ------------------------------------------------------------------ */ + + +void gPianoRoll::draw() { + + fl_copy_offscreen(x(), y(), 40, h(), surface1, 0, 0); + +#if defined(__APPLE__) + for (int i=36; itotalWidth; i+=36) /// TODO: i < pParent->coverX is faster + fl_copy_offscreen(x()+i, y(), 40, h(), surface2, 1, 0); +#else + for (int i=40; itotalWidth; i+=40) /// TODO: i < pParent->coverX is faster + fl_copy_offscreen(x()+i, y(), 40, h(), surface2, 0, 0); +#endif + + baseDraw(false); + draw_children(); +} + + +/* ------------------------------------------------------------------ */ + + +int gPianoRoll::handle(int e) { + + int ret = Fl_Group::handle(e); + + switch (e) { + case FL_PUSH: { + + /* avoid click on grey area */ + + if (Fl::event_x() >= pParent->coverX) { + ret = 1; + break; + } + + + push_y = Fl::event_y() - y(); + + if (Fl::event_button1()) { + + /* ax is driven by grid, ay by the height in px of each note */ + + int ax = Fl::event_x(); + int ay = Fl::event_y(); + + /* vertical snap */ + + int edge = (ay-y()-3) % 15; + if (edge != 0) ay -= edge; + + /* if no overlap, add new piano item. Also check that it doesn't + * overflow on the grey area, by shifting it to the left if + * necessary. */ + + if (!onItem(ax, ay-y()-3)) { + int greyover = ax+20 - pParent->coverX-x(); + if (greyover > 0) + ax -= greyover; + add(new gPianoItem(ax, ay, ax-x(), ay-y()-3, NULL, NULL, pParent)); + redraw(); + } + } + ret = 1; + break; + } + case FL_DRAG: { + + if (Fl::event_button3()) { + + gPianoRollContainer *prc = (gPianoRollContainer*) parent(); + position(x(), Fl::event_y() - push_y); + + if (y() > prc->y()) + position(x(), prc->y()); + else + if (y() < prc->y()+prc->h()-h()) + position(x(), prc->y()+prc->h()-h()); + + prc->redraw(); + } + ret = 1; + break; + } + case FL_MOUSEWHEEL: { // nothing to do, just avoid small internal scroll + ret = 1; + break; + } + } + return ret; +} + + +/* ------------------------------------------------------------------ */ + + +void gPianoRoll::updateActions() { + + /* when zooming, don't delete and re-add actions, just MOVE them. This + * function shifts the action by a zoom factor. Those singlepress are + * stretched, as well */ + + gPianoItem *i; + for (int k=0; kgetFrame_a(), i->getFrame_b(), i->x()); + + int newX = x() + (i->getFrame_a() / pParent->zoom); + int newW = ((i->getFrame_b() - i->getFrame_a()) / pParent->zoom); + if (newW < 8) + newW = 8; + i->resize(newX, i->y(), newW, i->h()); + i->redraw(); + + //gLog("update point %p, frame_a=%d frame_b=%d, x()=%d\n", (void*) i, i->getFrame_a(), i->getFrame_b(), i->x()); + } +} + + +/* ------------------------------------------------------------------ */ + + +bool gPianoRoll::onItem(int rel_x, int rel_y) { + + if (!pParent->chan->hasActions) + return false; + + int note = MAX_NOTES - (rel_y / CELL_H); + + int n = children(); + for (int i=0; igetNote() != note) + continue; + + /* when 2 segments overlap? + * start = the highest value between the two starting points + * end = the lowest value between the two ending points + * if start < end then there's an overlap of end-start pixels. We + * also add 1 px to the edges in order to gain some space: + * [ ][ ] ---> no + * [ ] [ ] ---> yes! */ + + int start = p->x() > rel_x ? p->x() : rel_x-1; + int end = p->x()+p->w() < rel_x + 20 ? p->x()+p->w() : rel_x + 21; + if (start < end) + return true; + } + return false; +} + +/* ------------------------------------------------------------------ */ +/* ------------------------------------------------------------------ */ +/* ------------------------------------------------------------------ */ + + +gPianoItem::gPianoItem(int X, int Y, int rel_x, int rel_y, recorder::action *a, recorder::action *b, gdActionEditor *pParent) + : Fl_Box (X, Y, 20, gPianoRoll::CELL_H-5), + a (a), + b (b), + pParent (pParent), + selected(false), + event_a (0x00), + event_b (0x00), + changed (false) +{ + + /* a is a pointer: action exists, needs to be displayed */ + + if (a) { + note = kernelMidi::getB2(a->iValue); + frame_a = a->frame; + frame_b = b->frame; + event_a = a->iValue; + event_b = b->iValue; + int newX = rel_x + (frame_a / pParent->zoom); + int newY = rel_y + getY(note); + int newW = (frame_b - frame_a) / pParent->zoom; + resize(newX, newY, newW, h()); + } + + /* a is null: action needs to be recorded from scratch */ + + else { + note = getNote(rel_y); + frame_a = rel_x * pParent->zoom; + frame_b = (rel_x + 20) * pParent->zoom; + record(); + size((frame_b - frame_a) / pParent->zoom, h()); + } +} + + +/* ------------------------------------------------------------------ */ + + +bool gPianoItem::overlap() { + + /* when 2 segments overlap? + * start = the highest value between the two starting points + * end = the lowest value between the two ending points + * if start < end then there's an overlap of end-start pixels. */ + + gPianoRoll *pPiano = (gPianoRoll*) parent(); + + for (int i=0; ichildren(); i++) { + + gPianoItem *pItem = (gPianoItem*) pPiano->child(i); + + /* don't check against itself and with different y positions */ + + if (pItem == this || pItem->y() != y()) + continue; + + int start = pItem->x() >= x() ? pItem->x() : x(); + int end = pItem->x()+pItem->w() < x()+w() ? pItem->x()+pItem->w() : x()+w(); + if (start < end) + return true; + } + + return false; +} + + +/* ------------------------------------------------------------------ */ + + +void gPianoItem::draw() { + int _w = w() > 4 ? w() : 4; + //gLog("[gPianoItem] draw me (%p) at x=%d\n", (void*)this, x()); + fl_rectf(x(), y(), _w, h(), (Fl_Color) selected ? COLOR_BD_1 : COLOR_BG_2); +} + + +/* ------------------------------------------------------------------ */ + + +void gPianoItem::record() { + + /* avoid frame overflow */ + + int overflow = frame_b - G_Mixer.totalFrames; + if (overflow > 0) { + frame_b -= overflow; + frame_a -= overflow; + } + + /* note off */ + /** FIXME - use constants */ + event_a |= (0x90 << 24); // note on + event_a |= (note << 16); // note value + event_a |= (0x3F << 8); // velocity + event_a |= (0x00); + + event_b |= (0x80 << 24); // note off + event_b |= (note << 16); // note value + event_b |= (0x3F << 8); // velocity + event_b |= (0x00); + + recorder::rec(pParent->chan->index, ACTION_MIDI, frame_a, event_a); + recorder::rec(pParent->chan->index, ACTION_MIDI, frame_b, event_b); +} + + +/* ------------------------------------------------------------------ */ + + +void gPianoItem::remove() { + recorder::deleteAction(pParent->chan->index, frame_a, ACTION_MIDI, true, event_a, 0.0); + recorder::deleteAction(pParent->chan->index, frame_b, ACTION_MIDI, true, event_b, 0.0); + + /* send a note-off in case we are deleting it in a middle of a key_on + * key_off sequence. */ + + ((MidiChannel*) pParent->chan)->sendMidi(event_b); +} + + +/* ------------------------------------------------------------------ */ + + +int gPianoItem::handle(int e) { + + int ret = 0; + + switch (e) { + + case FL_ENTER: { + selected = true; + ret = 1; + redraw(); + break; + } + + case FL_LEAVE: { + fl_cursor(FL_CURSOR_DEFAULT, FL_WHITE, FL_BLACK); + selected = false; + ret = 1; + redraw(); + break; + } + + case FL_MOVE: { + onLeftEdge = false; + onRightEdge = false; + + if (Fl::event_x() >= x() && Fl::event_x() < x()+4) { + onLeftEdge = true; + fl_cursor(FL_CURSOR_WE, FL_WHITE, FL_BLACK); + } + else + if (Fl::event_x() >= x()+w()-4 && Fl::event_x() <= x()+w()) { + onRightEdge = true; + fl_cursor(FL_CURSOR_WE, FL_WHITE, FL_BLACK); + } + else + fl_cursor(FL_CURSOR_DEFAULT, FL_WHITE, FL_BLACK); + + ret = 1; + break; + } + + case FL_PUSH: { + + push_x = Fl::event_x() - x(); + old_x = x(); + old_w = w(); + + if (Fl::event_button3()) { + fl_cursor(FL_CURSOR_DEFAULT, FL_WHITE, FL_BLACK); + remove(); + hide(); // for Windows + Fl::delete_widget(this); + ((gPianoRoll*)parent())->redraw(); + } + ret = 1; + break; + } + + case FL_DRAG: { + + changed = true; + + gPianoRoll *pr = (gPianoRoll*) parent(); + int coverX = pParent->coverX + pr->x(); // relative coverX + int nx, ny, nw; + + if (onLeftEdge) { + nx = Fl::event_x(); + ny = y(); + nw = x()-Fl::event_x()+w(); + if (nx < pr->x()) { + nx = pr->x(); + nw = w()+x()-pr->x(); + } + else + if (nx > x()+w()-8) { + nx = x()+w()-8; + nw = 8; + } + resize(nx, ny, nw, h()); + } + else + if (onRightEdge) { + nw = Fl::event_x()-x(); + if (Fl::event_x() < x()+8) + nw = 8; + else + if (Fl::event_x() > coverX) + nw = coverX-x(); + size(nw, h()); + } + else { + nx = Fl::event_x() - push_x; + if (nx < pr->x()+1) + nx = pr->x()+1; + else + if (nx+w() > coverX) + nx = coverX-w(); + + /* snapping */ + + if (pParent->gridTool->isOn()) + nx = pParent->gridTool->getSnapPoint(nx-pr->x()) + pr->x() - 1; + + position(nx, y()); + } + + /* update screen */ + + redraw(); + ((gPianoRoll*)parent())->redraw(); + ret = 1; + break; + } + + case FL_RELEASE: { + + /* delete & record the action, only if it doesn't overlap with + * another one */ + + if (overlap()) { + resize(old_x, y(), old_w, h()); + redraw(); + } + else + if (changed) { + remove(); + note = getNote(getRelY()); + frame_a = getRelX() * pParent->zoom; + frame_b = (getRelX()+w()) * pParent->zoom; + record(); + changed = false; + } + + ((gPianoRoll*)parent())->redraw(); + + ret = 1; + break; + } + } + return ret; +} diff --git a/src/ge_pianoRoll.h b/src/ge_pianoRoll.h new file mode 100644 index 0000000..30c04d2 --- /dev/null +++ b/src/ge_pianoRoll.h @@ -0,0 +1,183 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ge_pianoRoll + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + + +#ifndef GE_PIANOROLL_H +#define GE_PIANOROLL_H + +#include +#include +#include +#include "ge_actionWidget.h" +#include "recorder.h" + + +class gPianoRollContainer : public Fl_Scroll { + +private: + class gdActionEditor *pParent; + class gPianoRoll *pianoRoll; + +public: + gPianoRollContainer(int x, int y, class gdActionEditor *parent); + ~gPianoRollContainer(); + void draw(); + void updateActions(); +}; + + +/* ------------------------------------------------------------------ */ + + +class gPianoRoll : public gActionWidget { + +private: + + /* onItem + * is curson on a gPianoItem? */ + + bool onItem(int rel_x, int rel_y); + + /* drawSurface* + * generate 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. */ + + /* drawSurface1 + * draw first tile of note values. */ + + void drawSurface1(); + + /* drawSurface2 + * draw the rest of the piano roll. */ + + void drawSurface2(); + + int push_y; + Fl_Offscreen surface1; // notes, no repeat + Fl_Offscreen surface2; // lines, x-repeat + + +public: + gPianoRoll(int x, int y, int w, class gdActionEditor *pParent); + + void draw(); + int handle(int e); + void updateActions(); + + enum { MAX_NOTES = 127, CELL_H = 15 }; +}; + + +/* ------------------------------------------------------------------ */ + + +class gPianoItem : public Fl_Box { + +private: + + /* getRelX/Y + * return x/y point of this item, relative to piano roll (and not to + * entire screen) */ + + inline int getRelY() { return y() - parent()->y() - 3; }; + inline int getRelX() { return x() - parent()->x(); }; + + /* getNote + * from a relative_y return the real MIDI note, range 0-127. 15 is + * the hardcoded value for note height in pixels */ + + inline int getNote(int rel_y) { + return gPianoRoll::MAX_NOTES - (rel_y / gPianoRoll::CELL_H); + }; + + /* getY + * from a note, return the y position on piano roll */ + + inline int getY(int note) { + return (gPianoRoll::MAX_NOTES * gPianoRoll::CELL_H) - (note * gPianoRoll::CELL_H); + }; + + /* overlap + * check if this item don't overlap with another one. */ + + bool overlap(); + + recorder::action *a; + recorder::action *b; + class gdActionEditor *pParent; + + bool selected; + int push_x; + + /* MIDI note, start frame, end frame - Used only if it's a newly added + * action */ /** FIXME - is it true? */ + + int note; + int frame_a; + int frame_b; + + /* event - bitmasked MIDI events, generated by record() or by ctor if + * not newly added action */ + + int event_a; + int event_b; + + /* changed - if Item has been moved or resized: re-recording needed */ + + bool changed; + + /* onLeft,RightEdge - if cursor is on a widget's edge */ + + bool onLeftEdge; + bool onRightEdge; + + /* old_x, old_w - store previous width and position while dragging + * and moving, in order to restore it if overlap */ + + int old_x, old_w; + +public: + + /* pianoItem ctor + * if action *a == NULL, record a new action */ + + gPianoItem(int x, int y, int rel_x, int rel_y, recorder::action *a, recorder::action *b, class gdActionEditor *pParent); + + void draw(); + int handle(int e); + void record(); + void remove(); + + inline int getFrame_a() { return frame_a; } + inline int getFrame_b() { return frame_b; } + inline int getNote() { return note; } + +}; + +#endif diff --git a/src/ge_sampleChannel.cpp b/src/ge_sampleChannel.cpp new file mode 100644 index 0000000..11bc005 --- /dev/null +++ b/src/ge_sampleChannel.cpp @@ -0,0 +1,606 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ge_sampleChannel + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include "ge_sampleChannel.h" +#include "gd_mainWindow.h" +#include "gd_keyGrabber.h" +#include "gd_midiGrabber.h" +#include "gd_editor.h" +#include "gd_actionEditor.h" +#include "gd_warnings.h" +#include "gd_browser.h" +#include "gd_midiOutputSetup.h" +#include "gg_keyboard.h" +#include "pluginHost.h" +#include "mixer.h" +#include "conf.h" +#include "patch.h" +#include "graphics.h" +#include "channel.h" +#include "wave.h" +#include "sampleChannel.h" +#include "midiChannel.h" +#include "glue.h" +#include "gui_utils.h" + +#ifdef WITH_VST +#include "gd_pluginList.h" +#endif + + +extern Mixer G_Mixer; +extern Conf G_Conf; +extern Patch G_Patch; +extern gdMainWindow *mainWin; + + +gSampleChannel::gSampleChannel(int X, int Y, int W, int H, class SampleChannel *ch) + : gChannel(X, Y, W, H, CHANNEL_SAMPLE), ch(ch) +{ + begin(); + +#if defined(WITH_VST) + int delta = 168; // (7 widgets * 20) + (7 paddings * 4) +#else + int delta = 144; // (6 widgets * 20) + (6 paddings * 4) +#endif + + button = new gButton(x(), y(), 20, 20); + status = new gStatus(button->x()+button->w()+4, y(), 20, 20, ch); + mainButton = new gSampleMainButton(status->x()+status->w()+4, y(), w() - delta, 20, "-- no sample --"); + modeBox = new gModeBox(mainButton->x()+mainButton->w()+4, y(), 20, 20, ch); + mute = new gClick(modeBox->x()+modeBox->w()+4, y(), 20, 20, "", muteOff_xpm, muteOn_xpm); + solo = new gClick(mute->x()+mute->w()+4, y(), 20, 20, "", soloOff_xpm, soloOn_xpm); + readActions = NULL; // no 'R' button + +#if defined(WITH_VST) + fx = new gFxButton(solo->x()+solo->w()+4, y(), 20, 20, fxOff_xpm, fxOn_xpm); + vol = new gDial(fx->x()+fx->w()+4, y(), 20, 20); +#else + vol = new gDial(solo->x()+solo->w()+4, y(), 20, 20); +#endif + + end(); + + resizable(mainButton); + + update(); + + button->callback(cb_button, (void*)this); + button->when(FL_WHEN_CHANGED); // do callback on keypress && on keyrelease + +#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; +} + + +/* -------------------------------------------------------------------------- */ + + +void gSampleChannel::cb_button (Fl_Widget *v, void *p) { ((gSampleChannel*)p)->__cb_button(); } +void gSampleChannel::cb_mute (Fl_Widget *v, void *p) { ((gSampleChannel*)p)->__cb_mute(); } +void gSampleChannel::cb_solo (Fl_Widget *v, void *p) { ((gSampleChannel*)p)->__cb_solo(); } +void gSampleChannel::cb_openMenu (Fl_Widget *v, void *p) { ((gSampleChannel*)p)->__cb_openMenu(); } +void gSampleChannel::cb_changeVol (Fl_Widget *v, void *p) { ((gSampleChannel*)p)->__cb_changeVol(); } +void gSampleChannel::cb_readActions (Fl_Widget *v, void *p) { ((gSampleChannel*)p)->__cb_readActions(); } +#ifdef WITH_VST +void gSampleChannel::cb_openFxWindow(Fl_Widget *v, void *p) { ((gSampleChannel*)p)->__cb_openFxWindow(); } +#endif + + +/* -------------------------------------------------------------------------- */ + + +void gSampleChannel::__cb_mute() +{ + glue_setMute(ch); +} + + +/* -------------------------------------------------------------------------- */ + + +void gSampleChannel::__cb_solo() +{ + solo->value() ? glue_setSoloOn(ch) : glue_setSoloOff(ch); +} + + +/* -------------------------------------------------------------------------- */ + + +void gSampleChannel::__cb_changeVol() +{ + glue_setChanVol(ch, vol->value()); +} + + +/* -------------------------------------------------------------------------- */ + + +#ifdef WITH_VST +void gSampleChannel::__cb_openFxWindow() +{ + gu_openSubWindow(mainWin, new gdPluginList(PluginHost::CHANNEL, ch), WID_FX_LIST); +} +#endif + + +/* -------------------------------------------------------------------------- */ + + + +void gSampleChannel::__cb_button() +{ + if (button->value()) // pushed + glue_keyPress(ch, Fl::event_ctrl(), Fl::event_shift()); + else // released + glue_keyRelease(ch, Fl::event_ctrl(), Fl::event_shift()); +} + + +/* -------------------------------------------------------------------------- */ + + +void gSampleChannel::__cb_openMenu() +{ + /* if you're recording (actions or input) no menu is allowed; you can't + * do anything, especially deallocate the channel */ + + if (G_Mixer.chanInput == ch || recorder::active) + return; + + /* the following is a trash workaround for a FLTK menu. We need a gMenu + * widget asap */ + + Fl_Menu_Item rclick_menu[] = { + {"Load new sample..."}, // 0 + {"Export sample to file..."}, // 1 + {"Setup keyboard input..."}, // 2 + {"Setup MIDI input..."}, // 3 + {"Edit sample..."}, // 4 + {"Edit actions..."}, // 5 + {"Clear actions", 0, 0, 0, FL_SUBMENU}, // 6 + {"All"}, // 7 + {"Mute"}, // 8 + {"Volume"}, // 9 + {"Start/Stop"}, // 10 + {0}, // 11 + {"Free channel"}, // 12 + {"Delete channel"}, // 13 + {0} + }; + + if (ch->status & (STATUS_EMPTY | STATUS_MISSING)) { + rclick_menu[1].deactivate(); + rclick_menu[4].deactivate(); + rclick_menu[12].deactivate(); + } + + /* no 'clear actions' if there are no actions */ + + if (!ch->hasActions) + rclick_menu[6].deactivate(); + + /* no 'clear start/stop actions' for those channels in loop mode: + * they cannot have start/stop actions. */ + + if (ch->mode & LOOP_ANY) + rclick_menu[10].deactivate(); + + Fl_Menu_Button *b = new Fl_Menu_Button(0, 0, 100, 50); + b->box(G_BOX); + b->textsize(11); + b->textcolor(COLOR_TEXT_0); + b->color(COLOR_BG_0); + + const Fl_Menu_Item *m = rclick_menu->popup(Fl::event_x(), Fl::event_y(), 0, 0, b); + if (!m) return; + + if (strcmp(m->label(), "Load new sample...") == 0) { + openBrowser(BROWSER_LOAD_SAMPLE); + return; + } + + if (strcmp(m->label(), "Setup keyboard input...") == 0) { + new gdKeyGrabber(ch); /// FIXME - use gu_openSubWindow + return; + } + + if (strcmp(m->label(), "Setup MIDI input...") == 0) { + gu_openSubWindow(mainWin, new gdMidiGrabberChannel(ch), 0); + return; + } + + if (strcmp(m->label(), "Edit sample...") == 0) { + gu_openSubWindow(mainWin, new gdEditor(ch), WID_SAMPLE_EDITOR); /// FIXME title it's up to gdEditor + return; + } + + if (strcmp(m->label(), "Export sample to file...") == 0) { + openBrowser(BROWSER_SAVE_SAMPLE); + return; + } + + if (strcmp(m->label(), "Delete channel") == 0) { + if (!gdConfirmWin("Warning", "Delete channel: are you sure?")) + return; + glue_deleteChannel(ch); + return; + } + + if (strcmp(m->label(), "Free channel") == 0) { + if (ch->status == STATUS_PLAY) { + if (!gdConfirmWin("Warning", "This action will stop the channel: are you sure?")) + return; + } + else if (!gdConfirmWin("Warning", "Free channel: are you sure?")) + return; + + glue_freeChannel(ch); + + /* delete any related subwindow */ + + /** FIXME - use gu_closeAllSubwindows() */ + + mainWin->delSubWindow(WID_FILE_BROWSER); + mainWin->delSubWindow(WID_ACTION_EDITOR); + mainWin->delSubWindow(WID_SAMPLE_EDITOR); + mainWin->delSubWindow(WID_FX_LIST); + + return; + } + + if (strcmp(m->label(), "Mute") == 0) { + if (!gdConfirmWin("Warning", "Clear all mute actions: are you sure?")) + return; + recorder::clearAction(ch->index, ACTION_MUTEON | ACTION_MUTEOFF); + if (!ch->hasActions) + delActionButton(); + + /* TODO - set mute=false */ + + gu_refreshActionEditor(); // refresh a.editor window, it could be open + return; + } + + if (strcmp(m->label(), "Start/Stop") == 0) { + if (!gdConfirmWin("Warning", "Clear all start/stop actions: are you sure?")) + return; + recorder::clearAction(ch->index, ACTION_KEYPRESS | ACTION_KEYREL | ACTION_KILLCHAN); + if (!ch->hasActions) + delActionButton(); + gu_refreshActionEditor(); // refresh a.editor window, it could be open + return; + } + + if (strcmp(m->label(), "Volume") == 0) { + if (!gdConfirmWin("Warning", "Clear all volume actions: are you sure?")) + return; + recorder::clearAction(ch->index, ACTION_VOLUME); + if (!ch->hasActions) + delActionButton(); + gu_refreshActionEditor(); // refresh a.editor window, it could be open + return; + } + + if (strcmp(m->label(), "All") == 0) { + if (!gdConfirmWin("Warning", "Clear all actions: are you sure?")) + return; + recorder::clearChan(ch->index); + delActionButton(); + gu_refreshActionEditor(); // refresh a.editor window, it could be open + return; + } + + if (strcmp(m->label(), "Edit actions...") == 0) { + gu_openSubWindow(mainWin, new gdActionEditor(ch), WID_ACTION_EDITOR); + return; + } +} + + +/* -------------------------------------------------------------------------- */ + + +void gSampleChannel::__cb_readActions() +{ + ch->readActions ? glue_stopReadingRecs(ch) : glue_startReadingRecs(ch); +} + + +/* -------------------------------------------------------------------------- */ + + +void gSampleChannel::openBrowser(int type) +{ + const char *title = ""; + switch (type) { + case BROWSER_LOAD_SAMPLE: + title = "Browse Sample"; + break; + case BROWSER_SAVE_SAMPLE: + title = "Save Sample"; + break; + case -1: + title = "Edit Sample"; + break; + } + gWindow *childWin = new gdBrowser(title, G_Conf.samplePath, ch, type); + gu_openSubWindow(mainWin, childWin, WID_FILE_BROWSER); +} + + +/* -------------------------------------------------------------------------- */ + + +void gSampleChannel::refresh() +{ + if (!mainButton->visible()) // mainButton invisible? status too (see below) + return; + + setColorsByStatus(ch->status, ch->recStatus); + + if (ch->wave != NULL) { + + if (G_Mixer.chanInput == ch) + mainButton->bgColor0 = COLOR_BG_3; + + if (recorder::active) { + if (recorder::canRec(ch)) { + mainButton->bgColor0 = COLOR_BG_4; + mainButton->txtColor = COLOR_TEXT_0; + } + } + status->redraw(); // status invisible? sampleButton too (see below) + } + mainButton->redraw(); +} + + +/* -------------------------------------------------------------------------- */ + + +void gSampleChannel::reset() +{ + mainButton->bgColor0 = COLOR_BG_0; + mainButton->bdColor = COLOR_BD_0; + mainButton->txtColor = COLOR_TEXT_0; + mainButton->label("-- no sample --"); + delActionButton(true); // force==true, don't check, just remove it + mainButton->redraw(); + status->redraw(); +} + + +/* -------------------------------------------------------------------------- */ + + +void gSampleChannel::update() +{ + /* update sample button's label */ + + switch (ch->status) { + case STATUS_EMPTY: + mainButton->label("-- no sample --"); + break; + case STATUS_MISSING: + case STATUS_WRONG: + mainButton->label("* file not found! *"); + break; + default: + mainButton->label(ch->wave->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 (ch->hasActions) + addActionButton(); + else + delActionButton(); + + /* update key box */ + + char k[4]; + sprintf(k, "%c", ch->key); + button->copy_label(k); + button->redraw(); + + /* updates modebox */ + + modeBox->value(ch->mode); + modeBox->redraw(); + + /* update volumes+mute+solo */ + + vol->value(ch->volume); + mute->value(ch->mute); + solo->value(ch->solo); + +#ifdef WITH_VST + fx->full = ch->plugins.size > 0; +#endif +} + + +/* -------------------------------------------------------------------------- */ + + +int gSampleChannel::keyPress(int e) +{ + return handleKey(e, ch->key); +} + + +/* -------------------------------------------------------------------------- */ + + +void gSampleChannel::addActionButton() +{ + /* quit if 'R' exists yet. */ + + if (readActions != NULL) + return; + + mainButton->size(mainButton->w()-24, mainButton->h()); + + redraw(); + + readActions = new gClick(mainButton->x() + mainButton->w() + 4, + mainButton->y(), 20, 20, "", readActionOff_xpm, + readActionOn_xpm); + readActions->type(FL_TOGGLE_BUTTON); + readActions->value(ch->readActions); + readActions->callback(cb_readActions, (void*)this); + add(readActions); + + /* hard redraw: there's no other way to avoid glitches when moving + * the 'R' button */ + + mainWin->keyboard->redraw(); +} + + +/* -------------------------------------------------------------------------- */ + + +void gSampleChannel::delActionButton(bool force) +{ + if (readActions == NULL) + return; + + /* TODO - readActions check is useless here */ + + if (!force && (readActions == NULL || ch->hasActions)) + return; + + remove(readActions); // delete from Keyboard group (FLTK) + delete readActions; // delete (C++) + readActions = NULL; + + mainButton->size(mainButton->w()+24, mainButton->h()); + mainButton->redraw(); +} + + +/* -------------------------------------------------------------------------- */ + + +void gSampleChannel::resize(int X, int Y, int W, int H) +{ + gChannel::resize(X, Y, W, H); + + if (w() < BREAK_FX) { +#ifdef WITH_VST + fx->hide(); +#endif + mainButton->size(w() - BREAK_DELTA, mainButton->h()); + mute->resize(mainButton->x()+mainButton->w()+4, y(), 20, 20); + solo->resize(mute->x()+mute->w()+4, y(), 20, 20); + } + else + if (w() < BREAK_MODE_BOX) { +#ifdef WITH_VST + fx->show(); +#endif + mainButton->size(w() - (BREAK_DELTA + BREAK_UNIT), mainButton->h()); + mute->resize(mainButton->x()+mainButton->w()+4, y(), 20, 20); + solo->resize(mute->x()+mute->w()+4, y(), 20, 20); + modeBox->hide(); + } + else + if (w() < BREAK_READ_ACTIONS) { + modeBox->show(); + mainButton->size(w() - (BREAK_DELTA + (BREAK_UNIT * 2)), mainButton->h()); + modeBox->resize(mainButton->x()+mainButton->w()+4, y(), 20, 20); + if (readActions) { + readActions->hide(); + } + } + else { + if (readActions) { + mainButton->size(w() - (BREAK_DELTA + (BREAK_UNIT * 3)), mainButton->h()); + readActions->resize(mainButton->x()+mainButton->w()+4, y(), 20, 20); + readActions->show(); + } + } + + gChannel::init_sizes(); +} + + +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ + + +gSampleMainButton::gSampleMainButton(int x, int y, int w, int h, const char *l) + : gMainButton(x, y, w, h, l) {} + + +/* -------------------------------------------------------------------------- */ + + +int gSampleMainButton::handle(int e) +{ + int ret = gClick::handle(e); + switch (e) { + case FL_DND_ENTER: + case FL_DND_DRAG: + case FL_DND_RELEASE: { + ret = 1; + break; + } + case FL_PASTE: { + gSampleChannel *gch = (gSampleChannel*) parent(); // parent is gSampleChannel + SampleChannel *ch = gch->ch; + int result = glue_loadChannel(ch, gTrim(gStripFileUrl(Fl::event_text())).c_str()); + if (result != SAMPLE_LOADED_OK) + mainWin->keyboard->printChannelMessage(result); + ret = 1; + break; + } + } + return ret; +} diff --git a/src/ge_sampleChannel.h b/src/ge_sampleChannel.h new file mode 100644 index 0000000..09238d7 --- /dev/null +++ b/src/ge_sampleChannel.h @@ -0,0 +1,103 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ge_sampleChannel + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#ifndef GE_SAMPLE_CHANNEL_H +#define GE_SAMPLE_CHANNEL_H + + +#include +#include +#include +#include "ge_channel.h" +#include "ge_mixed.h" + + +class gSampleChannel : public gChannel +{ +private: + + static void cb_button (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_openMenu (Fl_Widget *v, void *p); + static void cb_changeVol (Fl_Widget *v, void *p); + static void cb_readActions (Fl_Widget *v, void *p); +#ifdef WITH_VST + static void cb_openFxWindow (Fl_Widget *v, void *p); +#endif + + inline void __cb_mute (); + inline void __cb_solo (); + inline void __cb_changeVol (); + inline void __cb_button (); + inline void __cb_openMenu (); + inline void __cb_readActions (); +#ifdef WITH_VST + inline void __cb_openFxWindow(); +#endif + + void openBrowser(int type); + +public: + + gSampleChannel(int x, int y, int w, int h, class SampleChannel *ch); + + void reset (); + void update (); + void refresh (); + int keyPress(int event); + void resize (int x, int y, int w, int h); + + /* add/delActionButton + * add or remove 'R' button when actions are available. 'Status' is + * the initial status of the button: on or off. + * If force==true remove the button with no further checks. */ + + void addActionButton(); + void delActionButton(bool force=false); + + class gModeBox *modeBox; + class gClick *readActions; + + class SampleChannel *ch; +}; + + +/* -------------------------------------------------------------------------- */ + + +class gSampleMainButton : public gMainButton +{ +public: + gSampleMainButton(int x, int y, int w, int h, const char *l=0); + int handle(int e); +}; + + +#endif diff --git a/src/ge_waveform.cpp b/src/ge_waveform.cpp new file mode 100644 index 0000000..5dcbdad --- /dev/null +++ b/src/ge_waveform.cpp @@ -0,0 +1,838 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ge_waveform + * an element which represents a waveform. + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + + +#include +#include +#include +#include "ge_waveform.h" +#include "gd_editor.h" +#include "wave.h" +#include "conf.h" +#include "glue.h" +#include "mixer.h" +#include "waveFx.h" +#include "ge_mixed.h" +#include "gg_waveTools.h" +#include "channel.h" +#include "sampleChannel.h" + + +extern Mixer G_Mixer; +extern Conf G_Conf; + + +gWaveform::gWaveform(int x, int y, int w, int h, class SampleChannel *ch, const char *l) +: Fl_Widget(x, y, w, h, l), + chan(ch), + menuOpen(false), + chanStart(0), + chanStartLit(false), + chanEnd(0), + chanEndLit(false), + ratio(0.0f), + selectionA(0), + selectionB(0), + selectionA_abs(0), + selectionB_abs(0) +{ + data.sup = NULL; + data.inf = NULL; + data.size = 0; + + grid.snap = G_Conf.sampleEditorGridOn; + grid.level = G_Conf.sampleEditorGridVal; + + stretchToWindow(); +} + + +/* ------------------------------------------------------------------ */ + + +gWaveform::~gWaveform() +{ + freeData(); +} + + +/* ------------------------------------------------------------------ */ + + +void gWaveform::freeData() +{ + if (data.sup != NULL) { + free(data.sup); + free(data.inf); + data.sup = NULL; + data.inf = NULL; + data.size = 0; + } + grid.points.clear(); +} + + +/* ------------------------------------------------------------------ */ + + +int gWaveform::alloc(int datasize) +{ + ratio = chan->wave->size / (float) datasize; + + if (ratio < 2) + return 0; + + freeData(); + + data.size = datasize; + data.sup = (int*) malloc(data.size * sizeof(int)); + data.inf = (int*) malloc(data.size * sizeof(int)); + + int offset = h() / 2; + int zero = y() + offset; // center, zero amplitude (-inf dB) + + /* grid frequency: store a grid point every 'gridFreq' pixel. Must be + * even, as always */ + + int gridFreq = 0; + if (grid.level != 0) { + gridFreq = chan->wave->size / grid.level; + if (gridFreq % 2 != 0) + gridFreq--; + } + + for (int i=0; iwave->size - 1) / (float) datasize); + pn = (i+1) * ((chan->wave->size - 1) / (float) datasize); + + if (pp % 2 != 0) pp -= 1; + if (pn % 2 != 0) pn -= 1; + + float peaksup = 0.0f; + float peakinf = 0.0f; + + /* scan the original data in chunks */ + + int k = pp; + while (k < pn) { + + if (chan->wave->data[k] > peaksup) + peaksup = chan->wave->data[k]; // FIXME - Left data only + else + if (chan->wave->data[k] <= peakinf) + peakinf = chan->wave->data[k]; // FIXME - Left data only + + /* print grid */ + + if (gridFreq != 0) + if (k % gridFreq == 0 && k != 0) + grid.points.add(i); + + k += 2; + } + + data.sup[i] = zero - (peaksup * chan->boost * offset); + data.inf[i] = zero - (peakinf * chan->boost * offset); + + // avoid window overflow + + if (data.sup[i] < y()) data.sup[i] = y(); + if (data.inf[i] > y()+h()-1) data.inf[i] = y()+h()-1; + } + + recalcPoints(); + return 1; +} + + +/* ------------------------------------------------------------------ */ + + +void gWaveform::recalcPoints() +{ + selectionA = relativePoint(selectionA_abs); + selectionB = relativePoint(selectionB_abs); + chanStart = relativePoint(chan->begin / 2); + + /* fix the rounding error when chanEnd is set on the very end of the + * sample */ + + if (chan->end == chan->wave->size) + chanEnd = data.size - 2; // 2 px border + else + chanEnd = relativePoint(chan->end / 2); +} + + +/* ------------------------------------------------------------------ */ + + +void gWaveform::draw() +{ + /* blank canvas */ + + fl_rectf(x(), y(), w(), h(), COLOR_BG_0); + + /* draw selection (if any) */ + + if (selectionA != selectionB) { + + int a_x = selectionA + x() - BORDER; // - start; + int b_x = selectionB + x() - BORDER; // - start; + + if (a_x < 0) + a_x = 0; + if (b_x >= w()-1) + b_x = w()-1; + + if (selectionA < selectionB) + fl_rectf(a_x+BORDER, y(), b_x-a_x, h(), COLOR_BD_0); + else + fl_rectf(b_x+BORDER, y(), a_x-b_x, h(), COLOR_BD_0); + } + + /* draw waveform from x1 (offset driven by the scrollbar) to x2 + * (width of parent window). We don't draw the entire waveform, + * only the visibile part. */ + + int offset = h() / 2; + int zero = y() + offset; // sample zero (-inf dB) + + int wx1 = abs(x() - ((gWaveTools*)parent())->x()); + int wx2 = wx1 + ((gWaveTools*)parent())->w(); + if (x()+w() < ((gWaveTools*)parent())->w()) + wx2 = x() + w() - BORDER; + + fl_color(0, 0, 0); + for (int i=wx1; i 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); + fl_color(255, 255, 255); + fl_draw("s", lineX+4, y()+h()-3); + } + + /* print chanEnd */ + + lineX = x()+chanEnd; + if (chanEndLit) fl_color(COLOR_BD_1); + else fl_color(COLOR_BD_0); + + 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); + fl_color(255, 255, 255); + fl_draw("e", lineX-10, y()+10); + } +} + + +/* ------------------------------------------------------------------ */ + + +int gWaveform::handle(int e) +{ + int ret = 0; + + switch (e) { + + case FL_PUSH: { + + mouseX = Fl::event_x(); + pushed = true; + + if (!mouseOnEnd() && !mouseOnStart()) { + + /* right button? show the menu. Don't set selectionA,B,etc */ + + if (Fl::event_button3()) { + openEditMenu(); + } + else + if (mouseOnSelectionA() || mouseOnSelectionB()) { + resized = true; + } + else { + dragged = true; + selectionA = Fl::event_x() - x(); + + if (selectionA >= data.size) selectionA = data.size; + + selectionB = selectionA; + selectionA_abs = absolutePoint(selectionA); + selectionB_abs = selectionA_abs; + } + } + + ret = 1; + break; + } + + case FL_RELEASE: { + + /* don't recompute points if something is selected */ + + if (selectionA != selectionB) { + pushed = false; + dragged = false; + ret = 1; + break; + } + + int realChanStart = chan->begin; + int realChanEnd = chan->end; + + if (chanStartLit) + realChanStart = absolutePoint(chanStart)*2; + else + if (chanEndLit) + realChanEnd = absolutePoint(chanEnd)*2; + + glue_setBeginEndChannel((gdEditor *) window(), chan, realChanStart, realChanEnd, false); + + pushed = false; + dragged = false; + + redraw(); + ret = 1; + break; + } + + case FL_ENTER: { // enables FL_DRAG + ret = 1; + break; + } + + case FL_LEAVE: { + if (chanStartLit || chanEndLit) { + chanStartLit = false; + chanEndLit = false; + redraw(); + } + ret = 1; + break; + } + + case FL_MOVE: { + mouseX = Fl::event_x(); + mouseY = Fl::event_y(); + + if (mouseOnStart()) { + chanStartLit = true; + redraw(); + } + else + if (chanStartLit) { + chanStartLit = false; + redraw(); + } + + if (mouseOnEnd()) { + chanEndLit = true; + redraw(); + } + else + if (chanEndLit) { + chanEndLit = false; + redraw(); + } + + if (mouseOnSelectionA()) + fl_cursor(FL_CURSOR_WE, FL_WHITE, FL_BLACK); + else + if (mouseOnSelectionB()) + fl_cursor(FL_CURSOR_WE, FL_WHITE, FL_BLACK); + else + fl_cursor(FL_CURSOR_DEFAULT, FL_WHITE, FL_BLACK); + + ret = 1; + break; + } + + case FL_DRAG: { + + /* here the mouse is on the chanStart tool */ + + if (chanStartLit && pushed) { + + chanStart = Fl::event_x() - x(); + + if (grid.snap) + chanStart = applySnap(chanStart); + + if (chanStart < 0) + chanStart = 0; + else + if (chanStart >= chanEnd) + chanStart = chanEnd-2; + + redraw(); + } + else + if (chanEndLit && pushed) { + + chanEnd = Fl::event_x() - x(); + + if (grid.snap) + chanEnd = applySnap(chanEnd); + + if (chanEnd >= data.size - 2) + chanEnd = data.size - 2; + else + if (chanEnd <= chanStart) + chanEnd = chanStart + 2; + + redraw(); + } + + /* here the mouse is on the waveform, i.e. a selection */ + + else + if (dragged) { + + selectionB = Fl::event_x() - x(); + + if (selectionB >= data.size) + selectionB = data.size; + + if (selectionB <= 0) + selectionB = 0; + + if (grid.snap) + selectionB = applySnap(selectionB); + + selectionB_abs = absolutePoint(selectionB); + redraw(); + } + + /* here the mouse is on a selection boundary i.e. resize */ + + else + if (resized) { + int pos = Fl::event_x() - x(); + if (mouseOnSelectionA()) { + selectionA = grid.snap ? applySnap(pos) : pos; + selectionA_abs = absolutePoint(selectionA); + } + else + if (mouseOnSelectionB()) { + selectionB = grid.snap ? applySnap(pos) : pos; + selectionB_abs = absolutePoint(selectionB); + } + redraw(); + } + mouseX = Fl::event_x(); + ret = 1; + break; + } + } + return ret; +} + + +/* ------------------------------------------------------------------ */ + +/* pixel snap disances (10px) must be equal to those defined in + * gWaveform::mouseOnSelectionA() and gWaverfrom::mouseOnSelectionB() */ +/* TODO - use constant for 10px */ + +int gWaveform::applySnap(int pos) +{ + for (unsigned i=0; i= grid.points.at(i) - 10 && + pos <= grid.points.at(i) + 10) + { + return grid.points.at(i); + } + } + return pos; +} + + +/* ------------------------------------------------------------------ */ + + +bool gWaveform::mouseOnStart() +{ + return mouseX-10 > chanStart + x() - BORDER && + mouseX-10 <= chanStart + x() - BORDER + FLAG_WIDTH && + mouseY > h() + y() - FLAG_HEIGHT; +} + + +/* ------------------------------------------------------------------ */ + + +bool gWaveform::mouseOnEnd() +{ + return mouseX-10 >= chanEnd + x() - BORDER - FLAG_WIDTH && + mouseX-10 <= chanEnd + x() - BORDER && + mouseY <= y() + FLAG_HEIGHT + 1; +} + + +/* ------------------------------------------------------------------ */ + +/* pixel boundaries (10px) must be equal to the snap factor distance + * defined in gWaveform::applySnap() */ + +bool gWaveform::mouseOnSelectionA() +{ + if (selectionA == selectionB) + return false; + return mouseX >= selectionA-10+x() && mouseX <= selectionA+10+x(); +} + + +bool gWaveform::mouseOnSelectionB() +{ + if (selectionA == selectionB) + return false; + return mouseX >= selectionB-10+x() && mouseX <= selectionB+10+x(); +} + + +/* ------------------------------------------------------------------ */ + + +int gWaveform::absolutePoint(int p) +{ + if (p <= 0) + return 0; + + if (p > data.size) + return chan->wave->size / 2; + + return (p * ratio) / 2; +} + + +/* ------------------------------------------------------------------ */ + + +int gWaveform::relativePoint(int p) +{ + return (ceilf(p / ratio)) * 2; +} + + +/* ------------------------------------------------------------------ */ + + +void gWaveform::openEditMenu() +{ + if (selectionA == selectionB) + return; + + menuOpen = true; + + Fl_Menu_Item menu[] = { + {"Cut"}, + {"Trim"}, + {"Silence"}, + {"Fade in"}, + {"Fade out"}, + {"Smooth edges"}, + {"Set start/end here"}, + {0} + }; + + if (chan->status == STATUS_PLAY) { + menu[0].deactivate(); + menu[1].deactivate(); + } + + Fl_Menu_Button *b = new Fl_Menu_Button(0, 0, 100, 50); + b->box(G_BOX); + b->textsize(11); + b->textcolor(COLOR_TEXT_0); + b->color(COLOR_BG_0); + + const Fl_Menu_Item *m = menu->popup(Fl::event_x(), Fl::event_y(), 0, 0, b); + if (!m) { + menuOpen = false; + return; + } + + /* straightSel() to ensure that point A is always lower than B */ + + straightSel(); + + if (strcmp(m->label(), "Silence") == 0) { + wfx_silence(chan->wave, absolutePoint(selectionA), absolutePoint(selectionB)); + + selectionA = 0; + selectionB = 0; + + stretchToWindow(); + redraw(); + menuOpen = false; + return; + } + + if (strcmp(m->label(), "Set start/end here") == 0) { + + glue_setBeginEndChannel( + (gdEditor *) window(), // parent + chan, + absolutePoint(selectionA) * 2, // stereo! + absolutePoint(selectionB) * 2, // stereo! + false, // no recalc (we do it here) + false // don't check + ); + + selectionA = 0; + selectionB = 0; + selectionA_abs = 0; + selectionB_abs = 0; + + recalcPoints(); + redraw(); + menuOpen = false; + return; + } + + if (strcmp(m->label(), "Cut") == 0) { + wfx_cut(chan->wave, absolutePoint(selectionA), absolutePoint(selectionB)); + + /* for convenience reset start/end points */ + + glue_setBeginEndChannel( + (gdEditor *) window(), + chan, + 0, + chan->wave->size, + false); + + selectionA = 0; + selectionB = 0; + selectionA_abs = 0; + selectionB_abs = 0; + + setZoom(0); + + menuOpen = false; + return; + } + + if (strcmp(m->label(), "Trim") == 0) { + wfx_trim(chan->wave, absolutePoint(selectionA), absolutePoint(selectionB)); + + glue_setBeginEndChannel( + (gdEditor *) window(), + chan, + 0, + chan->wave->size, + false); + + selectionA = 0; + selectionB = 0; + selectionA_abs = 0; + selectionB_abs = 0; + + stretchToWindow(); + menuOpen = false; + redraw(); + return; + } + + if (!strcmp(m->label(), "Fade in") || !strcmp(m->label(), "Fade out")) { + + int type = !strcmp(m->label(), "Fade in") ? 0 : 1; + wfx_fade(chan->wave, absolutePoint(selectionA), absolutePoint(selectionB), type); + + selectionA = 0; + selectionB = 0; + + stretchToWindow(); + redraw(); + menuOpen = false; + return; + } + + if (!strcmp(m->label(), "Smooth edges")) { + + wfx_smooth(chan->wave, absolutePoint(selectionA), absolutePoint(selectionB)); + + selectionA = 0; + selectionB = 0; + + stretchToWindow(); + redraw(); + menuOpen = false; + return; + } +} + + +/* ------------------------------------------------------------------ */ + + +void gWaveform::straightSel() +{ + if (selectionA > selectionB) { + unsigned tmp = selectionB; + selectionB = selectionA; + selectionA = tmp; + } +} + + +/* ------------------------------------------------------------------ */ + + +void gWaveform::setZoom(int type) +{ + int newSize; + if (type == -1) newSize = data.size*2; // zoom in + else newSize = data.size/2; // zoom out + + if (alloc(newSize)) { + size(data.size, h()); + + /* zoom to pointer */ + + int shift; + if (x() > 0) + shift = Fl::event_x() - x(); + else + if (type == -1) + shift = Fl::event_x() + abs(x()); + else + shift = (Fl::event_x() + abs(x())) / -2; + + if (x() - shift > BORDER) + shift = 0; + + position(x() - shift, 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 = ((gWaveTools*)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 gWaveform::stretchToWindow() +{ + int s = ((gWaveTools*)parent())->w(); + alloc(s); + position(BORDER, y()); + size(s, h()); +} + + +/* ------------------------------------------------------------------ */ + + +bool gWaveform::smaller() +{ + return w() < ((gWaveTools*)parent())->w(); +} + + +/* ------------------------------------------------------------------ */ + + +void gWaveform::setGridLevel(int l) +{ + grid.points.clear(); + grid.level = l; + alloc(data.size); + redraw(); +} diff --git a/src/ge_waveform.h b/src/ge_waveform.h new file mode 100644 index 0000000..65c1e40 --- /dev/null +++ b/src/ge_waveform.h @@ -0,0 +1,193 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ge_waveform + * an element which represents a waveform. + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + + +#ifndef GE_WAVEFORM_H +#define GE_WAVEFORM_H + +#include +#include +#include +#include +#include "utils.h" + +#define FLAG_WIDTH 14 +#define FLAG_HEIGHT 12 +#define BORDER 8 // window border <-> widget border + + +class gWaveform : public Fl_Widget { + +private: + + /* data + * real graphic stuff from the underlying waveform */ + + struct data { + int *sup; + int *inf; + int size; + } data; + + /* grid */ + + struct grid { + bool snap; + int level; + gVector points; + } grid; + + /* chan + * chan in use. */ + + class SampleChannel *chan; + + /* menuOpen + * is the menu open? */ + + bool menuOpen; + + /* mouseOnStart/end + * is mouse on start or end flag? */ + + bool mouseOnStart(); + bool mouseOnEnd(); + + /* mouseOnSelectionA/B + * as above, for the selection */ + + bool mouseOnSelectionA(); + bool mouseOnSelectionB(); + + /* absolutePoint + * from a relative 'p' point (zoom affected) returns the same point + * zoom 1:1 based */ + + int absolutePoint(int p); + + /* relativePoint + * from an absolute 'p' point (1:1 zoom), returns the same point zoom + * affected */ + + int relativePoint(int p); + + /* straightSel + * helper function which flattens the selection if it was made from + * right to left (inverse selection) */ + + void straightSel(); + + /* freeData + * destroy any graphical buffer */ + + void freeData(); + + /* smaller + * is the waveform smaller than the parent window? */ + + bool smaller(); + + /* applySnap + * snap a point at 'pos' pixel */ + + int applySnap(int pos); + +public: + + gWaveform(int x, int y, int w, int h, class SampleChannel *ch, const char *l=0); + ~gWaveform(); + void draw(); + int handle(int e); + + /* alloc + * allocate memory for the picture */ + + int alloc(int datasize=0); + + /* recalcPoints + * re-calc chanStart, chanEnd, ... */ + + void recalcPoints(); + + /* openEditMenu + * show edit menu on right-click */ + + void openEditMenu(); + + /* displayRatio + * how much of the waveform is being displayed on screen */ + + inline float displayRatio() { return 1.0f / (data.size / (float) w()); }; + + /* 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(); + + /* setGridLevel + * set a new frequency level for the grid. 0 means disabled. */ + + void setGridLevel(int l); + + inline void setSnap(bool v) { grid.snap = v; } + inline bool getSnap() { return grid.snap; } + + inline int getSize() { return data.size; } + + int chanStart; + bool chanStartLit; + int chanEnd; + bool chanEndLit; + bool pushed; + bool dragged; + bool resized; + + float ratio; + + /* TODO - useless! use Fl::mouse_x() and Fl::mouse_y() instead */ + int mouseX; // mouse pos for drag.n.drop + int mouseY; + + /* selectionA/B = portion of the selected wave + * " " "" " _abs = selectionA/B not affected by zoom */ + /** TODO - change selectionA to selectionA_rel + TODO - change selectionB to selectionB_rel */ + int selectionA; + int selectionB; + int selectionA_abs; + int selectionB_abs; +}; + + +#endif diff --git a/src/ge_window.cpp b/src/ge_window.cpp new file mode 100644 index 0000000..aba6fc0 --- /dev/null +++ b/src/ge_window.cpp @@ -0,0 +1,183 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ge_window + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + + +#include "ge_window.h" +#include "log.h" + + +gWindow::gWindow(int x, int y, int w, int h, const char *title, int id) + : Fl_Double_Window(x, y, w, h, title), id(id), parent(NULL) { } + + +/* ------------------------------------------------------------------ */ + + +gWindow::gWindow(int w, int h, const char *title, int id) + : Fl_Double_Window(w, h, title), id(id), parent(NULL) { } + + +/* ------------------------------------------------------------------ */ + + +gWindow::~gWindow() { + + /* delete all subwindows in order to empty the stack */ + + for (unsigned i=0; igetParent() != NULL) + (child->getParent())->delSubWindow(child); +} + + +/* ------------------------------------------------------------------ */ + + +void gWindow::addSubWindow(gWindow *w) { + + /** TODO - useless: delete ---------------------------------------- */ + for (unsigned i=0; igetId() == subWindows.at(i)->getId()) { + //gLog("[gWindow] 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.add(w); + //debug(); +} + + +/* ------------------------------------------------------------------ */ + + +void gWindow::delSubWindow(gWindow *w) { + for (unsigned i=0; igetId() == subWindows.at(i)->getId()) { + delete subWindows.at(i); + subWindows.del(i); + //debug(); + return; + } + //debug(); +} + + +/* ------------------------------------------------------------------ */ + + +void gWindow::delSubWindow(int id) { + for (unsigned i=0; igetId() == id) { + delete subWindows.at(i); + subWindows.del(i); + //debug(); + return; + } + //debug(); +} + + +/* ------------------------------------------------------------------ */ + + +int gWindow::getId() { + return id; +} + + +/* ------------------------------------------------------------------ */ + + +void gWindow::setId(int id) { + this->id = id; +} + + +/* ------------------------------------------------------------------ */ + + +void gWindow::debug() { + gLog("---- window stack (id=%d): ----\n", getId()); + for (unsigned i=0; igetId()); + gLog("----\n"); +} + + +/* ------------------------------------------------------------------ */ + + +gWindow *gWindow::getParent() { + return parent; +} + + +/* ------------------------------------------------------------------ */ + + +void gWindow::setParent(gWindow *w) { + parent = w; +} + + +/* ------------------------------------------------------------------ */ + + +bool gWindow::hasWindow(int id) { + for (unsigned i=0; igetId()) + return true; + return false; +} + + +/* ------------------------------------------------------------------ */ + + +gWindow *gWindow::getChild(int id) { + for (unsigned i=0; igetId()) + return subWindows.at(i); + return NULL; +} diff --git a/src/ge_window.h b/src/ge_window.h new file mode 100644 index 0000000..79d772e --- /dev/null +++ b/src/ge_window.h @@ -0,0 +1,73 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ge_window + * A custom window. + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + + +#ifndef __GE_WINDOW_H__ +#define __GE_WINDOW_H__ + + +#include +#include "utils.h" + + +class gWindow : public Fl_Double_Window { + +protected: + gVector subWindows; + int id; + gWindow *parent; + +public: + gWindow(int x, int y, int w, int h, const char *title=0, int id=0); + gWindow(int w, int h, const char *title=0, int id=0); + ~gWindow(); + + static void cb_closeChild(Fl_Widget *v, void *p); + + void addSubWindow(gWindow *w); + void delSubWindow(gWindow *w); + void delSubWindow(int id); + + int getId(); + void setId(int id); + void debug(); + + void setParent(gWindow *); + gWindow *getParent(); + gWindow *getChild(int id); + + /* hasWindow + * true if the window with id 'id' exists in the stack. */ + + bool hasWindow(int id); + +}; + + +#endif diff --git a/src/gg_keyboard.cpp b/src/gg_keyboard.cpp new file mode 100644 index 0000000..8ea9be8 --- /dev/null +++ b/src/gg_keyboard.cpp @@ -0,0 +1,366 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * gg_keyboard + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include "gg_keyboard.h" +#include "gd_browser.h" +#include "gd_mainWindow.h" +#include "gd_editor.h" +#include "gd_warnings.h" +#include "ge_channel.h" +#include "ge_sampleChannel.h" +#include "mixer.h" +#include "conf.h" +#include "const.h" +#include "glue.h" +#include "patch.h" +#include "channel.h" +#include "sampleChannel.h" +#include "log.h" + + +extern Mixer G_Mixer; +extern Conf G_Conf; +extern Patch G_Patch; +extern gdMainWindow *mainWin; + + +int gKeyboard::indexColumn = 0; + + +/* -------------------------------------------------------------------------- */ + + +gKeyboard::gKeyboard(int X, int Y, int W, int H) +: Fl_Scroll (X, Y, W, H), + bckspcPressed(false), + endPressed (false), + spacePressed (false), + addColumnBtn (NULL) +{ + color(COLOR_BG_MAIN); + type(Fl_Scroll::BOTH_ALWAYS); + scrollbar.color(COLOR_BG_0); + scrollbar.selection_color(COLOR_BG_1); + scrollbar.labelcolor(COLOR_BD_1); + scrollbar.slider(G_BOX); + hscrollbar.color(COLOR_BG_0); + hscrollbar.selection_color(COLOR_BG_1); + hscrollbar.labelcolor(COLOR_BD_1); + hscrollbar.slider(G_BOX); + + addColumnBtn = new gClick(8, y(), 200, 20, "Add new column"); + addColumnBtn->callback(cb_addColumn, (void*) this); + add(addColumnBtn); + + init(); +} + + +/* -------------------------------------------------------------------------- */ + + +void gKeyboard::init() +{ + /* add 6 empty columns as init layout */ + + __cb_addColumn(); + __cb_addColumn(); + __cb_addColumn(); + __cb_addColumn(); + __cb_addColumn(); + __cb_addColumn(); +} + + +/* -------------------------------------------------------------------------- */ + + +void gKeyboard::freeChannel(gChannel *gch) +{ + gch->reset(); +} + + +/* -------------------------------------------------------------------------- */ + + +void gKeyboard::deleteChannel(gChannel *gch) +{ + for (unsigned i=0; ifind(gch); + if (k != columns.at(i)->children()) { + columns.at(i)->deleteChannel(gch); + return; + } + } +} + + +/* -------------------------------------------------------------------------- */ + + +void gKeyboard::updateChannel(gChannel *gch) +{ + gch->update(); +} + + +/* -------------------------------------------------------------------------- */ + + +void gKeyboard::organizeColumns() +{ + /* if only one column exists don't cleanup: the initial column must + * stay here. */ + + if (columns.size == 1) + return; + + /* otherwise delete all empty columns */ + /** FIXME - this for loop might not work correctly! */ + + for (unsigned i=columns.size-1; i>=1; i--) { + if (columns.at(i)->isEmpty()) { + //Fl::delete_widget(columns.at(i)); + delete columns.at(i); + columns.del(i); + } + } + + /* compact column, avoid empty spaces */ + + for (unsigned i=1; iposition(columns.at(i-1)->x() + columns.at(i-1)->w() + 16, y()); + + addColumnBtn->position(columns.last()->x() + columns.last()->w() + 16, y()); + + redraw(); +} + + +/* -------------------------------------------------------------------------- */ + + +void gKeyboard::cb_addColumn(Fl_Widget *v, void *p) { ((gKeyboard*)p)->__cb_addColumn(); } + + +/* -------------------------------------------------------------------------- */ + + +gChannel *gKeyboard::addChannel(int colIndex, Channel *ch, bool build) +{ + gColumn *col = getColumn(colIndex); + + /* no column with index 'colIndex' found? Just create it and set its index + to 'colIndex'. */ + + if (!col) { + __cb_addColumn(); + col = columns.last(); + col->setIndex(colIndex); + gLog("[gKeyboard::addChannel] created new column with index=%d\n", colIndex); + } + + gLog("[gKeyboard::addChannel] add to column with index = %d\n", col->getIndex()); + return col->addChannel(ch); +} + + +/* -------------------------------------------------------------------------- */ + + +void gKeyboard::refreshColumns() +{ + for (unsigned i=0; irefreshChannels(); +} + + +/* -------------------------------------------------------------------------- */ + + +gColumn *gKeyboard::getColumn(int index) +{ + for (unsigned i=0; igetIndex() == index) + return columns.at(i); + return NULL; +} + + +/* -------------------------------------------------------------------------- */ + + +int gKeyboard::handle(int e) +{ + int ret = Fl_Group::handle(e); // assume the buttons won't handle the Keyboard events + switch (e) { + case FL_FOCUS: + case FL_UNFOCUS: { + ret = 1; // enables receiving Keyboard events + break; + } + case FL_SHORTCUT: // in case widget that isn't ours has focus + case FL_KEYDOWN: // Keyboard key pushed + case FL_KEYUP: { // Keyboard key released + + /* rewind session. Avoid retrigs */ + + if (e == FL_KEYDOWN) { + if (Fl::event_key() == FL_BackSpace && !bckspcPressed) { + bckspcPressed = true; + glue_rewindSeq(); + ret = 1; + break; + } + else if (Fl::event_key() == FL_End && !endPressed) { + endPressed = true; + glue_startStopInputRec(false); // update gui + ret = 1; + break; + } + else if (Fl::event_key() == FL_Enter && !enterPressed) { + enterPressed = true; + glue_startStopActionRec(); + ret = 1; + break; + } + else if (Fl::event_key() == ' ' && !spacePressed) { + spacePressed = true; + G_Mixer.running ? glue_stopSeq() : glue_startSeq(); // TODO - glue_startStopSeq, no core logic here + ret = 1; + break; + } + } + else if (e == FL_KEYUP) { + if (Fl::event_key() == FL_BackSpace) + bckspcPressed = false; + else if (Fl::event_key() == FL_End) + endPressed = false; + else if (Fl::event_key() == ' ') + spacePressed = false; + else if (Fl::event_key() == FL_Enter) + enterPressed = false; + } + + /* Walk button arrays, trying to match button's label with the Keyboard event. + * If found, set that button's value() based on up/down event, + * and invoke that button's callback() */ + + for (unsigned i=0; ichildren(); k++) + ret &= ((gChannel*)columns.at(i)->child(k))->keyPress(e); + break; + } + } + return ret; +} + + +/* -------------------------------------------------------------------------- */ + + +void gKeyboard::clear() +{ + for (unsigned i=0; iposition(8, y()); +} + + +/* -------------------------------------------------------------------------- */ + + +void gKeyboard::setChannelWithActions(gSampleChannel *gch) +{ + if (gch->ch->hasActions) + gch->addActionButton(); + else + gch->delActionButton(); +} + + +/* -------------------------------------------------------------------------- */ + + +void gKeyboard::printChannelMessage(int res) +{ + if (res == SAMPLE_NOT_VALID) + gdAlert("This is not a valid WAVE file."); + else if (res == SAMPLE_MULTICHANNEL) + gdAlert("Multichannel samples not supported."); + else if (res == SAMPLE_WRONG_BIT) + gdAlert("This sample has an\nunsupported bit-depth (> 32 bit)."); + else if (res == SAMPLE_WRONG_ENDIAN) + gdAlert("This sample has a wrong\nbyte order (not little-endian)."); + else if (res == SAMPLE_WRONG_FORMAT) + gdAlert("This sample is encoded in\nan unsupported audio format."); + else if (res == SAMPLE_READ_ERROR) + gdAlert("Unable to read this sample."); + else if (res == SAMPLE_PATH_TOO_LONG) + gdAlert("File path too long."); + else + gdAlert("Unknown error."); +} + +/* -------------------------------------------------------------------------- */ + + +void gKeyboard::__cb_addColumn() +{ + int colx; + int colxw; + int colw = 380; + if (columns.size == 0) { + colx = x() - xposition(); // mind the offset with xposition() + colxw = colx + colw; + } + else { + gColumn *prev = columns.last(); + colx = prev->x()+prev->w() + 16; + colxw = colx + colw; + } + + /* add gColumn to gKeyboard and to columns vector */ + + gColumn *gc = new gColumn(colx, y(), colw-20, 2000, indexColumn, this); + add(gc); + columns.add(gc); + indexColumn++; + + /* move addColumn button */ + + addColumnBtn->position(colxw-4, y()); + redraw(); + + gLog("[gKeyboard::__cb_addColumn] new column added (index = %d), total count=%d, addColumn(x)=%d\n", + gc->getIndex(), columns.size, addColumnBtn->x()); +} diff --git a/src/gg_keyboard.h b/src/gg_keyboard.h new file mode 100644 index 0000000..6059824 --- /dev/null +++ b/src/gg_keyboard.h @@ -0,0 +1,145 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * gg_keyboard + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#ifndef GG_KEYBOARD_H +#define GG_KEYBOARD_H + + +#include +#include +#include +#include +#include +#include "ge_column.h" +#include "utils.h" + + +class gKeyboard : public Fl_Scroll +{ +private: + + static void cb_addColumn (Fl_Widget *v, void *p); + inline void __cb_addColumn(); + + bool bckspcPressed; + bool endPressed; + bool spacePressed; + bool enterPressed; + + /* indexColumn + * the last index used for column. */ + + static int indexColumn; + + class gClick *addColumnBtn; + + /* columns + * a vector of columns which in turn contain channels. */ + + gVector columns; + +public: + + gKeyboard(int X, int Y, int W, int H); + + int handle(int e); + + /* init + * build the initial setup of empty channels. */ + + void init(); + + /* addChannel + * add a new channel to gChannels. Used by callbacks and during + * patch loading. Requires Channel (and not gChannel). If build is + * set to true, also generate the corresponding column.*/ + + gChannel *addChannel(int column, class Channel *ch, bool build=false); + + /* deleteChannel + * delete a channel from gChannels<> where gChannel->ch == ch and remove + * it from the stack. */ + + void deleteChannel(gChannel *gch); + + /* freeChannel + * free a channel from gChannels<> where gChannel->ch == ch. No channels + * are deleted */ + + void freeChannel(gChannel *gch); + + /* updateChannel + * wrapper function to call gch->update(). */ + + void updateChannel(gChannel *gch); + + /* organizeColumns + * reorganize columns layout by removing empty gaps. */ + + void organizeColumns(); + + /* refreshColumns + * refresh each column's channel, called on each GUI cycle. */ + + void refreshColumns(); + + /* getColumn + * return the column with index 'index', or NULL if not found. */ + + gColumn *getColumn(int index); + + /* clear + * delete all channels and groups. */ + + void clear(); + + /* setChannelWithActions + * add 'R' button if channel has actions, and set recorder to active. */ + + void setChannelWithActions(class gSampleChannel *gch); + + /* printChannelMessage + * given any output by glue_loadChannel, print the message on screen + * on a gdAlert subwindow. */ + + void printChannelMessage(int res); + + /* getTotalColumns */ + + inline 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. */ + + inline int getColumnWidth(int i) { return getColumn(i)->w(); } +}; + + +#endif diff --git a/src/gg_waveTools.cpp b/src/gg_waveTools.cpp new file mode 100644 index 0000000..d8462dd --- /dev/null +++ b/src/gg_waveTools.cpp @@ -0,0 +1,103 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * gg_waveTools + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + + +#include "gg_waveTools.h" +#include "graphics.h" +#include "ge_mixed.h" +#include "ge_waveform.h" +#include "mixer.h" + + +gWaveTools::gWaveTools(int x, int y, int w, int h, SampleChannel *ch, const char *l) + : Fl_Scroll(x, y, w, h, l) +{ + type(Fl_Scroll::HORIZONTAL_ALWAYS); + hscrollbar.color(COLOR_BG_0); + hscrollbar.selection_color(COLOR_BG_1); + hscrollbar.labelcolor(COLOR_BD_1); + hscrollbar.slider(G_BOX); + + waveform = new gWaveform(x, y, w, h-24, ch); + + + //resizable(waveform); +} + + + +/* ------------------------------------------------------------------ */ + + +void gWaveTools::updateWaveform() +{ + waveform->alloc(w()); + waveform->redraw(); +} + + +/* ------------------------------------------------------------------ */ + + +void gWaveTools::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 gWaveTools::handle(int e) +{ + int ret = Fl_Group::handle(e); + switch (e) { + case FL_MOUSEWHEEL: { + waveform->setZoom(Fl::event_dy()); + redraw(); + ret = 1; + break; + } + } + return ret; +} + diff --git a/src/gg_waveTools.h b/src/gg_waveTools.h new file mode 100644 index 0000000..8f81ba1 --- /dev/null +++ b/src/gg_waveTools.h @@ -0,0 +1,49 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * gg_waveTools + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + + +#ifndef GG_WAVETOOLS_H +#define GG_WAVETOOLS_H + +#include +#include +#include + + +class gWaveTools : public Fl_Scroll { +public: + class gWaveform *waveform; + + gWaveTools(int X,int Y,int W, int H, class SampleChannel *ch, const char *L=0); + void resize(int x, int y, int w, int h); + int handle(int e); + + void updateWaveform(); +}; + +#endif diff --git a/src/giada.ico b/src/giada.ico new file mode 100644 index 0000000000000000000000000000000000000000..e0a90bde05515358d30ac390ff16ba8082464b20 GIT binary patch literal 9662 zcmc(lYfRo(9mk&s3KS@paxYLWrBJQ~N+~VnQc7&4Jf1bn6^X~%>&%;HU zPtX6H^E6abZb7PV}HkbbSas9SA)>!Lv7SGb35AsufRKQ-?2Mw?vv}D`h zARK}Y2i4m_{jJapjZh2KPzj1zaTP)?C?3tD=1Ts!bE8ci^)y1&VDP#$Xf_i{j`5>FoeF2IWd~ zq1?$2t*dmf^B{ly{wprUr~E5kn}6l_I{5u`>x=X+tRVAO+Mfi)FbJoiA9~>^Xdbnu zl>ZvgS}Fybf32gH@*kwnt|P^x80n5lN`CJ9cxz?%t)}EH%Ht))5q_rdeEp8l|pZm9M+h!txPrswT5oAky zT63+OX`RUr(TCjYNl8h@g|^?*cM>#C2HGUEk6UVpGkM-Ef%*?!=pPz3;K|z5B zbMX9nbZ+HX_R!~wii(U&VPT>6a{gBO5w9)$tSc!gF@9J>_r$9+-f{R+h}}VQdX>HT z9BbnR&f7;gYd%U}%Sw7#JAW)ID>E*fMNh0GztQdZ#_^s0&GPbcW2va9FfMB6ocRgw zi$1@7Vtm-jR#sLT!CrXHFSi+Wd~ap#zOiS|9uocwkiNn16uCT`cWaUarOW&luL4NU5S5s4C z{LtRD8Gc)x`1y-{`}P@2ZEdZ|$;o*ZyQx;jEoo08jt7YOUy>!)Z?T?wX-~xW+jVty z#s%32-SHdw%pRSnudg>QL*=cEM zS*E$US)UF1W7JbiuHR{GZPnU;Q>6>Jc}Ka5Mdw;%*_%VMbKt;%WmLyj)mp~8GTaqD z*4EZ$4jw#s7ivdmjVr$q&z$w2YHx2hF0zlUi2F90P1gR;4jnpVTsk^Bj9{N__+5_s z$y0|9A2u$sh5yyje2vu4d=5z0kt0X$A}cFv&3h`6?nrIL#l_Ecc6J)e(W6I=;Jh1& zBp}J? zmE2>dW#`zjV`1#twd?74Z7>JvtigZx_4OHX{P=MXJ<}KUof3;(&W!t|zrVjf4Bln0 z#G=P9%f5P4I!~N9v5e}R%RPSC_0-8nvuvC^dD7$5sZ&Pu^z^*F9=)73W72c_^yy_( z=N^7#J$fUJWxxLI%$YOBMRvLCw?~p+OB;D99vB!fgM))%@E(40E#qU6;aqrnXlTgz z;a>B-SoHX1Ie%`RJ$u$zhKGmsj(^oJx86EuC8~$b$jC?-#G`wAWxV?L?%n(S(a}-k z!W`@SxefhJ1${x@@4r2F?wqllKY!kP=Kb)sc>U-8{YmK=8yj0jb?nu~t8=AseD1@{ zn7MG_LKv*yTiDB4Y20en)P$*(xpqrh5h$W`YNK0kJQh-W@Kn?k_XMu>4)aYC4fi zV4Zx4af^QcrHlT!_S9-N-9A1CUh~HwKX_N)U|+8KU4ne$^VW|vK9UZ#@%xTruD#88 z<}>$$oOka=${(NCW;jPC@U)caE`CRe?&hP(P zUtND}8|T-5@a%01~DwB5&@ zvx)wQ-!X6(?9iIGu!lOg((nI+Et%Bc0Igomd4DDpFf)O2Pd@OO6t4^#?pT4qVxqYg zDrdd+6f--$q<5}&shqcE`BHr$yu7)PrS`?ZRj*tKlzHWRd8Sv+_5O0Hoc?o));S#Ex=`uqpwjOuvnm5V8%GSPfq g?Iy?{RH>Y~TH%$mfy>jX&j;qb(qtL0ope+F50W~TZ2$lO literal 0 HcmV?d00001 diff --git a/src/glue.cpp b/src/glue.cpp new file mode 100644 index 0000000..7844175 --- /dev/null +++ b/src/glue.cpp @@ -0,0 +1,1187 @@ +/* ----------------------------------------------------------------------------- + * + * 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-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include "glue.h" +#include "ge_waveform.h" +#include "gd_mainWindow.h" +#include "gd_editor.h" +#include "gd_warnings.h" +#include "gg_waveTools.h" +#include "ge_mixed.h" +#include "gg_keyboard.h" +#include "ge_channel.h" +#include "ge_sampleChannel.h" +#include "gui_utils.h" +#include "mixerHandler.h" +#include "mixer.h" +#include "recorder.h" +#include "wave.h" +#include "pluginHost.h" +#include "channel.h" +#include "sampleChannel.h" +#include "midiChannel.h" +#include "utils.h" +#include "kernelMidi.h" +#include "log.h" +#include "patch.h" +#include "conf.h" + + +extern gdMainWindow *mainWin; +extern Mixer G_Mixer; +extern Patch G_Patch; +extern Conf G_Conf; +extern bool G_audio_status; +#ifdef WITH_VST +extern PluginHost G_PluginHost; +#endif + + +static bool __soloSession__ = false; + + +/* -------------------------------------------------------------------------- */ + + +int glue_loadChannel(SampleChannel *ch, const char *fname) +{ + /* save the patch and take the last browser's dir in order to re-use it + * the next time */ + + G_Conf.setPath(G_Conf.samplePath, gDirname(fname).c_str()); + + int result = ch->load(fname); + + if (result == SAMPLE_LOADED_OK) + mainWin->keyboard->updateChannel(ch->guiChannel); + + return result; +} + + +/* -------------------------------------------------------------------------- */ + + +Channel *glue_addChannel(int column, int type) +{ + Channel *ch = G_Mixer.addChannel(type); + gChannel *gch = mainWin->keyboard->addChannel(column, ch); + ch->guiChannel = gch; + glue_setChanVol(ch, 1.0, false); // false = not from gui click + return ch; +} + + +/* -------------------------------------------------------------------------- */ + + +int glue_loadPatch(const char *fname, const char *fpath, gProgress *status, bool isProject) +{ + /* update browser's status bar with % 0.1 */ + + status->show(); + status->value(0.1f); + //Fl::check(); + Fl::wait(0); + + /* is it a valid patch? */ + + int res = G_Patch.open(fname); + if (res != PATCH_OPEN_OK) + return res; + + /* close all other windows. This prevents segfault if plugin windows + * GUI are on. */ + + if (res) + 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); + + status->value(0.2f); // progress status: % 0.2 + //Fl::check(); + Fl::wait(0); + + /* mixerHandler will update the samples inside Mixer */ + + mh_loadPatch(isProject, fname); + + /* take the patch name and update the main window's title */ + + G_Patch.getName(); + gu_update_win_label(G_Patch.name); + + status->value(0.4f); // progress status: 0.4 + //Fl::check(); + Fl::wait(0); + + G_Patch.readRecs(); + status->value(0.6f); // progress status: 0.6 + //Fl::check(); + Fl::wait(0); + +#ifdef WITH_VST + int resPlugins = G_Patch.readPlugins(); + status->value(0.8f); // progress status: 0.8 + //Fl::check(); + Fl::wait(0); +#endif + + /* this one is vital: let recorder recompute the actions' positions if + * the current samplerate != patch samplerate */ + + recorder::updateSamplerate(G_Conf.samplerate, G_Patch.samplerate); + + /* update gui */ + + gu_updateControls(); + + status->value(1.0f); // progress status: 1.0 (done) + //Fl::check(); + Fl::wait(0); + + /* save patchPath by taking the last dir of the broswer, in order to + * reuse it the next time */ + + G_Conf.setPath(G_Conf.patchPath, fpath); + + gLog("[glue] patch %s loaded\n", fname); + +#ifdef WITH_VST + if (resPlugins != 1) + gdAlert("Some VST plugins were not loaded successfully."); +#endif + + return res; +} + + +/* -------------------------------------------------------------------------- */ + + +int glue_savePatch(const char *fullpath, const char *name, bool isProject) +{ + if (G_Patch.write(fullpath, name, isProject) == 1) { + strcpy(G_Patch.name, name); + G_Patch.name[strlen(name)] = '\0'; + gu_update_win_label(name); + gLog("[glue] patch saved as %s\n", fullpath); + return 1; + } + else + return 0; +} + + +/* -------------------------------------------------------------------------- */ + + +void glue_deleteChannel(Channel *ch) +{ + int index = ch->index; + recorder::clearChan(index); + Fl::lock(); + mainWin->keyboard->deleteChannel(ch->guiChannel); + Fl::unlock(); + G_Mixer.deleteChannel(ch); + gu_closeAllSubwindows(); +} + + +/* -------------------------------------------------------------------------- */ + + +void glue_freeChannel(Channel *ch) +{ + mainWin->keyboard->freeChannel(ch->guiChannel); + recorder::clearChan(ch->index); + ch->empty(); +} + + +/* -------------------------------------------------------------------------- */ + + +void glue_setBpm(const char *v1, const char *v2) +{ + char buf[6]; + float value = atof(v1) + (atof(v2)/10); + if (value < 20.0f) { + value = 20.0f; + sprintf(buf, "20.0"); + } + else + sprintf(buf, "%s.%s", v1, !strcmp(v2, "") ? "0" : v2); + + /* a value such as atof("120.1") will never be 120.1 but 120.0999999, + * because of the rounding error. So we pass the real "wrong" value to + * G_Mixer and we show the nice looking (but fake) one to the GUI. */ + + float old_bpm = G_Mixer.bpm; + G_Mixer.bpm = value; + G_Mixer.updateFrameBars(); + + /* inform recorder and actionEditor of the change */ + + recorder::updateBpm(old_bpm, value, G_Mixer.quanto); + gu_refreshActionEditor(); + + mainWin->timing->setBpm(buf); + gLog("[glue] Bpm changed to %s (real=%f)\n", buf, G_Mixer.bpm); +} + + +/* -------------------------------------------------------------------------- */ + + +void glue_setBeats(int beats, int bars, bool expand) +{ + /* temp vars to store old data (they are necessary) */ + + int oldvalue = G_Mixer.beats; + unsigned oldfpb = G_Mixer.totalFrames; + + if (beats > MAX_BEATS) + G_Mixer.beats = MAX_BEATS; + else if (beats < 1) + G_Mixer.beats = 1; + else + G_Mixer.beats = beats; + + /* update bars - bars cannot be greate than beats and must be a sub + * multiple of beats. If not, approximation to the nearest (and greater) + * value available. */ + + if (bars > G_Mixer.beats) + G_Mixer.bars = G_Mixer.beats; + else if (bars <= 0) + G_Mixer.bars = 1; + else if (beats % bars != 0) { + G_Mixer.bars = bars + (beats % bars); + if (beats % G_Mixer.bars != 0) // it could be an odd value, let's check it (and avoid it) + G_Mixer.bars = G_Mixer.bars - (beats % G_Mixer.bars); + } + else + G_Mixer.bars = bars; + + G_Mixer.updateFrameBars(); + + /* update recorded actions */ + + if (expand) { + if (G_Mixer.beats > oldvalue) + recorder::expand(oldfpb, G_Mixer.totalFrames); + //else if (G_Mixer.beats < oldvalue) + // recorder::shrink(G_Mixer.totalFrames); + } + + mainWin->timing->setMeter(G_Mixer.beats, G_Mixer.bars); + gu_refreshActionEditor(); // in case the action editor is open +} + + +/* -------------------------------------------------------------------------- */ + + +void glue_startStopSeq(bool gui) +{ + G_Mixer.running ? glue_stopSeq(gui) : glue_startSeq(gui); +} + + +/* -------------------------------------------------------------------------- */ + + +void glue_startSeq(bool gui) +{ + G_Mixer.running = true; + + if (gui) { +#ifdef __linux__ + kernelAudio::jackStart(); +#endif + } + + if (G_Conf.midiSync == MIDI_SYNC_CLOCK_M) { + kernelMidi::send(MIDI_START, -1, -1); + kernelMidi::send(MIDI_POSITION_PTR, 0, 0); + } + + if (gui) Fl::lock(); + mainWin->controller->updatePlay(1); + if (gui) Fl::unlock(); +} + + +/* -------------------------------------------------------------------------- */ + + +void glue_stopSeq(bool gui) { + + mh_stopSequencer(); + + if (G_Conf.midiSync == MIDI_SYNC_CLOCK_M) + kernelMidi::send(MIDI_STOP, -1, -1); + +#ifdef __linux__ + if (gui) + 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; + if (gui) Fl::lock(); + mainWin->controller->updateRecAction(0); + if (gui) 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 (G_Mixer.chanInput != NULL) { + mh_stopInputRec(); + if (gui) Fl::lock(); + mainWin->controller->updateRecInput(0); + if (gui) Fl::unlock(); + } + + if (gui) Fl::lock(); + mainWin->controller->updatePlay(0); + if (gui) Fl::unlock(); +} + + +/* -------------------------------------------------------------------------- */ + + +void glue_rewindSeq() { + mh_rewindSequencer(); + if (G_Conf.midiSync == MIDI_SYNC_CLOCK_M) + kernelMidi::send(MIDI_POSITION_PTR, 0, 0); +} + + +/* -------------------------------------------------------------------------- */ + + +void glue_startStopActionRec() { + recorder::active ? glue_stopActionRec() : glue_startActionRec(); +} + + +/* -------------------------------------------------------------------------- */ + + +void glue_startActionRec() { + if (G_audio_status == false) + return; + if (!G_Mixer.running) + glue_startSeq(); // start the sequencer for convenience + recorder::active = true; + + Fl::lock(); + mainWin->controller->updateRecAction(1); + Fl::unlock(); +} + + +/* -------------------------------------------------------------------------- */ + + +void glue_stopActionRec() { + + /* stop the recorder and sort new actions */ + + recorder::active = false; + recorder::sortActions(); + + for (unsigned i=0; itype == CHANNEL_SAMPLE) { + SampleChannel *ch = (SampleChannel*) G_Mixer.channels.at(i); + if (ch->hasActions) + ch->readActions = true; + else + ch->readActions = false; + mainWin->keyboard->setChannelWithActions((gSampleChannel*)ch->guiChannel); + } + + Fl::lock(); + mainWin->controller->updateRecAction(0); + Fl::unlock(); + + /* in case acton editor is on, refresh it */ + + gu_refreshActionEditor(); +} + + +/* -------------------------------------------------------------------------- */ + + +void glue_startStopReadingRecs(SampleChannel *ch, bool gui) { + if (ch->readActions) + glue_stopReadingRecs(ch, gui); + else + glue_startReadingRecs(ch, gui); +} + + +/* -------------------------------------------------------------------------- */ + + +void glue_startReadingRecs(SampleChannel *ch, bool gui) { + if (G_Conf.treatRecsAsLoops) + ch->recStatus = REC_WAITING; + else + ch->setReadActions(true); + if (!gui) { + gSampleChannel *gch = (gSampleChannel*)ch->guiChannel; + if (gch->readActions) { // if button exists + Fl::lock(); + gch->readActions->value(1); + Fl::unlock(); + } + } +} + + +/* -------------------------------------------------------------------------- */ + + +void glue_stopReadingRecs(SampleChannel *ch, bool gui) { + + /* if "treatRecsAsLoop" wait until the sequencer reaches beat 0, so put + * the channel in REC_ENDING status */ + + if (G_Conf.treatRecsAsLoops) + ch->recStatus = REC_ENDING; + else + ch->setReadActions(false); + if (!gui) { + gSampleChannel *gch = (gSampleChannel*)ch->guiChannel; + if (gch->readActions) { // if button exists + Fl::lock(); + gch->readActions->value(0); + Fl::unlock(); + } + } +} + + +/* -------------------------------------------------------------------------- */ + + +void glue_quantize(int val) { + G_Mixer.quantize = val; + G_Mixer.updateQuanto(); +} + + +/* -------------------------------------------------------------------------- */ + + +void glue_setChanVol(Channel *ch, float v, bool gui) { + + ch->volume = v; + + /* also update wave editor if it's shown */ + + gdEditor *editor = (gdEditor*) gu_getSubwindow(mainWin, WID_SAMPLE_EDITOR); + if (editor) { + glue_setVolEditor(editor, (SampleChannel*) ch, v, false); + Fl::lock(); + editor->volume->value(v); + Fl::unlock(); + } + + if (!gui) { + Fl::lock(); + ch->guiChannel->vol->value(v); + Fl::unlock(); + } +} + + +/* -------------------------------------------------------------------------- */ + + +void glue_setOutVol(float v, bool gui) { + G_Mixer.outVol = v; + if (!gui) { + Fl::lock(); + mainWin->inOut->setOutVol(v); + Fl::unlock(); + } +} + + +/* -------------------------------------------------------------------------- */ + + +void glue_setInVol(float v, bool gui) +{ + G_Mixer.inVol = v; + if (!gui) { + Fl::lock(); + mainWin->inOut->setInVol(v); + Fl::unlock(); + } +} + + +/* -------------------------------------------------------------------------- */ + + +void glue_clearAllSamples() +{ + G_Mixer.running = false; + for (unsigned i=0; iempty(); + G_Mixer.channels.at(i)->guiChannel->reset(); + } + recorder::init(); + return; +} + + +/* -------------------------------------------------------------------------- */ + + +void glue_clearAllRecs() +{ + recorder::init(); + gu_updateControls(); +} + + +/* -------------------------------------------------------------------------- */ + + +void glue_resetToInitState(bool resetGui, bool createColumns) +{ + G_Mixer.ready = false; + + mh_clear(); + mainWin->keyboard->clear(); + if (createColumns) + mainWin->keyboard->init(); + recorder::init(); + G_Patch.setDefault(); + G_Mixer.init(); +#ifdef WITH_VST + G_PluginHost.freeAllStacks(); +#endif + + if (resetGui) + gu_updateControls(); + + G_Mixer.ready = true; +} + + +/* -------------------------------------------------------------------------- */ + + +void glue_startStopMetronome(bool gui) +{ + G_Mixer.metronome = !G_Mixer.metronome; + if (!gui) { + Fl::lock(); + mainWin->controller->updateMetronome(G_Mixer.metronome); + Fl::unlock(); + } +} + + +/* -------------------------------------------------------------------------- */ + + +void glue_setBeginEndChannel(gdEditor *win, SampleChannel *ch, int b, int e, bool recalc, bool check) +{ + if (check) { + if (e > ch->wave->size) + e = ch->wave->size; + if (b < 0) + b = 0; + if (b > ch->wave->size) + b = ch->wave->size-2; + if (b >= ch->end) + b = ch->begin; + if (e <= ch->begin) + e = ch->end; + } + + /* continue only if new values != old values */ + + if (b == ch->begin && e == ch->end) + return; + + /* print mono values */ + + char tmp[16]; + sprintf(tmp, "%d", b/2); + win->chanStart->value(tmp); + + tmp[0] = '\0'; + sprintf(tmp, "%d", e/2); + win->chanEnd->value(tmp); + + ch->setBegin(b); + ch->setEnd(e); + + /* recalc is not needed when the user drags the bars directly over the waveform */ + + if (recalc) { + win->waveTools->waveform->recalcPoints(); // importante, altrimenti non si vedono + win->waveTools->waveform->redraw(); + } +} + + +/* -------------------------------------------------------------------------- */ + + +void glue_setBoost(gdEditor *win, SampleChannel *ch, float val, bool numeric) +{ + if (numeric) { + if (val > 20.0f) + val = 20.0f; + else if (val < 0.0f) + val = 0.0f; + + float linear = pow(10, (val / 20)); // linear = 10^(dB/20) + + ch->boost = linear; + + char buf[10]; + sprintf(buf, "%.2f", val); + win->boostNum->value(buf); + win->boostNum->redraw(); + + win->boost->value(linear); + win->boost->redraw(); /// inutile + } + else { + ch->boost = val; + char buf[10]; + sprintf(buf, "%.2f", 20*log10(val)); + win->boostNum->value(buf); + win->boostNum->redraw(); + } +} + + +/* -------------------------------------------------------------------------- */ + + +void glue_setVolEditor(class gdEditor *win, SampleChannel *ch, float val, bool numeric) +{ + if (numeric) { + if (val > 0.0f) + val = 0.0f; + else if (val < -60.0f) + val = -INFINITY; + + float linear = pow(10, (val / 20)); // linear = 10^(dB/20) + + ch->volume = linear; + + win->volume->value(linear); + win->volume->redraw(); + + char buf[10]; + if (val > -INFINITY) + sprintf(buf, "%.2f", val); + else + sprintf(buf, "-inf"); + win->volumeNum->value(buf); + win->volumeNum->redraw(); + + ch->guiChannel->vol->value(linear); + ch->guiChannel->vol->redraw(); + } + else { + ch->volume = val; + + float dbVal = 20 * log10(val); + char buf[10]; + if (dbVal > -INFINITY) + sprintf(buf, "%.2f", dbVal); + else + sprintf(buf, "-inf"); + + win->volumeNum->value(buf); + win->volumeNum->redraw(); + + ch->guiChannel->vol->value(val); + ch->guiChannel->vol->redraw(); + } +} + + +/* -------------------------------------------------------------------------- */ + + +void glue_setMute(Channel *ch, bool gui) +{ + if (recorder::active && recorder::canRec(ch)) { + if (!ch->mute) + recorder::startOverdub(ch->index, ACTION_MUTES, G_Mixer.actualFrame); + else + recorder::stopOverdub(G_Mixer.actualFrame); + } + + ch->mute ? ch->unsetMute(false) : ch->setMute(false); + + if (!gui) { + Fl::lock(); + ch->guiChannel->mute->value(ch->mute); + Fl::unlock(); + } +} + + +/* -------------------------------------------------------------------------- */ + + +void glue_setSoloOn(Channel *ch, bool gui) +{ + /* if there's no solo session, store mute configuration of all chans + * and start the session */ + + if (!__soloSession__) { + for (unsigned i=0; imute_s = och->mute; + } + __soloSession__ = true; + } + + ch->solo = !ch->solo; + + /* mute all other channels and unmute this (if muted) */ + + for (unsigned i=0; isolo && !och->mute) { + och->setMute(false); + Fl::lock(); + och->guiChannel->mute->value(true); + Fl::unlock(); + } + } + + if (ch->mute) { + ch->unsetMute(false); + Fl::lock(); + ch->guiChannel->mute->value(false); + Fl::unlock(); + } + + if (!gui) { + Fl::lock(); + ch->guiChannel->solo->value(1); + Fl::unlock(); + } +} + + +/* -------------------------------------------------------------------------- */ + + +void glue_setSoloOff(Channel *ch, bool gui) +{ + /* if this is uniqueSolo, stop solo session and restore mute status, + * else mute this */ + + if (mh_uniqueSolo(ch)) { + __soloSession__ = false; + for (unsigned i=0; imute_s) { + och->setMute(false); + Fl::lock(); + och->guiChannel->mute->value(true); + Fl::unlock(); + } + else { + och->unsetMute(false); + Fl::lock(); + och->guiChannel->mute->value(false); + Fl::unlock(); + } + och->mute_s = false; + } + } + else { + ch->setMute(false); + Fl::lock(); + ch->guiChannel->mute->value(true); + Fl::unlock(); + } + + ch->solo = !ch->solo; + + if (!gui) { + Fl::lock(); + ch->guiChannel->solo->value(0); + Fl::unlock(); + } +} + + +/* -------------------------------------------------------------------------- */ + + +void glue_setPanning(class gdEditor *win, SampleChannel *ch, float val) +{ + if (val < 1.0f) { + ch->panLeft = 1.0f; + ch->panRight= 0.0f + val; + + char buf[8]; + sprintf(buf, "%d L", abs((ch->panRight * 100.0f) - 100)); + win->panNum->value(buf); + } + else if (val == 1.0f) { + ch->panLeft = 1.0f; + ch->panRight= 1.0f; + win->panNum->value("C"); + } + else { + ch->panLeft = 2.0f - val; + ch->panRight= 1.0f; + + char buf[8]; + sprintf(buf, "%d R", abs((ch->panLeft * 100.0f) - 100)); + win->panNum->value(buf); + } + win->panNum->redraw(); +} + + +/* -------------------------------------------------------------------------- */ + + +void glue_startStopInputRec(bool gui, bool alert) +{ + if (G_Mixer.chanInput == NULL) { + if (!glue_startInputRec(gui)) { + if (alert) gdAlert("No channels available for recording."); + else gLog("[glue] no channels available for recording\n"); + } + } + else + glue_stopInputRec(gui); +} + + +/* -------------------------------------------------------------------------- */ + + +int glue_startInputRec(bool gui) +{ + if (G_audio_status == false) + return -1; + + SampleChannel *ch = mh_startInputRec(); + if (ch == NULL) { // no chans available + Fl::lock(); + mainWin->controller->updateRecInput(0); + Fl::unlock(); + return 0; + } + + if (!G_Mixer.running) { + glue_startSeq(); + Fl::lock(); + mainWin->controller->updatePlay(1); + Fl::unlock(); + } + + glue_setChanVol(ch, 1.0f, false); // false = not from gui click + + ch->guiChannel->mainButton->label(ch->wave->name.c_str()); + + if (!gui) { + Fl::lock(); + mainWin->controller->updateRecInput(1); + Fl::unlock(); + } + + return 1; + +} + + +/* -------------------------------------------------------------------------- */ + + +int glue_stopInputRec(bool gui) +{ + SampleChannel *ch = mh_stopInputRec(); + + if (ch->mode & (LOOP_BASIC | LOOP_ONCE | LOOP_REPEAT)) + ch->start(0, true); // on frame 0: user-generated event + + if (!gui) { + Fl::lock(); + mainWin->controller->updateRecInput(0); + Fl::unlock(); + } + + return 1; +} + + +/* -------------------------------------------------------------------------- */ + + +int glue_saveProject(const char *folderPath, const char *projName) +{ + if (gIsProject(folderPath)) { + gLog("[glue] the project folder already exists\n"); + // don't exit + } + else if (!gMkdir(folderPath)) { + gLog("[glue] unable to make project directory!\n"); + return 0; + } + + /* copy all samples inside the folder. Takes and logical ones are saved + * via glue_saveSample() */ + + for (unsigned i=0; itype == CHANNEL_SAMPLE) { + + SampleChannel *ch = (SampleChannel*) G_Mixer.channels.at(i); + + if (ch->wave == NULL) + continue; + + /* update the new samplePath: everything now comes from the + * project folder (folderPath) */ + + char samplePath[PATH_MAX]; + sprintf(samplePath, "%s%s%s.%s", folderPath, gGetSlash().c_str(), ch->wave->basename().c_str(), ch->wave->extension().c_str()); + + /* remove any existing file */ + + if (gFileExists(samplePath)) + remove(samplePath); + if (ch->save(samplePath)) + ch->wave->pathfile = samplePath; + } + } + + char gptcPath[PATH_MAX]; + sprintf(gptcPath, "%s%s%s.gptc", folderPath, gGetSlash().c_str(), gStripExt(projName).c_str()); + glue_savePatch(gptcPath, projName, true); // true == it's a project + + return 1; +} + + +/* -------------------------------------------------------------------------- */ + + +void glue_keyPress(Channel *ch, bool ctrl, bool shift) +{ + if (ch->type == CHANNEL_SAMPLE) + glue_keyPress((SampleChannel*)ch, ctrl, shift); + else + glue_keyPress((MidiChannel*)ch, ctrl, shift); +} + + +/* -------------------------------------------------------------------------- */ + + +void glue_keyRelease(Channel *ch, bool ctrl, bool shift) +{ + if (ch->type == CHANNEL_SAMPLE) + glue_keyRelease((SampleChannel*)ch, ctrl, shift); +} + + +/* -------------------------------------------------------------------------- */ + + +void glue_keyPress(MidiChannel *ch, bool ctrl, bool shift) +{ + if (ctrl) + glue_setMute(ch); + else + if (shift) + ch->kill(0); // on frame 0: user-generated event + else + ch->start(0, true); // on frame 0: user-generated event +} + + +/* -------------------------------------------------------------------------- */ + + +void glue_keyPress(SampleChannel *ch, bool ctrl, bool shift) +{ + /* case CTRL */ + + if (ctrl) + glue_setMute(ch); + + /* case SHIFT + * + * action recording on: + * if seq is playing, rec a killchan + * action recording off: + * if chan has recorded events: + * | if seq is playing OR channel 'c' is stopped, de/activate recs + * | else kill chan + * else kill chan */ + + else + if (shift) { + if (recorder::active) { + if (G_Mixer.running) { + ch->kill(0); // on frame 0: user-generated event + if (recorder::canRec(ch) && !(ch->mode & LOOP_ANY)) // don't record killChan actions for LOOP channels + recorder::rec(ch->index, ACTION_KILLCHAN, G_Mixer.actualFrame); + } + } + else { + if (ch->hasActions) { + if (G_Mixer.running || ch->status == STATUS_OFF) + ch->readActions ? glue_stopReadingRecs(ch) : glue_startReadingRecs(ch); + else + ch->kill(0); // on frame 0: user-generated event + } + else + ch->kill(0); // on frame 0: user-generated event + } + } + + /* case no modifier */ + + else { + + /* record now if the quantizer is off, otherwise let mixer to handle it + * when a quantoWait has passed. Moreover, KEYPRESS and KEYREL are + * meaningless for loop modes */ + + if (G_Mixer.quantize == 0 && + recorder::canRec(ch) && + !(ch->mode & LOOP_ANY)) + { + if (ch->mode == SINGLE_PRESS) + recorder::startOverdub(ch->index, ACTION_KEYS, G_Mixer.actualFrame); + else + recorder::rec(ch->index, ACTION_KEYPRESS, G_Mixer.actualFrame); + } + + ch->start(0, true); // on frame 0: user-generated event + } + + /* the GUI update is done by gui_refresh() */ +} + + +/* -------------------------------------------------------------------------- */ + + +void glue_keyRelease(SampleChannel *ch, bool ctrl, bool shift) +{ + if (!ctrl && !shift) { + ch->stop(); + + /* record a key release only if channel is single_press. For any + * other mode the KEY REL is meaningless. */ + + if (ch->mode == SINGLE_PRESS && recorder::canRec(ch)) + recorder::stopOverdub(G_Mixer.actualFrame); + } + + /* the GUI update is done by gui_refresh() */ + +} + + +/* -------------------------------------------------------------------------- */ + + +void glue_setPitch(class gdEditor *win, SampleChannel *ch, float val, bool numeric) +{ + if (numeric) { + if (val <= 0.0f) + val = 0.1000f; + if (val > 4.0f) + val = 4.0000f; + if (win) + win->pitch->value(val); + } + + ch->setPitch(val); + + if (win) { + char buf[16]; + sprintf(buf, "%.4f", val); + Fl::lock(); + win->pitchNum->value(buf); + win->pitchNum->redraw(); + Fl::unlock(); + } +} + + +/* -------------------------------------------------------------------------- */ + + +/* never expand or shrink recordings (last param of setBeats = false): + * this is live manipulation */ + +void glue_beatsMultiply() +{ + glue_setBeats(G_Mixer.beats*2, G_Mixer.bars, false); +} + +void glue_beatsDivide() +{ + glue_setBeats(G_Mixer.beats/2, G_Mixer.bars, false); +} diff --git a/src/glue.h b/src/glue.h new file mode 100644 index 0000000..d018a6c --- /dev/null +++ b/src/glue.h @@ -0,0 +1,169 @@ +/* --------------------------------------------------------------------- + * + * 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-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + + +#ifndef GLUE_H +#define GLUE_H + +/* addChannel + * add an empty new channel to the stack. Returns the new channel. */ + +class Channel *glue_addChannel(int column, int type); + +/* loadChannel + * fill an existing channel with a wave. */ + +int glue_loadChannel(class SampleChannel *ch, const char *fname); + +void glue_deleteChannel(class Channel *ch); + +void glue_freeChannel(class Channel *ch); + +/** FIXME - nobody will call these via MIDI/keyb/mouse! */ +int glue_loadPatch(const char *fname, const char *fpath, class gProgress *status, bool isProject); +int glue_savePatch(const char *fullpath, const char *name, bool isProject); + +/* keyPress / keyRelease + * handle the key pressure, either via mouse/keyboard or MIDI. If gui + * is true it means that the event comes from the main window (mouse, + * keyb or MIDI), otherwise the event comes from the action recorder. */ + +void glue_keyPress (class Channel *ch, bool ctrl=0, bool shift=0); +void glue_keyPress (class SampleChannel *ch, bool ctrl=0, bool shift=0); +void glue_keyPress (class MidiChannel *ch, bool ctrl=0, bool shift=0); +void glue_keyRelease(class Channel *ch, bool ctrl=0, bool shift=0); +void glue_keyRelease(class SampleChannel *ch, bool ctrl=0, bool shift=0); + +void glue_setBpm(const char *v1, const char *v2); +void glue_setBeats(int beats, int bars, bool expand); + +/* start, stop, rewind sequencer + * if gui == true the signal comes from an internal 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 (); + +/* start/stopActionRec + * handle the action recording. */ + +void glue_startStopActionRec(); +void glue_startActionRec(); +void glue_stopActionRec(); + +/* start/stopInputRec + * handle 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. Alert displays or not the popup message + * if there are no available channels. */ + +void glue_startStopInputRec(bool gui=true, bool alert=true); +int glue_startInputRec (bool gui=true); +int glue_stopInputRec (bool gui=true); + +/* start/stopReadingRecs + * handle the 'R' button. If gui == true the signal comes from an + * internal interaction on the GUI, otherwise it's a MIDI/Jack/external + * signal. */ + +void glue_startStopReadingRecs(class SampleChannel *ch, bool gui=true); +void glue_startReadingRecs (class SampleChannel *ch, bool gui=true); +void glue_stopReadingRecs (class SampleChannel *ch, bool gui=true); + +void glue_quantize(int val); + +void glue_setChanVol(class Channel *ch, float v, bool gui=true); +void glue_setOutVol (float v, bool gui=true); +void glue_setInVol (float v, bool gui=true); + +void glue_setPanning(class gdEditor *win, class SampleChannel *ch, float val); + +void glue_clearAllSamples(); +void glue_clearAllRecs(); + +/* resetToInitState + * reset Giada to init state. If resetGui also refresh all widgets. If + * createColumns also build initial empty columns. */ + +void glue_resetToInitState(bool resetGui=true, bool createColumns=true); + +void glue_startStopMetronome(bool gui=true); + +/* setBeginEndChannel + * sets start/end points in the sample editor. + * Recalc=false: don't recalc internal position + * check=true: check the points' consistency */ + +/** FIXME - nobody will call this via MIDI/keyb/mouse! */ +void glue_setBeginEndChannel(class gdEditor *win, class SampleChannel *ch, int b, int e, + bool recalc=false, bool check=true); + +/** FIXME - nobody will call this via MIDI/keyb/mouse! */ +void glue_setBoost(class gdEditor *win, class SampleChannel *ch, float val, bool numeric); + +void glue_setPitch(class gdEditor *win, class SampleChannel *ch, float val, bool numeric); + +/* setVolEditor + * handles the volume inside the SAMPLE EDITOR (not the main gui). The + * numeric flag tells if we want to handle the dial or the numeric input + * field. */ + + /** FIXME - nobody will call this via MIDI/keyb/mouse! */ +void glue_setVolEditor(class gdEditor *win, class SampleChannel *ch, float val, bool numeric); + +/* mute + * set mute on or off. If gui == true the signal comes from an internal + * interaction on the GUI, otherwise it's a MIDI/Jack/external signal. */ + +void glue_setMute(class Channel *ch, bool gui=true); + +/* solo on/off + * set solo on and off. If gui == true the signal comes from an internal + * interaction on the GUI, otherwise it's a MIDI/Jack/external signal. */ + +void glue_setSoloOn (class Channel *ch, bool gui=true); +void glue_setSoloOff(class Channel *ch, bool gui=true); + +/** FIXME - nobody will call this via MIDI/keyb/mouse! */ +int glue_saveProject(const char *folderPath, const char *projName); + + +/* beatsDivide/Multiply + * shrinks or enlarges the number of beats by 2. */ + +void glue_beatsMultiply(); +void glue_beatsDivide(); + +#endif diff --git a/src/graphics.cpp b/src/graphics.cpp new file mode 100644 index 0000000..82ca2bc --- /dev/null +++ b/src/graphics.cpp @@ -0,0 +1,1631 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * graphics + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + +#include "graphics.h" + +const char *giada_logo_xpm[] = { +"300 82 8 1", +" c #181917", +". c #333432", +"+ c #484A47", +"@ c #5F615E", +"# c #767875", +"$ c #8D8F8C", +"% c #A5A7A4", +"& c #C5C7C4", +" .#%%&$ ", +" ..#%&&&&&# ", +" +@#$%&&&&&&&&&@ ", +" .. +&&&&&&&&&&&&&&. ", +" +$%%#+ +%&&&&&&&&&&&&&. ", +" #&&&&&%+ .+@@$&&&&&&&&&%. ", +" #&&&&&&&$ +&&&&&&&&# ", +" .&&&&&&&&%. #&&&&&&&@ ", +" @&&&&&&&&$ +&&&&&&&+ ", +" $&&&&&&&&# .&&&&&&&. ", +" #&&&&&&&&. +&&&&&&%. ", +" .&&&&&&&@ @&&&&&&$. ", +" @&&&&&@ $&&&&&&# ", +" .##@. %&&&&&&@ ", +" &&&&&&&+ ", +" .&&&&&&%. ", +" @&&&&&&$. ", +" .+@@###@+. ...... ... ++@@###@@. ....... .+@@###@@+. #&&&&&&# .+@@###@@+ ....... ", +" .@$%%&&&&&&&%$+ +%%%%%%. .+@#$%%$. .@$%&&&&&&&&%$@. $%%%%%%+ .@#%%&&&&&&&&%$@ %&&&&&&@ .@$%%&&&&&&&%$#. @%%%%%%@ ", +" #%&&&&&&&&&&&&&&$. #&&&&&& +@#$$%%%&&&&&%. .$%&&&&&&&&&&&&&&%@ .&&&&&&&+ #%&&&&&&&&&&&&&&&$+ &&&&&&&@ #%&&&&&&&&&&&&&&%#. %&&&&&&@ ", +" +%&&&&&&&&&&&&&&&&&%@ .&&&&&&% .%&&&&&&&&&&&&$ #%&&&&&&&&&&&&&&&&&&$ +&&&&&&%. @%&&&&&&&&&&&&&&&&&&&# .&&&&&&%+ @%&&&&&&&&&&&&&&&&&&%. &&&&&&&+ ", +" #&&&&&&&&&#+....@$&&&&@@&&&&&&$ .&&&&&&&&&&&&&# +%&&&&&&&&%#+....+#%&&&$#&&&&&&$. .$&&&&&&&&&$+.....@%&&&&$@&&&&&&%. .$&&&&&&&&&#@.....#%&&&%+&&&&&&%. ", +" $&&&&&&&&@ .%&&&&&&&&&&@ .@$&&&&&&&&&&@ +%&&&&&&&%@ .#&&&&&&&&&&$ .%&&&&&&&&@ .$&&&&&&&&&&$ .%&&&&&&&&#. @&&&&&&&&&&%. ", +" $&&&&&&&%. @&&&&&&&&&+ .%&&&&&&&%+ @&&&&&&&&# +%&&&&&&&&# +%&&&&&&&$. @&&&&&&&&&# .%&&&&&&&$. .%&&&&&&&&$ ", +" %&&&&&&&# @&&&&&&&&. +%&&&&&&%. @&&&&&&&&# .%&&&&&&&@ +%&&&&&&&# @&&&&&&&&@ +%&&&&&&&# %&&&&&&&@ ", +" $&&&&&&&$ #&&&&&&%. %&&&&&&% +&&&&&&&&@ +&&&&&&%+ .%&&&&&&&# @&&&&&&&+ .%&&&&&&&# &&&&&&&+ ", +" @&&&&&&&$. #&&&&&&$ %&&&&&&$ .%&&&&&&&@ @&&&&&&%. .$&&&&&&&$ +&&&&&&%+ $&&&&&&&$ .&&&&&&&+ ", +" +&&&&&&&%+ %&&&&&&# %&&&&&&# $&&&&&&&$ $&&&&&&% @&&&&&&&% #&&&&&&%. #&&&&&&&%. @&&&&&&%. ", +" %&&&&&&&# &&&&&&&+ .%&&&&&&@ @&&&&&&&$ %&&&&&&$ +&&&&&&&&+ $&&&&&&$ +&&&&&&&%. $&&&&&&$. ", +" @&&&&&&&%. .&&&&&&&. +%&&&&&%. .&&&&&&&&. .%&&&&&&# $&&&&&&&# %&&&&&&# .$&&&&&&&@ %&&&&&&# ", +" .%&&&&&&&@ @&&&&&&&. @&&&&&&% @&&&&&&&# @&&&&&&&+ +&&&&&&&&. +&&&&&&&@ +&&&&&&&% .&&&&&&&@ ", +" @&&&&&&&% $&&&&&&%. #&&&&&&% &&&&&&&&. #&&&&&&%. %&&&&&&&# @&&&&&&%+ .$&&&&&&&@ +&&&&&&&+ ", +" .$&&&&&&&# %&&&&&&# $&&&&&&# #&&&&&&&# $&&&&&&% +&&&&&&&&. $&&&&&&%. +&&&&&&&% #&&&&&&%+ ", +" +%&&&&&&&+ .&&&&&&&@ .%&&&&&&@ %&&&&&&&+ .%&&&&&&$ $&&&&&&&$ %&&&&&&% #&&&&&&&# %&&&&&&%. ", +" @&&&&&&&% +&&&&&&&+ +%&&&&&&+ .&&&&&&&%. +&&&&&&&# &&&&&&&&+ +%&&&&&&$ .%&&&&&&&. &&&&&&&$ ", +" $&&&&&&&@ #&&&&&&&. @&&&&&&&. #&&&&&&&# #&&&&&&&@ +&&&&&&&%. @&&&&&&&# .&&&&&&&% +&&&&&&&# ", +" .%&&&&&&&. %&&&&&&%. #&&&&&&% %&&&&&&&+ $&&&&&&&+ #&&&&&&&$. $&&&&&&&+ @&&&&&&&@ #&&&&&&&@ ", +" +&&&&&&&& &&&&&&&$. #&&&&&&$ &&&&&&&%. .%&&&&&&& %&&&&&&&# .%&&&&&&%. $&&&&&&&+ $&&&&&&%+ ", +" +&&&&&&&$ +&&&&&&&# .$&&&&&&# .&&&&&&&$. +&&&&&&&% %&&&&&&&+ +%&&&&&&% %&&&&&&&. .%&&&&&&%. ", +" @&&&&&&&@ $&&&&&&&@ .%&&&&&&+ +&&&&&&&# #&&&&&&&$ &&&&&&&&+ #&&&&&&&% &&&&&&&% @&&&&&&&$ ", +" @&&&&&&&+ &&&&&&&&+ +%&&&&&&. @&&&&&&&@ .%&&&&&&&@ .&&&&&&&%. .%&&&&&&&# &&&&&&&# $&&&&&&&$ ", +" #&&&&&&&. @&&&&&&&%. @&&&&&&& @&&&&&&&@ @&&&&&&&&+ .&&&&&&&%. @&&&&&&&&@ .&&&&&&&# +%&&&&&&&# ", +" #&&&&&&&. %&&&&&&&$. #&&&&&&% #&&&&&&&+ .$&&&&&&&&. .&&&&&&&$. .$&&&&&&&&. .&&&&&&&@ $&&&&&&&&@ ", +" #&&&&&&& #&&&&&&&&$ $&&&&&&# @&&&&&&&+ @&&&&&&&&& .&&&&&&&$. @&&&&&&&&& .&&&&&&&+ +%&&&&&&&%+ ", +" @&&&&&&& .%&&&&&&&&# .%&&&&&&@ @&&&&&&%+ .%&&&&&&&&% &&&&&&&$. .%&&&&&&&&% &&&&&&&+ $&&&&&&&&%. ", +" @&&&&&&&. $&&&&&&&&&@ +%&&&&&&. +&&&&&&&@ @&&&&&&&&&$ &&&&&&&%. #&&&&&&&&&$ &&&&&&&@ +&&&&&&&&&% ", +" +&&&&&&&+ @&&&&&&&&&%+ +&&&&&&& &&&&&&&@ +&&&&&&&&&&# $&&&&&&%+ @&&&&&&&&&&# $&&&&&&# .%&&&&&&&&&% ", +" .%&&&&&&@ +%&&$&&&&&&%. +&&&&&&& %&&&&&&# .&&&&&&&&&&&@ @&&&&&&&+ +&&&$%&&&&&&@ @&&&&&&$ .$&&&&&&&&&&$ ", +" #&&&&&&$ .$&&##&&&&&&$ @&&&&&&& @&&&&&&%. .%&&%@%&&&&&&@ .&&&&&&&# .&&&$+%&&&&&&. .&&&&&&&. .#&&%@%&&&&&&$ ", +" +&&&&&&&. .%&&% $&&&&&&# +&&&&&&&. +.&&&&&&&@ .%&&%+.%&&&&&&$ @+$&&&&&&&+ +&&&% +&&&&&&&+ .+ $&&&&&&$ .$&&&@ %&&&&&&% .# ", +" $&&&&&&$ +%&&&+ %&&&&&&@ +&&&&&&&@ #&$#&&&&&&%+ @&&&&@ .$&&&&&&&+ #&&@&&&&&&&$. .#&&&%. +%&&&&&&# .$&$ +&&&&&&&+ +%&&&# $&&&&&&&# +&&$ ", +" +%&&&&&&#. +#&&&%@ .%&&&&&&+ .%&&&&&&&+ .$&&%.%&&&&&&%+ +#&&&&@ #&&&&&&&%@..+@$&&&+@&&&&&&&%+ .@%&&&%+ .%&&&&&&&+ +%&&$. #&&&&&&%@ +#&&&&# @&&&&&&&&@...+#&&&# ", +" @&&&&&&&$@+..++#%&&&%+ +&&&&&&%. $&&&&&&&%#@@#%&&%. .%&&&&&&%@+...+@$&&&&%+ +%&&&&&&&&%$%&&&&+ $&&&&&&&&$#@+@@#%&&&&$. #&&&&&&&&#@+@$&&&$. .$&&&&&&&#+...++#%&&&&@ .%&&&&&&&&%$%&&&&# ", +" @&&&&&&&&%%%%&&&&&$. @&&&&&&% +%&&&&&&&&&&&&&$. +%&&&&&&&&%$%%&&&&&$. #&&&&&&&&&&&&&%+ .$&&&&&&&&&&&&&&&&&&# .%&&&&&&&&&&&&&&# .$&&&&&&&&%$%%&&&&&%+ @&&&&&&&&&&&&&%@ ", +" @%&&&&&&&&&&&&&%@. #&&&&&&$ #&&&&&&&&&&&%@. .$&&&&&&&&&&&&&&%@. .%&&&&&&&&&&&#. @&&&&&&&&&&&&&&&$+ +&&&&&&&&&&&&%+ .#&&&&&&&&&&&&&&&#. $&&&&&&&&&&&$+ ", +" .#%&&&&&&&&&%+. %&&&&&&# +%&&&&&&&%#. +$&&&&&&&&&&%@. .$&&&&&&&&#+ .#&&&&&&&&&&%#. .$&&&&&&&&%@. +$&&&&&&&&&&%@. .@&&&&&&&&$+. ", +" .+#$%%$#+.. .%&&&&&&+ .@#$%$#+. .@#$%%$#@+ +@$%$#+. .+#$%%$#@+. +@$%$$@. .+#$%%$#@+. .@$%$#@. ", +" +&&&&&&%. ", +" #&&&&&&% ", +" .%&&&&&&@ ", +" @&&&&&&%. ", +" $&&&&&&$ ", +" @&&&&&&&+ ", +" @#$#+ .$&&&&&&$ ", +" $&&&&&# #&&&&&&% ", +"#&&&&&&&@ @&&&&&&&+ ", +"%&&&&&&&%. @&&&&&&%+ ", +"%&&&&&&&&# #&&&&&&%. ", +"@&&&&&&&&&@ .$&&&&&&#. ", +" $&&&&&&&&&$+ +$&&&&&&%+ ", +" +&&&&&&&&&&%#@@#$%&&&&&&&#. +. .+ +@. ++ .+ +++++ +. .+ ++ +++++. .++++. +@. .@+ .++++. .+++++++ +. +@. .+@. .++++. ++. ++. .+. .+@. .+ +. .+ .+. .+ .+++++++ ", +" .$&&&&&&&&&&&&&&&&&&&%@ $%. %@ +&&%&%. .$$ +& .&&&&&&# .%@ +&. %&+ $&&&&&%. @&&&&&%. +&&%&%. .%&%&&+ #&&&&&&@ +&&&&&&%. &# +&&%&%. @&&%&%. +&&&&&&@ $&% +%&+ .$&@ @&&%&$. $% .%$ $% +&%. +%. +&&&&&&$. ", +" +$%%&&&&&&&&&&%$@. +&# #% +&# %% $$ +& .&+ @&@ .%@ +&. +$&$ $$ .%% @% .%&. .&$ .%$. %%. #&+ #& .$&+ +&. &# +&# $%. @&# .%%. +&. $&+ $%&+ #%&+ .&%$ @&# +%$ $% .%# $% +&%$ +%. +&. ", +" .++@###@@+. #&. .&+ $%. .&@ $$ +& .&+ %$ .%@ +&. ##$% $$ @&. @% %$ $%. @&. @&+ %$. #& .%@ +&. &# %% .&# %% .&@ +&. &# $#%$ $#&+ @%@%. %% @%+ $% .%# $% +&+%+ +%. +&. ", +" .%$ $$ .&# %% $$ +& .&+ %$ .%@ +&. .%@+&+ $$ @&. @% #& &# +. %% #&. #& .%@ +&. &# .&@ $$ +&+ .$$ +&. %# $#@& +$@&+ $#.%@ +&@ .+. $% .%# $% +& $$ +%. +&. ", +" @&#&. .&@ $& $$ +& .&#+++#&+ .%%####$&. +%. %$ $%.++@&$ @% +&..&@ &$ @&+ #&++++$%. +&$###@ &# +&+ #%.@&. #% +&+ ..#&+ $#.&@ ##+&+ .&..$$ @&. $&$###$&# $% +& +%@ +%. +&####+ ", +" %&@ +&+ #& $$ +& .&%$$%%@ .%%####$&. #$ #& $&$$%&#. @% +&++&@ &# +&+ #&$$%&%+ +&$$$$# &# @&. #%.@&. #%.+%%%%%%@ $# $% .$+.&+ @% @%. @&. $&$###$&# $% +& #%.+%. +&$$$$@ ", +" #&. .&@ $% $$ +& .&#+.$%. .%@ +&. .$%##$&+ $%.+@&@ @% +&..&@ &$ @&. #&+++$$ +&+ &# +&. #% @&. $$ +&#@@++ $# +&.+$.+&+ $%@#$&+ @&. $% .%# $% +& .%@+%. +&. ", +" @& .%# &$ $$ +% .&+ .%@ .%@ +&. +%$###&$ $$ $% @% $& &$ .$+ $% #%. #& +&+ +&. &# .&@ .%$ +&@ .%# +&. $# .%### +&+ .&####&# .&@ .$+ $% .%# $% +& @%@%. +&. ", +" @& #%. @&. #%. $% .&+ $% .%@ +&. @$. #& $$ +&+ @% +&@ #&. #&. +&+ .%@ #& .%# +&. &# $% +&+ %% @&+ +&. $# #&%+ +&+ @% #%. %% #%. $% .%# $% +& .$%%. +&. ", +" @& .%%+.@&# .%%++#&@ .&+ +&+ .%@ +&. $# .&+ $$ %$ @&+++#&%. $%+.#&# #&@.+%%. #& @%. +&@+++++. &$++++. .%%+.@%# .%%..@&# +&. $# +&$ +&+ %# +&+ .%%+.#&# $% .%# $% +& +&&. +&@++++. %&", +" @& .$&&%@ +%&&&@ .&+ %$ .%@ +&..%+ %$ $$ @&. @&&&&%@ .$%&&# @%&&$. #& .%@ +&&&&&&%+ &&&&&&$. .$&&%# .$&&%@ +&. $# .%# +&+.&. .%# .$&&&@ $% .%# $% +& $&. +&&&&&&%. &&"}; + + +const char *loopRepeat_xpm[] = { +"18 18 8 1", +" c #181917", +". c #242523", +"+ c #323331", +"@ c #4D4F4C", +"# c #646663", +"$ c #787A77", +"% c #919390", +"& c #BFC1BD", +"..................", +"..................", +"..................", +"...&%#......#%&...", +"...&&&%+..+%&&&...", +"...$%&&%..%&&%$...", +".....$&&##&&$.....", +"......%&%%&%......", +"......$&&&&$......", +"......$&&&&$......", +"......%&%%&%......", +".....$&&##&&$.....", +"...$%&&%..%&&%$...", +"...&&&%+..+%&&&...", +"...&%#......#%&...", +"..................", +"..................", +".................."}; + + +const char *loopBasic_xpm[] = { +"18 18 8 1", +" c #181917", +". c #242523", +"+ c #313230", +"@ c #4D4F4C", +"# c #666765", +"$ c #787A77", +"% c #919390", +"& c #BEC0BD", +"..................", +"..................", +"..................", +"......#%&&%#......", +"....+%&&&&&&%+....", +"....%&&&%%&&&%....", +"...#&&%+..+%&&#...", +"...%&&+....+&&%...", +"...&&%......%&&...", +"...&&%......%&&...", +"...%&&+....+&&%...", +"...#&&%+..+%&&#...", +"....%&&&%%&&&%....", +"....+%&&&&&&%+....", +"......#%&&%#......", +"..................", +"..................", +".................."}; + + +const char *loopOnce_xpm[] = { +"18 18 8 1", +" c #181917", +". c #242523", +"+ c #323331", +"@ c #4D4F4C", +"# c #646663", +"$ c #787A77", +"% c #919390", +"& c #BFC1BD", +"..................", +"..................", +"..................", +"......$%&&%#......", +"....+&&&&&&&&+....", +"...+&&&&$$&&&&+...", +"...$&&$....$&&$...", +"...%&&......%&%...", +"..................", +"..................", +"...%&&+.....&&&...", +"...#&&$....$&&#...", +"....%&&&%$&&&%....", +"....+%&&&&&&%+....", +"......#%&&%#......", +"..................", +"..................", +".................."}; + + +const char *loopOnceBar_xpm[] = { +"18 18 8 1", +" c #242523", +". c #393A38", +"+ c #545553", +"@ c #747673", +"# c #A3A5A2", +"$ c #ADAFAC", +"% c #B5B7B4", +"& c #C7C9C6", +" ", +" ", +" ", +" @$&%#@ ", +" .$&&&&&&$. ", +" %&&#@@#&&$ ", +" @&&@ @&&@ ", +" %&# +%$+ #&$ ", +" %&&% ", +" %&&% ", +" $&# +%%+ #&$ ", +" @&&@ @&&@ ", +" $&&#@@#&&$ ", +" .$&&&&&&$. ", +" @#&&#@ ", +" ", +" ", +" "}; + + +const char *oneshotBasic_xpm[] = { +"18 18 8 1", +" c #181917", +". c #242523", +"+ c #313230", +"@ c #4D4F4C", +"# c #666765", +"$ c #787A77", +"% c #919390", +"& c #BEC0BD", +"..................", +"..................", +"..................", +"..................", +"..................", +"..................", +"..................", +"..................", +"..................", +"..................", +"..................", +"..................", +"...$$$$$$$$$$$$...", +"...&&&&&&&&&&&&...", +"...&&&&&&&&&&&&...", +"..................", +"..................", +".................."}; + + +const char *oneshotRetrig_xpm[] = { +"18 18 8 1", +" c #181917", +". c #242523", +"+ c #313230", +"@ c #4D4F4C", +"# c #666765", +"$ c #787A77", +"% c #919390", +"& c #BEC0BD", +"..................", +"..................", +"..................", +"..................", +"..................", +"...$$$$$$$#@......", +"...&&&&&&&&&&@....", +"...&&&&&&&&&&&+...", +"..........+$&&%...", +"............%&&...", +"............%&&...", +"...........+&&%...", +"...$$$$$$$%&&&#...", +"...&&&&&&&&&&%....", +"...&&&&&&&&%#.....", +"..................", +"..................", +".................."}; + + +const char *oneshotPress_xpm[] = { +"18 18 8 1", +" c #181917", +". c #242523", +"+ c #313230", +"@ c #4D4F4C", +"# c #666765", +"$ c #787A77", +"% c #919390", +"& c #BEC0BD", +"..................", +"..................", +"..................", +"..................", +"..................", +"..................", +"...+%&%+..........", +"...%&&&%..........", +"...&&&&&..........", +"...$&&&$..........", +"...+$&$+..........", +"..................", +"...$$$$$$$$$$$$...", +"...&&&&&&&&&&&&...", +"...&&&&&&&&&&&&...", +"..................", +"..................", +".................."}; + + +const char *oneshotEndless_xpm[] = { +"18 18 6 1", +" c #242523", +". c #464745", +"+ c #6D6F6C", +"@ c #888A87", +"# c #ADAFAC", +"$ c #C6C8C5", +" ", +" ", +" ", +" ", +" ", +" .++. ", +" @$$$$#. ", +" @$$$$$$$. ", +" .$$#. +$$@ ", +" +$$. @$# ", +" +$$ @$$ ", +" .$$+ #$# ", +" @@@$$$@@#$$+ ", +" $$$$$$$$$$@ ", +" $$$$$$$$#+ ", +" ", +" ", +" "}; + + +const char *updirOff_xpm[] = { +"18 18 8 1", +" c #242523", +". c #332F2E", +"+ c #54494A", +"@ c #6B5A5C", +"# c #866C6B", +"$ c #967B7A", +"% c #987D7C", +"& c #B18E8F", +" ", +" ", +" ", +" ", +" @@ ", +" #&&# ", +" .#&&&&#. ", +" .$&&&&&&$. ", +" +@%&&&&%@+ ", +" #&&&&# ", +" #&&&&# ", +" #&&&&# ", +" #&&&&# ", +" #&&&&# ", +" .... ", +" ", +" ", +" "}; + + +const char *updirOn_xpm[] = { +"18 18 8 1", +" c #4D4F4C", +". c #555150", +"+ c #706465", +"@ c #7D6B6E", +"# c #877373", +"$ c #957978", +"% c #9F8382", +"& c #B18E8F", +" ", +" ", +" ", +" ", +" ## ", +" #&&# ", +" .$&&&&$. ", +" .%&&&&&&%. ", +" +@%&&&&%@+ ", +" $&&&&$ ", +" $&&&&$ ", +" $&&&&$ ", +" $&&&&$ ", +" $&&&&$ ", +" .... ", +" ", +" ", +" "}; + + +const char *pause_xpm[] = { +"23 23 8 1", +" c #4D4F4C", +". c #514E53", +"+ c #5C4F61", +"@ c #6F507E", +"# c #855098", +"$ c #9551AE", +"% c #A652C5", +"& c #AE52D1", +" ", +" ", +" ", +" ", +" ", +" #+ ", +" &%#. ", +" &&&%@ ", +" &&&&&$+ ", +" &&&&&&&#. ", +" &&&&&&&&%@ ", +" &&&&&&&&&&# ", +" &&&&&&&&%@. ", +" &&&&&&&#. ", +" &&&&&$+ ", +" &&&%@ ", +" &&#. ", +" $+ ", +" ", +" ", +" ", +" ", +" "}; + + +const char *play_xpm[] = { +"23 23 8 1", +" c #242523", +". c #393534", +"+ c #574B4C", +"@ c #6E5B5A", +"# c #7C6663", +"$ c #8C7170", +"% c #A48384", +"& c #B18E8F", +" ", +" ", +" ", +" ", +" ", +" $. ", +" &&@ ", +" &&&%+ ", +" &&&&&$. ", +" &&&&&&&@ ", +" &&&&&&&&%+ ", +" &&&&&&&&&&# ", +" &&&&&&&&%+ ", +" &&&&&&&#. ", +" &&&&&$. ", +" &&&%+ ", +" &&@ ", +" $. ", +" ", +" ", +" ", +" ", +" "}; + + +const char *rewindOff_xpm[] = { +"23 23 8 1", +" c #242523", +". c #393534", +"+ c #574B4C", +"@ c #6E5B5A", +"# c #7C6663", +"$ c #8C7170", +"% c #A48384", +"& c #B18E8F", +" ", +" ", +" ", +" ", +" ", +" .$ ", +" @&& ", +" +%&&& ", +" .$&&&&& ", +" @&&&&&&& ", +" +%&&&&&&&& ", +" #&&&&&&&&&& ", +" +%&&&&&&&& ", +" .#&&&&&&& ", +" .$&&&&& ", +" +%&&& ", +" @&& ", +" .$ ", +" ", +" ", +" ", +" ", +" "}; + + +const char *rewindOn_xpm[] = { +"23 23 8 1", +" c #4D4F4C", +". c #514E53", +"+ c #5C4F61", +"@ c #6F507E", +"# c #855098", +"$ c #9551AE", +"% c #A652C5", +"& c #AE52D1", +" ", +" ", +" ", +" ", +" ", +" +# ", +" .#%& ", +" @%&&& ", +" +$&&&&& ", +" .#&&&&&&& ", +" @%&&&&&&&& ", +" #&&&&&&&&&& ", +" .@%&&&&&&&& ", +" .#&&&&&&& ", +" +$&&&&& ", +" @%&&& ", +" .#&& ", +" +$ ", +" ", +" ", +" ", +" ", +" "}; + + +// 18x18 +/* +const unsigned char giada_icon[] = { + 0x00, 0x00, 0x00, 0xfc, 0xff, 0x00, 0xfe, 0xff, 0x01, 0xfe, 0xff, 0x01, + 0x3e, 0xf0, 0x01, 0x1e, 0xe0, 0x01, 0x0e, 0xc3, 0x01, 0x8e, 0xff, 0x01, + 0x8e, 0xc1, 0x01, 0x8e, 0xc1, 0x01, 0x8e, 0xc7, 0x01, 0x0e, 0xc7, 0x01, + 0x1e, 0xc0, 0x01, 0x3e, 0xf0, 0x01, 0xfe, 0xff, 0x01, 0xfe, 0xff, 0x01, + 0xfc, 0xff, 0x00, 0x00, 0x00, 0x00 }; +*/ + +const char *giada_icon[] = { +"65 65 11 1", +" c None", +". c #000000", +"+ c #000100", +"@ c #FFFFFF", +"# c #FDFFFC", +"$ c #CBCDC9", +"% c #292B28", +"& c #626461", +"* c #484A47", +"= c #888A87", +"- c #A7A9A6", +"....+++++++++++++++++++++++++++++++++++++++++++++++++++++++++....", +".@@@#####################$%+++++++++++++%&&*%+++++++&##%+++++....", +".@@#####################&++++++++++++=$#######-*+++++&$+++++++...", +".@@####################&++++++++++%-############$*+++++++++++++..", +"+#####################*++++++++++&################-++++++++++++%+", +"+####################*++++++++++=##################$+++++++++++*+", +"+###################*++++++++++$####################$++++++++++=+", +"+##################=++++++++++-######################-+++++++++-+", +"+#################$++++++++++=#######################=+++++++++$+", +"+#################%+++++++++*########################*+++++++++#+", +"+################*++++++++++#########################%++++++++*#+", +"+###############-++++++++++=########################$+++++++++&#+", +"+###############%+++++++++%#########################-+++++++++-#+", +"+##############-++++++++++-#########################&+++++++++$#+", +"+##############%+++++++++%##########################*+++++++++##+", +"+#############-++++++++++-##########################+++++++++%##+", +"+#############%++++++++++##########################$+++++++++*##+", +"+############$++++++++++&##########################=+++++++++=##+", +"+############=++++++++++$##########################&+++++++++-##+", +"+############*+++++++++%###########################%+++++++++$##+", +"+############++++++++++=###########################++++++++++###+", +"+###########$++++++++++$##########################-+++++++++*###+", +"+###########=++++++++++###########################=+++++++++&###+", +"+###########*+++++++++%###########################*+++++++++-###+", +"+###########%+++++++++&###########################++++++++++$###+", +"+###########%+++++++++-##########################=++++++++++####+", +"+###########++++++++++$##########################%+++++++++%####+", +"+###########++++++++++##########################$++++++++++&####+", +"+##########$++++++++++##########################&++++++++++=####+", +"+##########$+++++++++%#########################$+++++++++++-####+", +"+##########$+++++++++%#########################&+++++++++++$####+", +"+###########+++++++++*########################$++++++++++++#####+", +"+###########+++++++++*########################*+++++++++++*#####+", +"+###########%++++++++%#######################=++++++++++++&#####+", +"+###########&+++++++++######################$+++++++++++++-#####+", +"+###########=+++++++++$#####################%+++%+++++++++$#####+", +"+###########$+++++++++$####################&+++%=+++++++++######+", +"+############%++++++++&###################=++++$&++++++++%######+", +"+############-+++++++++$#################&++++=#*++++++++&######+", +"+#############+++++++++&################*++++*##+++++++++=######+", +"+#############=+++++++++-#############$%++++%###+++++++++-######+", +"+##############*+++++++++=##########$*+++++%###$+++++++++#######+", +"+###############++++++++++&$######$&++++++&####=+++++++++#######+", +"+###############$++++++++++++%&*%++++++++=#####&++++++++*#######+", +"+################$%+++++++++++++++++++++-######%++++++++=#######+", +"+##################&++++++++++++++++++=########+++++++++-#######+", +"+###################$%+++++++++++++%=#########$+++++++++########+", +"+#####################$=%++++++%*=-###########=++++++++%########+", +"+##########################$$$################*++++++++&########+", +"+#############################################+++++++++=########+", +"+############################################$+++++++++$########+", +"+############################################&++++++++%#########+", +"+############################################+++++++++=#########+", +"+###########################################=+++++++++##########+", +"+###########################################%++++++++*##########+", +"+##########################################=+++++++++-##########+", +"+#########-==$############################$+++++++++&###########+", +"+#######=++++++-##########################*+++++++++############+", +"+######$++++++++$########################=+++++++++-############+", +"+######=+++++++++$######################-+++++++++&#############+", +"+######=+++++++++*#####################$+++++++++&##############.", +".@#####=++++++++++-###################-+++++++++=##############@.", +".@@####=++++++++++%##################=+++++++++=#############@@@.", +".@@@###=+++++++++++*###############$*++++++++%$##############@@@.", +"....++++++++++++++++++++++++++++++++++++++++++++++++++++++++....."}; + +const char *recOff_xpm[] = { +"23 23 8 1", +" c #242523", +". c #342F2E", +"+ c #3F3B3A", +"@ c #594F4F", +"# c #7A6663", +"$ c #8C7170", +"% c #A68384", +"& c #B18E8F", +" ", +" ", +" ", +" ", +" ", +" @$%%$@ ", +" .$&&&&&&$. ", +" $&&&&&&&&$ ", +" @&&&#++#&&&@ ", +" $&&# #&&$ ", +" %&&+ +&&% ", +" %&&+ +&&% ", +" $&&# #&&$ ", +" @&&&#++#&&&@ ", +" $&&&&&&&&$ ", +" .$&&&&&&$. ", +" @$%%$@ ", +" ", +" ", +" ", +" ", +" ", +" "}; + +const char *recOn_xpm[] = { +"23 23 8 1", +" c #4D4F4C", +". c #5F4E50", +"+ c #6E4F50", +"@ c #8C5050", +"# c #AE5454", +"$ c #BB5253", +"% c #C55352", +"& c #E85557", +" ", +" ", +" ", +" ", +" ", +" @$&&$@ ", +" .%&&&&&&%. ", +" %&&&&&&&&% ", +" @&&&#++#&&&@ ", +" $&&# #&&$ ", +" &&&+ +&&& ", +" &&&+ +&&& ", +" $&&# #&&$ ", +" @&&&#++#&&&@ ", +" %&&&&&&&&% ", +" .%&&&&&&%. ", +" @$&&$@ ", +" ", +" ", +" ", +" ", +" ", +" "}; + +const char *inputRecOn_xpm[] = { +"23 23 8 1", +" c #524D4C", +". c #4D4F4C", +"+ c #5D4F50", +"@ c #8C5050", +"# c #BB5253", +"$ c #C45251", +"% c #DD5256", +"& c #EA5657", +".......................", +".......................", +".......................", +".......................", +".......................", +"........ @#%%#@ .......", +".......+$&&&&&&$+......", +"...... $&&&&&&&&$ .....", +"......@&&&&&&&&&&@.....", +"......#&&&&&&&&&&#.....", +"......%&&&&&&&&&&%.....", +"......%&&&&&&&&&&%.....", +"......#&&&&&&&&&&#.....", +"......@&&&&&&&&&&@.....", +".......$&&&&&&&&$......", +".......+$&&&&&&$+......", +"........ @#%%#@ .......", +".......................", +".......................", +".......................", +".......................", +".......................", +"......................."}; + +const char *inputRecOff_xpm[] = { +"23 23 8 1", +" c #242523", +". c #252724", +"+ c #332F2E", +"@ c #594E4F", +"# c #896E6D", +"$ c #8D7271", +"% c #A68384", +"& c #B18E8F", +" ", +" ", +" ", +" ", +" ", +" .@#%%#@. ", +" +$&&&&&&$+ ", +" .$&&&&&&&&$. ", +" @&&&&&&&&&&@ ", +" #&&&&&&&&&&# ", +" %&&&&&&&&&&% ", +" %&&&&&&&&&&% ", +" #&&&&&&&&&&# ", +" @&&&&&&&&&&@ ", +" $&&&&&&&&$ ", +" +$&&&&&&$+ ", +" .@#%%#@. ", +" ", +" ", +" ", +" ", +" ", +" "}; + +const char *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 *beatsDivideOn_xpm[] = { +"13 13 13 1", +" c None", +". c #595B58", +"+ c #5B5D5A", +"@ c #5F615E", +"# c #686967", +"$ c #737572", +"% c #787A77", +"& c #80827F", +"* c #8F918E", +"= c #959794", +"- c #9A9C99", +"; c #C4C6C3", +"> c #C7C9C6", +".............", +".............", +".............", +".............", +".....#>#.....", +"....+@%@+....", +"...*>>>>>*...", +"....+@%@+....", +".....#>#.....", +".............", +".............", +".............", +"............."}; + + +const char *beatsDivideOff_xpm[] = { +"13 13 13 1", +" c None", +". c #242523", +"+ c #262825", +"@ c #2D2E2C", +"# c #3A3B39", +"$ c #494B48", +"% c #525451", +"& c #595B58", +"* c #5F615E", +"= c #787A77", +"- c #858784", +"; c #C3C5C1", +"> c #C7C9C6", +".............", +".............", +".............", +"......+......", +".....#>#.....", +"...++@%@++...", +"...=>>>>>=...", +"...++@%@++...", +".....#>#.....", +"......+......", +".............", +".............", +"............."}; + + +const char *beatsMultiplyOn_xpm[] = { +"13 13 13 1", +" c None", +". c #595B58", +"+ c #5B5D5A", +"@ c #5F615E", +"# c #686967", +"$ c #737572", +"% c #787A77", +"& c #80827F", +"* c #8F918E", +"= c #959794", +"- c #9A9C99", +"; c #C4C6C3", +"> c #C7C9C6", +".............", +".............", +".............", +"....$...$....", +"...$;&.&;$...", +"....&;-;&....", +".....->-.....", +"....&;=;&....", +"...$;&.&;$...", +"...+$...$....", +".............", +".............", +"............."}; + + +const char *beatsMultiplyOff_xpm[] = { +"13 13 12 1", +" c #242523", +". c #262825", +"+ c #2D2E2C", +"@ c #3A3B39", +"# c #494B48", +"$ c #525451", +"% c #595B58", +"& c #5F615E", +"* c #787A77", +"= c #858784", +"- c #C3C5C1", +"; c #C7C9C6", +" ", +" ", +" ", +" .# #. ", +" #-& &-# ", +" &-=-& ", +" =;= ", +" %-*-& ", +" #-% %-# ", +" .# #. ", +" ", +" ", +" "}; diff --git a/src/graphics.h b/src/graphics.h new file mode 100644 index 0000000..c76da0f --- /dev/null +++ b/src/graphics.h @@ -0,0 +1,102 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * graphics + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + +#ifndef GRAPHICS_H +#define 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 *beatsDivideOn_xpm[]; +extern const char *beatsDivideOff_xpm[]; +extern const char *beatsMultiplyOn_xpm[]; +extern const char *beatsMultiplyOff_xpm[]; + +extern const char *muteOff_xpm[]; +extern const char *muteOn_xpm[]; + +extern const char *soloOff_xpm[]; +extern const char *soloOn_xpm[]; + +extern const char *readActionOn_xpm[]; +extern const char *readActionOff_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 diff --git a/src/gui_utils.cpp b/src/gui_utils.cpp new file mode 100644 index 0000000..4c3ffa7 --- /dev/null +++ b/src/gui_utils.cpp @@ -0,0 +1,210 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * gui_utils + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include "mixer.h" +#include "patch.h" +#include "gui_utils.h" +#include "graphics.h" +#include "gd_warnings.h" +#include "ge_window.h" +#include "ge_channel.h" +#include "gd_mainWindow.h" +#include "gg_keyboard.h" +#include "gd_actionEditor.h" +#include "recorder.h" +#include "wave.h" +#include "pluginHost.h" +#include "channel.h" +#include "log.h" +#include "conf.h" + + +extern Mixer G_Mixer; +extern unsigned G_beats; +extern bool G_audio_status; +extern Patch G_patch; +extern Conf G_conf; +extern uint32_t G_time; +extern gdMainWindow *mainWin; +#ifdef WITH_VST +extern PluginHost G_PluginHost; +#endif + + +static int blinker = 0; + + +void gu_refresh() +{ + Fl::lock(); + + /* update dynamic elements: in and out meters, beat meter and + * each channel */ + + mainWin->inOut->refresh(); + mainWin->beatMeter->redraw(); + mainWin->keyboard->refreshColumns(); + + /* compute timer for blinker */ + + blinker++; + if (blinker > 12) + blinker = 0; + + /* redraw GUI */ + + Fl::unlock(); + Fl::awake(); +} + + +/* -------------------------------------------------------------------------- */ + + +int gu_getBlinker() +{ + return blinker; +} + + +/* -------------------------------------------------------------------------- */ + + +void gu_updateControls() +{ + for (unsigned i=0; iguiChannel->update(); + + mainWin->inOut->setOutVol(G_Mixer.outVol); + mainWin->inOut->setInVol(G_Mixer.inVol); +#ifdef WITH_VST + mainWin->inOut->setMasterFxOutFull(G_PluginHost.masterOut.size > 0); + mainWin->inOut->setMasterFxInFull(G_PluginHost.masterIn.size > 0); +#endif + + mainWin->timing->setMeter(G_Mixer.beats, G_Mixer.bars); + mainWin->timing->setBpm(G_Mixer.bpm); + + /* if you reset to init state while the seq is in play: it's better to + * update the button status */ + + mainWin->controller->updatePlay(G_Mixer.running); + mainWin->controller->updateMetronome(0); +} + + +/* -------------------------------------------------------------------------- */ + + +void gu_update_win_label(const char *c) +{ + std::string out = VERSIONE_STR; + out += " - "; + out += c; + 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, + NULL); + w->icon((char *)p); +#elif defined(_WIN32) + w->icon((char *)LoadIcon(fl_display, MAKEINTRESOURCE(IDI_ICON1))); +#endif +} + + +/* -------------------------------------------------------------------------- */ + + +void gu_openSubWindow(gWindow *parent, gWindow *child, int id) +{ + if (parent->hasWindow(id)) { + gLog("[GU] parent has subwindow with id=%d, deleting\n", id); + parent->delSubWindow(id); + } + child->setId(id); + parent->addSubWindow(child); +} + + +/* -------------------------------------------------------------------------- */ + + +void gu_refreshActionEditor() +{ + /** TODO - why don't we simply call WID_ACTION_EDITOR->redraw()? */ + + gdActionEditor *aeditor = (gdActionEditor*) mainWin->getChild(WID_ACTION_EDITOR); + if (aeditor) { + Channel *chan = aeditor->chan; + mainWin->delSubWindow(WID_ACTION_EDITOR); + gu_openSubWindow(mainWin, new gdActionEditor(chan), WID_ACTION_EDITOR); + } +} + + +/* -------------------------------------------------------------------------- */ + + +gWindow *gu_getSubwindow(gWindow *parent, int id) +{ + if (parent->hasWindow(id)) + return parent->getChild(id); + else + return NULL; +} + + +/* -------------------------------------------------------------------------- */ + + +void gu_closeAllSubwindows() +{ + /* don't close WID_FILE_BROWSER, because it's the caller of this + * function */ + + mainWin->delSubWindow(WID_ACTION_EDITOR); + mainWin->delSubWindow(WID_SAMPLE_EDITOR); + mainWin->delSubWindow(WID_FX_LIST); + mainWin->delSubWindow(WID_FX); +} diff --git a/src/gui_utils.h b/src/gui_utils.h new file mode 100644 index 0000000..80c873c --- /dev/null +++ b/src/gui_utils.h @@ -0,0 +1,93 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * gui_utils + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + +#ifndef GUI_UTILS_H +#define GUI_UTILS_H + +#include +#include +#include +#include +#ifdef __APPLE__ + #include // in osx, for basename() (but linux?) +#endif + +/* including stuff for the favicon (or whatever called) */ + +#if defined(_WIN32) + #include "resource.h" +#elif defined(__linux__) + #include +#endif + + +/* refresh + * refresh all GUI elements. */ + +void gu_refresh(); + +/* 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_update_win_label(const char *c); + +void gu_setFavicon(Fl_Window *w); + +void gu_openSubWindow(class gWindow *parent, gWindow *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 NULL. */ + +gWindow *gu_getSubwindow(class gWindow *parent, int id); + +#endif diff --git a/src/init.cpp b/src/init.cpp new file mode 100644 index 0000000..51915b7 --- /dev/null +++ b/src/init.cpp @@ -0,0 +1,188 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * init + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + + +#include +#include "init.h" +#include "log.h" +#include "mixer.h" +#include "wave.h" +#include "const.h" +#include "utils.h" +#include "mixerHandler.h" +#include "patch.h" +#include "conf.h" +#include "pluginHost.h" +#include "recorder.h" +#include "gd_mainWindow.h" +#include "gui_utils.h" +#include "gd_warnings.h" +#include "kernelMidi.h" + + +extern Mixer G_Mixer; +extern bool G_audio_status; +extern bool G_quit; +extern Patch G_Patch; +extern Conf G_Conf; +extern gdMainWindow *mainWin; + +#ifdef WITH_VST +extern PluginHost G_PluginHost; +#endif + + +void init_prepareParser() +{ + G_Conf.read(); + G_Patch.setDefault(); + if (!gLog_init(G_Conf.logMode)) + gLog("[init] log init failed! Using default stdout\n"); + time_t t; + time (&t); + gLog("[init] Giada "VERSIONE" - %s", ctime(&t)); + gLog("[init] configuration file ready\n"); +} + + +/* ------------------------------------------------------------------ */ + + +void init_prepareKernelAudio() +{ + kernelAudio::openDevice( + G_Conf.soundSystem, + G_Conf.soundDeviceOut, + G_Conf.soundDeviceIn, + G_Conf.channelsOut, + G_Conf.channelsIn, + G_Conf.samplerate, + G_Conf.buffersize); + G_Mixer.init(); + recorder::init(); +} + + +/* ------------------------------------------------------------------ */ + + +void init_prepareKernelMIDI() +{ + kernelMidi::setApi(G_Conf.midiSystem); + kernelMidi::openOutDevice(G_Conf.midiPortOut); + kernelMidi::openInDevice(G_Conf.midiPortIn); +} + + +/* ------------------------------------------------------------------ */ + + +void init_startGUI(int argc, char **argv) +{ + char win_label[32]; + sprintf(win_label, "%s - %s", + VERSIONE_STR, + !strcmp(G_Patch.name, "") ? "(default patch)" : G_Patch.name); + + mainWin = new gdMainWindow(GUI_WIDTH, GUI_HEIGHT, win_label, argc, argv); + mainWin->resize(G_Conf.mainWindowX, G_Conf.mainWindowY, G_Conf.mainWindowW, G_Conf.mainWindowH); + + /* never update the GUI elements if G_audio_status is bad, segfaults + * are around the corner */ + + if (G_audio_status) + gu_updateControls(); + + if (!G_audio_status) + gdAlert( + "Your soundcard isn't configured correctly.\n" + "Check the configuration and restart Giada." + ); +} + +/* ------------------------------------------------------------------ */ + + +void init_startKernelAudio() +{ + if (G_audio_status) + kernelAudio::startStream(); + +#ifdef WITH_VST + G_PluginHost.allocBuffers(); +#endif +} + + +/* ------------------------------------------------------------------ */ + + +void init_shutdown() +{ + G_quit = true; + + /* store position and size of the main window for the next startup */ + + G_Conf.mainWindowX = mainWin->x(); + G_Conf.mainWindowY = mainWin->y(); + G_Conf.mainWindowW = mainWin->w(); + G_Conf.mainWindowH = mainWin->h(); + + /* close any open subwindow, especially before cleaning PluginHost to + * avoid mess */ + + gu_closeAllSubwindows(); + gLog("[init] all subwindows closed\n"); + + /* write configuration file */ + + if (!G_Conf.write()) + gLog("[init] error while saving configuration file!\n"); + else + gLog("[init] configuration saved\n"); + + /* if G_audio_status we close the kernelAudio FIRST, THEN the mixer. + * The opposite could cause random segfaults (even now with RtAudio?). */ + + if (G_audio_status) { + kernelAudio::closeDevice(); + G_Mixer.close(); + gLog("[init] Mixer closed\n"); + } + + recorder::clearAll(); + gLog("[init] Recorder cleaned up\n"); + +#ifdef WITH_VST + G_PluginHost.freeAllStacks(); + gLog("[init] Plugin Host cleaned up\n"); +#endif + + gLog("[init] Giada "VERSIONE" closed\n\n"); + gLog_close(); +} diff --git a/src/init.h b/src/init.h new file mode 100644 index 0000000..42a1102 --- /dev/null +++ b/src/init.h @@ -0,0 +1,49 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * init + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + + +#ifndef INIT_H +#define INIT_H + + +#include +#include +#ifdef __APPLE__ + #include +#endif + + +void init_prepareParser(); +void init_startGUI(int argc, char **argv); +void init_prepareKernelAudio(); +void init_prepareKernelMIDI(); +void init_startKernelAudio(); +void init_shutdown(); + + +#endif diff --git a/src/kernelAudio.cpp b/src/kernelAudio.cpp new file mode 100644 index 0000000..094422c --- /dev/null +++ b/src/kernelAudio.cpp @@ -0,0 +1,467 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * KernelAudio + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + + +#include +#include "kernelAudio.h" +#include "mixer.h" +#include "glue.h" +#include "conf.h" +#include "log.h" + + +extern Mixer G_Mixer; +extern Conf G_Conf; +extern bool G_audio_status; + + +namespace kernelAudio { + +RtAudio *system = NULL; +unsigned numDevs = 0; +bool inputEnabled = 0; +unsigned realBufsize = 0; +int api = 0; + +int openDevice( + int _api, + int outDev, + int inDev, + int outChan, + int inChan, + int samplerate, + int buffersize) +{ + api = _api; + gLog("[KA] using system 0x%x\n", api); +#if defined(__linux__) + if (api == SYS_API_JACK && hasAPI(RtAudio::UNIX_JACK)) + system = new RtAudio(RtAudio::UNIX_JACK); + else + if (api == SYS_API_ALSA && hasAPI(RtAudio::LINUX_ALSA)) + system = new RtAudio(RtAudio::LINUX_ALSA); + else + if (api == SYS_API_PULSE && hasAPI(RtAudio::LINUX_PULSE)) + system = new RtAudio(RtAudio::LINUX_PULSE); +#elif defined(_WIN32) + if (api == SYS_API_DS && hasAPI(RtAudio::WINDOWS_DS)) + system = new RtAudio(RtAudio::WINDOWS_DS); + else + if (api == SYS_API_ASIO && hasAPI(RtAudio::WINDOWS_ASIO)) + system = new RtAudio(RtAudio::WINDOWS_ASIO); +#elif defined(__APPLE__) + if (api == SYS_API_CORE && hasAPI(RtAudio::MACOSX_CORE)) + system = new RtAudio(RtAudio::MACOSX_CORE); +#endif + else { + G_audio_status = false; + return 0; + } + + + + //gLog("[KA] %d\n", sizeof(system->rtapi_)); + + gLog("[KA] Opening devices %d (out), %d (in), f=%d...\n", outDev, inDev, samplerate); + + numDevs = system->getDeviceCount(); + + if (numDevs < 1) { + gLog("[KA] no devices found with this API\n"); + closeDevice(); + G_audio_status = false; + return 0; + } + else { + gLog("[KA] %d device(s) found\n", numDevs); + for (unsigned i=0; iopenStream( + &outParams, // output params + &inParams, // input params + RTAUDIO_FLOAT32, // audio format + samplerate, // sample rate + &realBufsize, // buffer size in byte + &G_Mixer.masterPlay, // audio callback + NULL, // user data (unused) + &options); + } + else { + system->openStream( + &outParams, // output params + NULL, // input params + RTAUDIO_FLOAT32, // audio format + samplerate, // sample rate + &realBufsize, // buffer size in byte + &G_Mixer.masterPlay, // audio callback + NULL, // user data (unused) + &options); + } + G_audio_status = true; + +#if defined(__linux__) + if (api == SYS_API_JACK) + jackSetSyncCb(); +#endif + + return 1; + } + catch (RtAudioError &e) { + gLog("[KA] system init error: %s\n", e.getMessage().c_str()); + closeDevice(); + G_audio_status = false; + return 0; + } +} + + +/* ------------------------------------------------------------------ */ + + +int startStream() { + try { + system->startStream(); + gLog("[KA] latency = %lu\n", system->getStreamLatency()); + return 1; + } + catch (RtAudioError &e) { + gLog("[KA] Start stream error: %s\n", e.getMessage().c_str()); + return 0; + } +} + + +/* ------------------------------------------------------------------ */ + + +int stopStream() { + try { + system->stopStream(); + return 1; + } + catch (RtAudioError &e) { + gLog("[KA] Stop stream error\n"); + return 0; + } +} + + +/* ------------------------------------------------------------------ */ + + +const char *getDeviceName(unsigned dev) { + try { + return ((RtAudio::DeviceInfo) system->getDeviceInfo(dev)).name.c_str(); + } + catch (RtAudioError &e) { + gLog("[KA] invalid device ID = %d\n", dev); + return NULL; + } +} + + +/* ------------------------------------------------------------------ */ + + +int closeDevice() { + if (system->isStreamOpen()) { +#if defined(__linux__) || defined(__APPLE__) + system->abortStream(); // stopStream seems to lock the thread +#elif defined(_WIN32) + system->stopStream(); // on Windows it's the opposite +#endif + system->closeStream(); + delete system; + system = NULL; + } + return 1; +} + + +/* ------------------------------------------------------------------ */ + + +unsigned getMaxInChans(int dev) { + + if (dev == -1) return 0; + + try { + return ((RtAudio::DeviceInfo) system->getDeviceInfo(dev)).inputChannels; + } + catch (RtAudioError &e) { + gLog("[KA] Unable to get input channels\n"); + return 0; + } +} + + +/* ------------------------------------------------------------------ */ + + +unsigned getMaxOutChans(unsigned dev) { + try { + return ((RtAudio::DeviceInfo) system->getDeviceInfo(dev)).outputChannels; + } + catch (RtAudioError &e) { + gLog("[KA] Unable to get output channels\n"); + return 0; + } +} + + +/* ------------------------------------------------------------------ */ + + +bool isProbed(unsigned dev) { + try { + return ((RtAudio::DeviceInfo) system->getDeviceInfo(dev)).probed; + } + catch (RtAudioError &e) { + return 0; + } +} + + +/* ------------------------------------------------------------------ */ + + +unsigned getDuplexChans(unsigned dev) { + try { + return ((RtAudio::DeviceInfo) system->getDeviceInfo(dev)).duplexChannels; + } + catch (RtAudioError &e) { + return 0; + } +} + + +/* ------------------------------------------------------------------ */ + + +bool isDefaultIn(unsigned dev) { + try { + return ((RtAudio::DeviceInfo) system->getDeviceInfo(dev)).isDefaultInput; + } + catch (RtAudioError &e) { + return 0; + } +} + + +/* ------------------------------------------------------------------ */ + + +bool isDefaultOut(unsigned dev) { + try { + return ((RtAudio::DeviceInfo) system->getDeviceInfo(dev)).isDefaultOutput; + } + catch (RtAudioError &e) { + return 0; + } +} + + +/* ------------------------------------------------------------------ */ + + +int getTotalFreqs(unsigned dev) { + try { + return ((RtAudio::DeviceInfo) system->getDeviceInfo(dev)).sampleRates.size(); + } + catch (RtAudioError &e) { + return 0; + } +} + + +/* ------------------------------------------------------------------ */ + + +int getFreq(unsigned dev, int i) { + try { + return ((RtAudio::DeviceInfo) system->getDeviceInfo(dev)).sampleRates.at(i); + } + catch (RtAudioError &e) { + return 0; + } +} + + +/* ------------------------------------------------------------------ */ + + +int getDefaultIn() { + return system->getDefaultInputDevice(); +} + +int getDefaultOut() { + return system->getDefaultOutputDevice(); +} + + +/* ------------------------------------------------------------------ */ + + +int getDeviceByName(const char *name) { + for (unsigned i=0; i APIs; + RtAudio::getCompiledApi(APIs); + for (unsigned i=0; i +#include +#include + +jack_client_t *jackGetHandle() { + return (jack_client_t*) system->rtapi_->__HACK__getJackClient(); +} + +void jackStart() { + if (api == SYS_API_JACK) { + jack_client_t *client = jackGetHandle(); + jack_transport_start(client); + } +} + + +void jackStop() { + if (api == SYS_API_JACK) { + jack_client_t *client = jackGetHandle(); + jack_transport_stop(client); + } +} + + +void jackSetSyncCb() { + jack_client_t *client = jackGetHandle(); + jack_set_sync_callback(client, jackSyncCb, NULL); + //jack_set_sync_timeout(client, 8); +} + + +int jackSyncCb(jack_transport_state_t state, jack_position_t *pos, + void *arg) +{ + switch (state) { + case JackTransportStopped: + gLog("[KA] Jack transport stopped, frame=%d\n", pos->frame); + glue_stopSeq(false); // false = not from GUI + if (pos->frame == 0) + glue_rewindSeq(); + break; + + case JackTransportRolling: + gLog("[KA] Jack transport rolling\n"); + break; + + case JackTransportStarting: + gLog("[KA] Jack transport starting, frame=%d\n", pos->frame); + glue_startSeq(false); // false = not from GUI + if (pos->frame == 0) + glue_rewindSeq(); + break; + + default: + gLog("[KA] Jack transport [unknown]\n"); + } + return 1; +} + +#endif + +} + + diff --git a/src/kernelAudio.h b/src/kernelAudio.h new file mode 100644 index 0000000..0d566e8 --- /dev/null +++ b/src/kernelAudio.h @@ -0,0 +1,96 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * KernelAudio + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + + +#ifndef KERNELAUDIO_H +#define KERNELAUDIO_H + + +#include "rtaudio-mod/RtAudio.h" +#if defined(__linux__) + #include + #include + #include +#endif + + +namespace kernelAudio { + + int openDevice( + int api, + int outDev, + int inDev, + int outChan, + int inChan, + int samplerate, + int buffersize); + int closeDevice(); + + int startStream(); + int stopStream(); + + bool isProbed (unsigned dev); + bool isDefaultIn (unsigned dev); + bool isDefaultOut (unsigned dev); + const char *getDeviceName (unsigned dev); + unsigned getMaxInChans (int dev); + unsigned getMaxOutChans (unsigned dev); + unsigned getDuplexChans (unsigned dev); + int getTotalFreqs (unsigned dev); + int getFreq (unsigned dev, int i); + int getDeviceByName(const char *name); + int getDefaultOut (); + int getDefaultIn (); + bool hasAPI (int API); + + std::string getRtAudioVersion(); + +#ifdef __linux__ + jack_client_t *jackGetHandle(); + void jackStart(); + void jackStop(); + void jackSetSyncCb(); + int jackSyncCb(jack_transport_state_t state, jack_position_t *pos, void *arg); +#endif + + /* *** how to avoid multiple definition of *** + * When you declare a variable in a header file, every source file that + * includes that header, either directly or indirectly, gets its own + * separate copy of the variable. Then when you go to link all the .o + * files together, the linker sees that the variable is instantiated + * in a bunch of .o files. Make it extern in the header file and + * instantiate it in memory.cpp. */ + + extern RtAudio *system; + extern unsigned numDevs; + extern bool inputEnabled; + extern unsigned realBufsize; // reale bufsize from the soundcard + extern int api; +} + +#endif diff --git a/src/kernelMidi.cpp b/src/kernelMidi.cpp new file mode 100644 index 0000000..3a42424 --- /dev/null +++ b/src/kernelMidi.cpp @@ -0,0 +1,385 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * KernelMidi + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + + +#include +#include "kernelMidi.h" +#include "glue.h" +#include "mixer.h" +#include "channel.h" +#include "sampleChannel.h" +#include "pluginHost.h" +#include "conf.h" +#include "log.h" + + +extern bool G_midiStatus; +extern Conf G_Conf; +extern Mixer G_Mixer; + +#ifdef WITH_VST +extern PluginHost G_PluginHost; +#endif + + +namespace kernelMidi +{ + +int api = 0; // one api for both in & out +RtMidiOut *midiOut = NULL; +RtMidiIn *midiIn = NULL; +unsigned numOutPorts = 0; +unsigned numInPorts = 0; + +cb_midiLearn *cb_learn = NULL; +void *cb_data = NULL; + + +/* ------------------------------------------------------------------ */ + + +void startMidiLearn(cb_midiLearn *cb, void *data) +{ + cb_learn = cb; + cb_data = data; +} + + +/* ------------------------------------------------------------------ */ + + +void stopMidiLearn() +{ + cb_learn = NULL; + cb_data = NULL; +} + + +/* ------------------------------------------------------------------ */ + + +void setApi(int _api) +{ + api = api; + gLog("[KM] using system 0x%x\n", api); +} + + +/* ------------------------------------------------------------------ */ + + +int openOutDevice(int port) +{ + try { + midiOut = new RtMidiOut((RtMidi::Api) api, "Giada MIDI Output"); + G_midiStatus = true; + } + catch (RtMidiError &error) { + gLog("[KM] MIDI out device error: %s\n", error.getMessage().c_str()); + G_midiStatus = false; + return 0; + } + + /* print output ports */ + + numOutPorts = midiOut->getPortCount(); + gLog("[KM] %d output MIDI ports found\n", numOutPorts); + for (unsigned i=0; i 0) { + try { + midiOut->openPort(port, getOutPortName(port)); + gLog("[KM] MIDI out port %d open\n", port); + return 1; + } + catch (RtMidiError &error) { + gLog("[KM] unable to open MIDI out port %d: %s\n", port, error.getMessage().c_str()); + G_midiStatus = false; + return 0; + } + } + else + return 2; +} + + +/* ------------------------------------------------------------------ */ + + +int openInDevice(int port) +{ + try { + midiIn = new RtMidiIn((RtMidi::Api) api, "Giada MIDI input"); + G_midiStatus = true; + } + catch (RtMidiError &error) { + gLog("[KM] MIDI in device error: %s\n", error.getMessage().c_str()); + G_midiStatus = false; + return 0; + } + + /* print input ports */ + + numInPorts = midiIn->getPortCount(); + gLog("[KM] %d input MIDI ports found\n", numInPorts); + for (unsigned i=0; i 0) { + try { + midiIn->openPort(port, getInPortName(port)); + midiIn->ignoreTypes(true, false, true); // ignore all system/time msgs, for now + gLog("[KM] MIDI in port %d open\n", port); + midiIn->setCallback(&callback); + return 1; + } + catch (RtMidiError &error) { + gLog("[KM] unable to open MIDI in port %d: %s\n", port, error.getMessage().c_str()); + G_midiStatus = false; + return 0; + } + } + else + return 2; +} + + +/* ------------------------------------------------------------------ */ + + +bool hasAPI(int API) +{ + std::vector APIs; + RtMidi::getCompiledApi(APIs); + for (unsigned i=0; igetPortName(p).c_str(); } + catch (RtMidiError &error) { return NULL; } +} + +const char *getInPortName(unsigned p) +{ + try { return midiIn->getPortName(p).c_str(); } + catch (RtMidiError &error) { return NULL; } +} + + +/* ------------------------------------------------------------------ */ + + +void send(uint32_t data) +{ + if (!G_midiStatus) + return; + + //std::vector msg(1, 0x00); + //msg[0] = getB1(data); + //msg[1] = getB2(data); + //msg[2] = getB3(data); + + std::vector msg(1, getB1(data)); + msg.push_back(getB2(data)); + msg.push_back(getB3(data)); + + midiOut->sendMessage(&msg); + //gLog("[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 (!G_midiStatus) + return; + + std::vector msg(1, b1); + + if (b2 != -1) + msg.push_back(b2); + if (b3 != -1) + msg.push_back(b3); + + midiOut->sendMessage(&msg); + //gLog("[KM] send msg=(%X %X %X)\n", b1, b2, b3); +} + + +/* ------------------------------------------------------------------ */ + + +void callback(double t, std::vector *msg, void *data) +{ + /* 0.8.0 - for now we handle other midi signals (common and real-time + * messages) as unknown, for debugging purposes */ + + if (msg->size() < 3) { + gLog("[KM] MIDI received - unknown signal - size=%d, value=0x", (int) msg->size()); + for (unsigned i=0; isize(); i++) + gLog("%X", (int) msg->at(i)); + gLog("\n"); + return; + } + + /* in this place we want to catch two things: a) note on/note off + * from a keyboard and b) knob/wheel/slider movements from a + * controller */ + + uint32_t input = getIValue(msg->at(0), msg->at(1), msg->at(2)); + uint32_t chan = input & 0x0F000000; + uint32_t value = input & 0x0000FF00; + uint32_t pure = 0x00; + if (!G_Conf.noNoteOff) + pure = input & 0xFFFF0000; // input without 'value' byte + else + pure = input & 0xFFFFFF00; // input with 'value' byte + + gLog("[KM] MIDI received - 0x%X (chan %d)", input, chan >> 24); + + /* start dispatcher. If midi learn is on don't parse channels, just + * learn incoming midi signal. Otherwise 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) { + gLog("\n"); + cb_learn(pure, cb_data); + } + else { + + /* process master events */ + + if (pure == G_Conf.midiInRewind) { + gLog(" >>> rewind (global) (pure=0x%X)", pure); + glue_rewindSeq(); + } + else if (pure == G_Conf.midiInStartStop) { + gLog(" >>> startStop (global) (pure=0x%X)", pure); + glue_startStopSeq(); + } + else if (pure == G_Conf.midiInActionRec) { + gLog(" >>> actionRec (global) (pure=0x%X)", pure); + glue_startStopActionRec(); + } + else if (pure == G_Conf.midiInInputRec) { + gLog(" >>> inputRec (global) (pure=0x%X)", pure); + glue_startStopInputRec(false, false); // update gui, no popup messages + } + else if (pure == G_Conf.midiInMetronome) { + gLog(" >>> metronome (global) (pure=0x%X)", pure); + glue_startStopMetronome(false); + } + else if (pure == G_Conf.midiInVolumeIn) { + float vf = (value >> 8)/127.0f; + gLog(" >>> input volume (global) (pure=0x%X, value=%d, float=%f)", pure, value >> 8, vf); + glue_setInVol(vf, false); + } + else if (pure == G_Conf.midiInVolumeOut) { + float vf = (value >> 8)/127.0f; + gLog(" >>> output volume (global) (pure=0x%X, value=%d, float=%f)", pure, value >> 8, vf); + glue_setOutVol(vf, false); + } + else if (pure == G_Conf.midiInBeatDouble) { + gLog(" >>> sequencer x2 (global) (pure=0x%X)", pure); + glue_beatsMultiply(); + } + else if (pure == G_Conf.midiInBeatHalf) { + gLog(" >>> sequencer /2 (global) (pure=0x%X)", pure); + glue_beatsDivide(); + } + + /* process channels */ + + for (unsigned i=0; imidiIn) continue; + + if (pure == ch->midiInKeyPress) { + gLog(" >>> keyPress, ch=%d (pure=0x%X)", ch->index, pure); + glue_keyPress(ch, false, false); + } + else if (pure == ch->midiInKeyRel) { + gLog(" >>> keyRel ch=%d (pure=0x%X)", ch->index, pure); + glue_keyRelease(ch, false, false); + } + else if (pure == ch->midiInMute) { + gLog(" >>> mute ch=%d (pure=0x%X)", ch->index, pure); + glue_setMute(ch, false); + } + else if (pure == ch->midiInSolo) { + gLog(" >>> solo ch=%d (pure=0x%X)", ch->index, pure); + ch->solo ? glue_setSoloOn(ch, false) : glue_setSoloOff(ch, false); + } + else if (pure == ch->midiInVolume) { + float vf = (value >> 8)/127.0f; + gLog(" >>> volume ch=%d (pure=0x%X, value=%d, float=%f)", ch->index, pure, value >> 8, vf); + glue_setChanVol(ch, vf, false); + } + else if (pure == ((SampleChannel*)ch)->midiInPitch) { + float vf = (value >> 8)/(127/4.0f); // [0-127] ~> [0.0 4.0] + gLog(" >>> pitch ch=%d (pure=0x%X, value=%d, float=%f)", ch->index, pure, value >> 8, vf); + glue_setPitch(NULL, (SampleChannel*)ch, vf, false); + } + else if (pure == ((SampleChannel*)ch)->midiInReadActions) { + gLog(" >>> start/stop read actions ch=%d (pure=0x%X)", ch->index, pure); + glue_startStopReadingRecs((SampleChannel*)ch, false); + } + } + gLog("\n"); + } +} + + +/* ------------------------------------------------------------------ */ + + +std::string getRtMidiVersion() +{ + return midiOut->getVersion(); +} + + +} // namespace diff --git a/src/kernelMidi.h b/src/kernelMidi.h new file mode 100644 index 0000000..b17bbeb --- /dev/null +++ b/src/kernelMidi.h @@ -0,0 +1,104 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * KernelMidi + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + + +#ifndef KERNELMIDI_H +#define KERNELMIDI_H + + +#include +#include +#include "channel.h" + + +namespace kernelMidi { + + extern int api; // one api for both in & out + extern unsigned numOutPorts; + extern unsigned numInPorts; + + typedef void (cb_midiLearn) (uint32_t, void *); + + /* cb_learn + * callback prepared by the gdMidiGrabber window and called by + * kernelMidi. It contains things to do once the midi message has been + * stored. */ + + extern cb_midiLearn *cb_learn; + extern void *cb_data; + + void startMidiLearn(cb_midiLearn *cb, void *data); + void stopMidiLearn(); + + inline int getB1(uint32_t iValue) { return (iValue >> 24) & 0xFF; } + inline int getB2(uint32_t iValue) { return (iValue >> 16) & 0xFF; } + inline int getB3(uint32_t iValue) { return (iValue >> 8) & 0xFF; } + + inline uint32_t getIValue(int b1, int b2, int b3) { + return (b1 << 24) | (b2 << 16) | (b3 << 8) | (0x00); + } + + /* send + * send a MIDI message 's' (uint32_t). */ + + void send(uint32_t s); + + /* send (2) + * send separate bytes of MIDI message. */ + + void send(int b1, int b2=-1, int b3=-1); + + /* setApi + * set the Api in use for both in & out messages. */ + + void setApi(int api); + + /* 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'. */ + + const char *getInPortName(unsigned p); + const char *getOutPortName(unsigned p); + + bool hasAPI(int API); + + /* callback + * master callback for input events. */ + + void callback(double t, std::vector *msg, void *data); + + std::string getRtMidiVersion(); +} + +#endif diff --git a/src/log.cpp b/src/log.cpp new file mode 100644 index 0000000..0f5be34 --- /dev/null +++ b/src/log.cpp @@ -0,0 +1,80 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * log + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + + +#include +#include +#include +#include "log.h" +#include "const.h" +#include "utils.h" + + +static FILE *f; +static int mode; +static bool stat; + + +int gLog_init(int m) { + mode = m; + stat = true; + if (mode == LOG_MODE_FILE) { + std::string fpath = gGetHomePath() + "/giada.log"; + f = fopen(fpath.c_str(), "a"); + if (!f) { + stat = false; + return 0; + } + } + return 1; +} + + +/* ------------------------------------------------------------------ */ + + +void gLog_close() { + if (mode == LOG_MODE_FILE) + fclose(f); +} + + +/* ------------------------------------------------------------------ */ + + +void gLog(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); + else + vprintf(format, args); + va_end(args); +} diff --git a/src/log.h b/src/log.h new file mode 100644 index 0000000..c53f9c7 --- /dev/null +++ b/src/log.h @@ -0,0 +1,45 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * log + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + + +#ifndef __LOG_H__ +#define __LOG_H__ + + +/* init + * init logger. Mode defines where to write the output: LOG_MODE_STDOUT, + * LOG_MODE_FILE and LOG_MODE_MUTE. */ + +int gLog_init (int mode); + +void gLog_close(); + +void gLog(const char *format, ...); + + +#endif diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..687fb05 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,98 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + + +#include +#if defined(__linux__) || defined(__APPLE__) + #include +#endif +#include "init.h" +#include "const.h" +#include "patch.h" +#include "conf.h" +#include "mixer.h" +#include "mixerHandler.h" +#include "kernelAudio.h" +#include "recorder.h" +#include "gui_utils.h" +#include "gd_mainWindow.h" +#ifdef WITH_VST +#include "pluginHost.h" +#endif + + +/* global variables. Yeah, we are nasty */ + +pthread_t t_video; +Mixer G_Mixer; +bool G_quit; +bool G_audio_status; +bool G_midiStatus; +Patch G_Patch; +Conf G_Conf; +gdMainWindow *mainWin; + +#ifdef WITH_VST +PluginHost G_PluginHost; +#endif + + +void *thread_video(void *arg); + + +int main(int argc, char **argv) { + + G_quit = false; + + init_prepareParser(); + init_prepareKernelAudio(); + init_prepareKernelMIDI(); + init_startGUI(argc, argv); + Fl::lock(); + pthread_create(&t_video, NULL, thread_video, NULL); + init_startKernelAudio(); + + int ret = Fl::run(); + + pthread_join(t_video, NULL); + return ret; +} + + + +void *thread_video(void *arg) { + if (G_audio_status) + while (!G_quit) { + gu_refresh(); +#ifdef _WIN32 + Sleep(GUI_SLEEP); +#else + usleep(GUI_SLEEP); +#endif + } + pthread_exit(NULL); + return 0; +} diff --git a/src/midiChannel.cpp b/src/midiChannel.cpp new file mode 100644 index 0000000..f1e43b7 --- /dev/null +++ b/src/midiChannel.cpp @@ -0,0 +1,323 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * channel + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + + +#include "channel.h" +#include "midiChannel.h" +#include "pluginHost.h" +#include "patch.h" +#include "conf.h" +#include "kernelMidi.h" +#include "log.h" + + +extern Patch G_Patch; +extern Mixer G_Mixer; +extern Conf G_Conf; +#ifdef WITH_VST +extern PluginHost G_PluginHost; +#endif + + +MidiChannel::MidiChannel(int bufferSize) + : Channel (CHANNEL_MIDI, STATUS_OFF, bufferSize), + midiOut (false), + midiOutChan(MIDI_CHANS[0]) +{ +#ifdef WITH_VST // init VstEvents stack + freeVstMidiEvents(true); +#endif +} + + +/* ------------------------------------------------------------------ */ + + +MidiChannel::~MidiChannel() {} + + +/* ------------------------------------------------------------------ */ + + +#ifdef WITH_VST + +void MidiChannel::freeVstMidiEvents(bool init) { + if (events.numEvents == 0 && !init) + return; + memset(events.events, 0, sizeof(VstEvent*) * MAX_VST_EVENTS); + events.numEvents = 0; + events.reserved = 0; +} + +#endif + + +/* ------------------------------------------------------------------ */ + + +#ifdef WITH_VST + +void MidiChannel::addVstMidiEvent(uint32_t msg) { + addVstMidiEvent(G_PluginHost.createVstMidiEvent(msg)); +} + +#endif + + +/* ------------------------------------------------------------------ */ + + +#ifdef WITH_VST + +void MidiChannel::addVstMidiEvent(VstMidiEvent *e) { + if (events.numEvents < MAX_VST_EVENTS) { + events.events[events.numEvents] = (VstEvent*) e; + events.numEvents++; + /* + gLog("[MidiChannel] VstMidiEvent added - numEvents=%d offset=%d note=%d number=%d velo=%d\n", + events.numEvents, + e->deltaFrames, + e->midiData[0], + e->midiData[1], + e->midiData[2] + );*/ + } + else + gLog("[MidiChannel] channel %d VstEvents = %d > MAX_VST_EVENTS, nothing to do\n", index, events.numEvents); +} + +#endif + + +/* ------------------------------------------------------------------ */ + + +void MidiChannel::onBar(int frame) {} + + +/* ------------------------------------------------------------------ */ + + +void MidiChannel::stop() {} + + +/* ------------------------------------------------------------------ */ + + +void MidiChannel::empty() {} + + +/* ------------------------------------------------------------------ */ + + +void MidiChannel::quantize(int index, int localFrame, int globalFrame) {} + + +/* ------------------------------------------------------------------ */ + +#ifdef WITH_VST + +VstEvents *MidiChannel::getVstEvents() { + return (VstEvents *) &events; +} + +#endif + + +/* ------------------------------------------------------------------ */ + + +void MidiChannel::parseAction(recorder::action *a, int localFrame, int globalFrame) { + if (a->type == ACTION_MIDI) + sendMidi(a, localFrame/2); +} + + +/* ------------------------------------------------------------------ */ + + +void MidiChannel::onZero(int frame) { + if (status == STATUS_ENDING) + status = STATUS_OFF; + else + if (status == STATUS_WAIT) + status = STATUS_PLAY; +} + + +/* ------------------------------------------------------------------ */ + + +void MidiChannel::setMute(bool internal) { + mute = true; // internal mute does not exist for midi (for now) + if (midiOut) + kernelMidi::send(MIDI_ALL_NOTES_OFF); +#ifdef WITH_VST + addVstMidiEvent(MIDI_ALL_NOTES_OFF); +#endif +} + + +/* ------------------------------------------------------------------ */ + + +void MidiChannel::unsetMute(bool internal) { + mute = false; // internal mute does not exist for midi (for now) +} + + +/* ------------------------------------------------------------------ */ + + +void MidiChannel::process(float *buffer) { +#ifdef WITH_VST + G_PluginHost.processStack(vChan, PluginHost::CHANNEL, this); + freeVstMidiEvents(); +#endif + + for (int j=0; jiValue | MIDI_CHANS[midiOutChan]); + +#ifdef WITH_VST + a->event->deltaFrames = localFrame; + addVstMidiEvent(a->event); +#endif + } +} + + +void MidiChannel::sendMidi(uint32_t data) +{ + if (status & (STATUS_PLAY | STATUS_ENDING) && !mute) { + if (midiOut) + kernelMidi::send(data | MIDI_CHANS[midiOutChan]); +#ifdef WITH_VST + addVstMidiEvent(data); +#endif + } +} + + +/* ------------------------------------------------------------------ */ + + +void MidiChannel::rewind() { + if (midiOut) + kernelMidi::send(MIDI_ALL_NOTES_OFF); +#ifdef WITH_VST + addVstMidiEvent(MIDI_ALL_NOTES_OFF); +#endif +} + + +/* ------------------------------------------------------------------ */ + + +void MidiChannel::writePatch(FILE *fp, int i, bool isProject) +{ + Channel::writePatch(fp, i, isProject); + + fprintf(fp, "chanMidiOut%d=%u\n", i, midiOut); + fprintf(fp, "chanMidiOutChan%d=%u\n", i, midiOutChan); +} diff --git a/src/midiChannel.h b/src/midiChannel.h new file mode 100644 index 0000000..e6e1a09 --- /dev/null +++ b/src/midiChannel.h @@ -0,0 +1,136 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * channel + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + + +#ifndef MIDI_CHANNEL_H +#define MIDI_CHANNEL_H + + +#ifdef WITH_VST + +/* before including aeffetx(x).h we must define __cdecl, otherwise VST + * headers can't be compiled correctly. In windows __cdecl is already + * defined. */ + + #ifdef __GNUC__ + #ifndef _WIN32 + #define __cdecl + #endif + #endif + #include "vst/aeffectx.h" + +#endif + + +class MidiChannel : public Channel { + +public: + + MidiChannel(int bufferSize); + ~MidiChannel(); + + bool midiOut; // enable midi output + uint8_t midiOutChan; // midi output channel + + void process (float *buffer); + void start (int frame, bool doQuantize); + void kill (int frame); + void empty (); + void stopBySeq (); + void stop (); + void rewind (); + void setMute (bool internal); + void unsetMute (bool internal); + int loadByPatch(const char *file, int i); + void writePatch (FILE *fp, int i, bool isProject); + void quantize (int index, int localFrame, int globalFrame); + void onZero (int frame); + void onBar (int frame); + void parseAction(recorder::action *a, int localFrame, int globalFrame); + + /* ---------------------------------------------------------------- */ + + /* sendMidi + * send Midi event to the outside world. */ + + void sendMidi(recorder::action *a, int localFrame); + void sendMidi(uint32_t data); + +#ifdef WITH_VST + + /* getVstEvents + * return a pointer to gVstEvents. */ + + VstEvents *getVstEvents(); + + /* freeVstMidiEvents + * empty vstEvents structure. Init: use the method for channel + * initialization. */ + + void freeVstMidiEvents(bool init=false); + + /* addVstMidiEvent + * take a composite MIDI event, decompose it and add it to channel. The + * other version creates a VstMidiEvent on the fly. */ + + void addVstMidiEvent(struct VstMidiEvent *e); + void addVstMidiEvent(uint32_t msg); + +#endif + + /* ---------------------------------------------------------------- */ + +#ifdef WITH_VST + + /* VST struct containing MIDI events. When ready, events are sent to + * each plugin in the channel. + * + * Anatomy of VstEvents + * -------------------- + * + * VstInt32 numEvents = number of Events in array + * VstIntPtr reserved = zero (Reserved for future use) + * VstEvent *events[n] = event pointer array, variable size + * + * Note that by default VstEvents only holds three events- if you want + * it to hold more, create an equivalent struct with a larger array, + * and then cast it to a VstEvents object when you've populated it. + * That's what we do with gVstEvents! */ + + struct gVstEvents { + VstInt32 numEvents; + VstIntPtr reserved; + VstEvent *events[MAX_VST_EVENTS]; + } events; + +#endif + +}; + + +#endif diff --git a/src/mixer.cpp b/src/mixer.cpp new file mode 100644 index 0000000..4c25065 --- /dev/null +++ b/src/mixer.cpp @@ -0,0 +1,684 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * mixer + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + + +#include +#include "mixer.h" +#include "init.h" +#include "wave.h" +#include "gui_utils.h" +#include "recorder.h" +#include "pluginHost.h" +#include "patch.h" +#include "conf.h" +#include "mixerHandler.h" +#include "channel.h" +#include "sampleChannel.h" +#include "midiChannel.h" +#include "kernelMidi.h" +#include "log.h" + + +extern Mixer G_Mixer; +extern Patch G_Patch; +extern Conf G_Conf; +#ifdef WITH_VST +extern PluginHost G_PluginHost; +#endif + + +Mixer::Mixer() {} +Mixer::~Mixer() {} + + +#define TICKSIZE 38 + + +float Mixer::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 Mixer::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 +}; + + +/* ------------------------------------------------------------------ */ + + +void Mixer::init() { + quanto = 1; + docross = false; + rewindWait = false; + running = false; + ready = true; + waitRec = 0; + actualFrame = 0; + bpm = DEFAULT_BPM; + bars = DEFAULT_BARS; + beats = DEFAULT_BEATS; + quantize = DEFAULT_QUANTIZE; + metronome = false; + + tickTracker = 0; + tockTracker = 0; + tickPlay = false; + tockPlay = false; + + outVol = DEFAULT_OUT_VOL; + inVol = DEFAULT_IN_VOL; + peakOut = 0.0f; + peakIn = 0.0f; + chanInput = NULL; + inputTracker = 0; + + actualBeat = 0; + + midiTCstep = 0; + midiTCrate = (G_Conf.samplerate / G_Conf.midiTCfps) * 2; // dealing with stereo vals + midiTCframes = 0; + midiTCseconds = 0; + midiTCminutes = 0; + midiTChours = 0; + + /* alloc virtual input channels. vChanInput malloc is done in + * updateFrameBars, because of its variable size */ + /** TODO - set kernelAudio::realBufsize * 2 as private member */ + + vChanInput = NULL; + vChanInToOut = (float *) malloc(kernelAudio::realBufsize * 2 * sizeof(float)); + + pthread_mutex_init(&mutex_recs, NULL); + pthread_mutex_init(&mutex_chans, NULL); + pthread_mutex_init(&mutex_plugins, NULL); + + updateFrameBars(); + rewind(); +} + + +/* ------------------------------------------------------------------ */ + + +Channel *Mixer::addChannel(int type) { + + Channel *ch; + int bufferSize = kernelAudio::realBufsize*2; + + if (type == CHANNEL_SAMPLE) + ch = new SampleChannel(bufferSize); + else + ch = new MidiChannel(bufferSize); + + while (true) { + int lockStatus = pthread_mutex_trylock(&mutex_chans); + if (lockStatus == 0) { + channels.add(ch); + pthread_mutex_unlock(&mutex_chans); + break; + } + } + + ch->index = getNewIndex(); + gLog("[mixer] channel index=%d added, type=%d, total=%d\n", ch->index, ch->type, channels.size); + return ch; +} + + +/* ------------------------------------------------------------------ */ + + +int Mixer::getNewIndex() { + + /* always skip last channel: it's the last one just added */ + + if (channels.size == 1) + return 0; + + int index = 0; + for (unsigned i=0; iindex > index) + index = channels.at(i)->index; + } + index += 1; + return index; +} + + +/* ------------------------------------------------------------------ */ + + +int Mixer::deleteChannel(Channel *ch) { + int lockStatus; + while (true) { + lockStatus = pthread_mutex_trylock(&mutex_chans); + if (lockStatus == 0) { + channels.del(ch); + delete ch; + pthread_mutex_unlock(&mutex_chans); + return 1; + } + //else + // gLog("[mixer::deleteChannel] waiting for mutex...\n"); + } +} + + +/* ------------------------------------------------------------------ */ + + +Channel *Mixer::getChannelByIndex(int index) { + for (unsigned i=0; iindex == index) + return channels.at(i); + gLog("[mixer::getChannelByIndex] channel at index %d not found!\n", index); + return NULL; +} + + +/* ------------------------------------------------------------------ */ + + +void Mixer::sendMIDIsync() { + + if (G_Conf.midiSync == MIDI_SYNC_CLOCK_M) { + if (actualFrame % (framesPerBeat/24) == 0) + kernelMidi::send(MIDI_CLOCK, -1, -1); + } + else + if (G_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 (actualFrame % midiTCrate == 0) { + + /* 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 > G_Conf.midiTCfps) { + midiTCframes = 0; + midiTCseconds++; + if (midiTCseconds >= 60) { + midiTCminutes++; + midiTCseconds = 0; + if (midiTCminutes >= 60) { + midiTChours++; + midiTCminutes = 0; + } + } + //gLog("%d:%d:%d:%d\n", midiTChours, midiTCminutes, midiTCseconds, midiTCframes); + } + } + } +} + + +/* ------------------------------------------------------------------ */ + + +void Mixer::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 (G_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 + } +} + +/* ------------------------------------------------------------------ */ + + +int Mixer::masterPlay( + void *out_buf, void *in_buf, unsigned n_frames, + double streamTime, RtAudioStreamStatus status, void *userData) { + return G_Mixer.__masterPlay(out_buf, in_buf, n_frames); +} + + +/* ------------------------------------------------------------------ */ + + +int Mixer::__masterPlay(void *out_buf, void *in_buf, unsigned bufferFrames) { + + if (!ready) + return 0; + + float *outBuf = ((float *) out_buf); + float *inBuf = ((float *) in_buf); + bufferFrames *= 2; // stereo + peakOut = 0.0f; // reset peak calculator + peakIn = 0.0f; // reset peak calculator + + /* always clean each buffer */ + + memset(outBuf, 0, sizeof(float) * bufferFrames); // out + memset(vChanInToOut, 0, sizeof(float) * bufferFrames); // inToOut vChan + + pthread_mutex_lock(&mutex_chans); + for (unsigned i=0; itype == CHANNEL_SAMPLE) + ((SampleChannel*)channels.at(i))->clear(); + pthread_mutex_unlock(&mutex_chans); + + for (unsigned j=0; j peakIn) + peakIn = inBuf[j] * inVol; + + /* "hear what you're playing" - process, copy and paste the input buffer + * onto the output buffer */ + + if (inToOut) { + vChanInToOut[j] = inBuf[j] * inVol; + vChanInToOut[j+1] = inBuf[j+1] * inVol; + } + } + + /* operations to do if the sequencer is running: + * - compute quantizer + * - time check for LOOP_REPEAT + * - reset loops at beat 0 + * - read recorded actions + * - reset actualFrame */ + + if (running) { + + /* line in recording */ + + if (chanInput != NULL && kernelAudio::inputEnabled) { + + /* delay comp: wait until waitRec reaches delayComp. WaitRec + * returns to 0 in mixerHandler, as soon as the recording ends */ + + if (waitRec < G_Conf.delayComp) + waitRec += 2; + else { + vChanInput[inputTracker] += inBuf[j] * inVol; + vChanInput[inputTracker+1] += inBuf[j+1] * inVol; + inputTracker += 2; + if (inputTracker >= totalFrames) + inputTracker = 0; + } + } + + /* quantizer computations: quantize rewind and all channels. */ + + if (quantize > 0 && quanto > 0) { + if (actualFrame % (quanto) == 0) { // is quanto! + if (rewindWait) { + rewindWait = false; + rewind(); + } + pthread_mutex_lock(&mutex_chans); + for (unsigned k=0; kquantize(k, j, actualFrame); // j == localFrame + pthread_mutex_unlock(&mutex_chans); + } + } + + /* reset LOOP_REPEAT, if a bar has passed */ + + if (actualFrame % framesPerBar == 0 && actualFrame != 0) { + if (metronome) + tickPlay = true; + + pthread_mutex_lock(&mutex_chans); + for (unsigned k=0; konBar(j); + pthread_mutex_unlock(&mutex_chans); + } + + /* reset loops on beat 0 */ + + if (actualFrame == 0) { + pthread_mutex_lock(&mutex_chans); + for (unsigned k=0; konZero(j); + pthread_mutex_unlock(&mutex_chans); + } + + /* reading all actions recorded */ + + pthread_mutex_lock(&mutex_recs); + for (unsigned y=0; ychan; + Channel *ch = getChannelByIndex(index); + ch->parseAction(recorder::global.at(y).at(z), j, actualFrame); + } + break; + } + } + pthread_mutex_unlock(&mutex_recs); + + /* increase actualFrame */ + + actualFrame += 2; + + /* if actualFrame > totalFrames the sequencer returns to frame 0, + * beat 0. This must be the last operation. */ + + if (actualFrame > totalFrames) { + actualFrame = 0; + actualBeat = 0; + } + else + if (actualFrame % framesPerBeat == 0 && actualFrame > 0) { + actualBeat++; + + /* avoid tick and tock to overlap when a new bar has passed (which + * is also a beat) */ + + if (metronome && !tickPlay) + tockPlay = true; + } + + sendMIDIsync(); + + } // if (running) + + /* sum channels, CHANNEL_SAMPLE only */ + + pthread_mutex_lock(&mutex_chans); + for (unsigned k=0; ktype == CHANNEL_SAMPLE) + ((SampleChannel*)channels.at(k))->sum(j, running); + } + pthread_mutex_unlock(&mutex_chans); + + /* metronome play */ + /** FIXME - move this one after the peak meter calculation */ + + if (tockPlay) { + outBuf[j] += tock[tockTracker]; + outBuf[j+1] += tock[tockTracker]; + tockTracker++; + if (tockTracker >= TICKSIZE-1) { + tockPlay = false; + tockTracker = 0; + } + } + if (tickPlay) { + outBuf[j] += tick[tickTracker]; + outBuf[j+1] += tick[tickTracker]; + tickTracker++; + if (tickTracker >= TICKSIZE-1) { + tickPlay = false; + tickTracker = 0; + } + } + } // end loop J + + + /* final loop: sum virtual channels and process plugins. */ + + pthread_mutex_lock(&mutex_chans); + for (unsigned k=0; kprocess(outBuf); + pthread_mutex_unlock(&mutex_chans); + + /* processing fxs master in & out, if any. */ + +#ifdef WITH_VST + pthread_mutex_lock(&mutex_plugins); + G_PluginHost.processStack(outBuf, PluginHost::MASTER_OUT); + G_PluginHost.processStack(vChanInToOut, PluginHost::MASTER_IN); + pthread_mutex_unlock(&mutex_plugins); +#endif + + /* post processing master fx + peak calculation. */ + + for (unsigned j=0; j peakOut) + peakOut = outBuf[j]; + + if (G_Conf.limitOutput) { + if (outBuf[j] > 1.0f) + outBuf[j] = 1.0f; + else if (outBuf[j] < -1.0f) + outBuf[j] = -1.0f; + + if (outBuf[j+1] > 1.0f) + outBuf[j+1] = 1.0f; + else if (outBuf[j+1] < -1.0f) + outBuf[j+1] = -1.0f; + } + } + + return 0; +} + + +/* ------------------------------------------------------------------ */ + + +void Mixer::updateFrameBars() { + + /* seconds ....... total time of play (in seconds) of the whole + * sequencer. 60 / bpm == how many seconds lasts one bpm + * totalFrames ... number of frames in the whole sequencer, x2 because + * it's stereo + * framesPerBar .. n. of frames within a bar + * framesPerBeat . n. of frames within a beat */ + + float seconds = (60.0f / bpm) * beats; + totalFrames = G_Conf.samplerate * seconds * 2; + framesPerBar = totalFrames / bars; + framesPerBeat = totalFrames / beats; + framesInSequencer = framesPerBeat * MAX_BEATS; + + /* big troubles if frames are odd. */ + + if (totalFrames % 2 != 0) + totalFrames--; + if (framesPerBar % 2 != 0) + framesPerBar--; + if (framesPerBeat % 2 != 0) + framesPerBeat--; + + updateQuanto(); + + /* realloc input virtual channel, if not NULL. TotalFrames is changed! */ + + if (vChanInput != NULL) + free(vChanInput); + vChanInput = (float*) malloc(totalFrames * sizeof(float)); + if (!vChanInput) + gLog("[Mixer] vChanInput realloc error!\n"); +} + + +/* ------------------------------------------------------------------ */ + + +int Mixer::close() { + running = false; + while (channels.size > 0) + deleteChannel(channels.at(0)); + free(vChanInput); + free(vChanInToOut); + return 1; +} + + +/* ------------------------------------------------------------------ */ + + +bool Mixer::isSilent() { + for (unsigned i=0; istatus == STATUS_PLAY) + return false; + return true; +} + + +/* ------------------------------------------------------------------ */ + + +void Mixer::rewind() { + + actualFrame = 0; + actualBeat = 0; + + if (running) + for (unsigned i=0; irewind(); + + sendMIDIrewind(); +} + + +/* ------------------------------------------------------------------ */ + + +void Mixer::updateQuanto() { + + /* big troubles if frames are odd. */ + + if (quantize != 0) + quanto = framesPerBeat / quantize; + if (quanto % 2 != 0) + quanto++; +} + + +/* ------------------------------------------------------------------ */ + + +bool Mixer::hasLogicalSamples() { + for (unsigned i=0; itype == CHANNEL_SAMPLE) + if (((SampleChannel*)channels.at(i))->wave) + if (((SampleChannel*)channels.at(i))->wave->isLogical) + return true; + return false; +} + + +/* ------------------------------------------------------------------ */ + + +bool Mixer::hasEditedSamples() { + for (unsigned i=0; itype == CHANNEL_SAMPLE) + if (((SampleChannel*)channels.at(i))->wave) + if (((SampleChannel*)channels.at(i))->wave->isEdited) + return true; + return false; +} + + +/* ------------------------------------------------------------------ */ + + +bool Mixer::mergeVirtualInput() { + if (vChanInput == NULL) { + gLog("[Mixer] virtual input channel not alloc'd\n"); + return false; + } + else { +#ifdef WITH_VST + G_PluginHost.processStackOffline(vChanInput, PluginHost::MASTER_IN, 0, totalFrames); +#endif + int numFrames = totalFrames*sizeof(float); + memcpy(chanInput->wave->data, vChanInput, numFrames); + memset(vChanInput, 0, numFrames); // clear vchan + return true; + } +} diff --git a/src/mixer.h b/src/mixer.h new file mode 100644 index 0000000..586bacc --- /dev/null +++ b/src/mixer.h @@ -0,0 +1,209 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * mixer + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + + +#ifndef MIXER_H +#define MIXER_H + +#include +#include +#include "const.h" +#include "kernelAudio.h" +#include "utils.h" + + +class Mixer { + +public: + + Mixer(); + ~Mixer(); + + void init(); + int close(); + + /* addChannel + * add a new channel without any wave inside of it. */ + + class Channel *addChannel(int type); + + /* deleteChannel + * completely remove a channel from the stack. */ + + int deleteChannel(class Channel *ch); + + /* masterPlay + * core method (callback) */ + + static int masterPlay( + void *out_buf, void *in_buf, unsigned n_frames, + double streamTime, RtAudioStreamStatus status, void *userData + ); + int __masterPlay(void *out_buf, void *in_buf, unsigned n_frames); + + /* updateFrameBars + * updates bpm, frames, beats and so on. */ + + void updateFrameBars(); + + /* isSilent + * is mixer silent? */ + + bool isSilent(); + + /* rewind + * rewind sequencer to sample 0. */ + + void rewind(); + + /* updateQuanto + * recomputes the quanto between two quantizations */ + + void updateQuanto(); + + /* 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(); + + /* mergeVirtualInput + * memcpy the virtual channel input in the channel designed for input + * recording. Called by mixerHandler on stopInputRec() */ + + bool mergeVirtualInput(); + + /* getChannelByIndex + * return channel with given index 'i'. */ + + Channel *getChannelByIndex(int i); + + inline Channel* getLastChannel() { return channels.at(channels.size-1); } + + + /* ---------------------------------------------------------------- */ + + + enum { // const - what to do when a fadeout ends + DO_STOP = 0x01, + DO_MUTE = 0x02, + DO_MUTE_I = 0x04 + }; + + enum { // const - fade types + FADEOUT = 0x01, + XFADE = 0x02 + }; + + gVector channels; + + bool running; + bool ready; + float *vChanInput; // virtual channel for recording + float *vChanInToOut; // virtual channel in->out bridge (hear what you're playin) + int frameSize; + float outVol; + float inVol; + float peakOut; + float peakIn; + int quanto; + char quantize; + bool metronome; + float bpm; + int bars; + int beats; + int waitRec; // delayComp guard + + bool docross; // crossfade guard + bool rewindWait; // rewind guard, if quantized + + int framesPerBar; // frames in one bar + int framesPerBeat; // frames in one beat + int framesInSequencer; // frames in the whole sequencer + int totalFrames; // frames in the selected range (e.g. 4/4) + int actualFrame; + int actualBeat; + +#define TICKSIZE 38 + static float tock[TICKSIZE]; + static float tick[TICKSIZE]; + int tickTracker, tockTracker; + bool tickPlay, tockPlay; // 1 = play, 0 = stop + + /* chanInput + * the active channel during a recording. NULL = no channels active */ + + class SampleChannel *chanInput; + + /* inputTracker + * position of the sample in the input side (recording) */ + + int inputTracker; + + /* inToOut + * copy, process and paste the input into the output, in order to + * obtain a "hear what you're playing" feature. */ + + bool inToOut; + + pthread_mutex_t mutex_recs; + pthread_mutex_t mutex_chans; + pthread_mutex_t mutex_plugins; + + +private: + + int midiTCstep; // part of MTC to send (0 to 7) + int midiTCrate; // send MTC data every midiTCrate frames + int midiTCframes; + int midiTCseconds; + int midiTCminutes; + int midiTChours; + + /* getNewIndex + * compute new index value for new channels */ + + int getNewIndex(); + + /* sendMIDIsync + * generate MIDI sync output data */ + + void sendMIDIsync(); + + /* sendMIDIrewind + * rewind timecode to beat 0 and also send a MTC full frame to cue + * the slave */ + + void sendMIDIrewind(); +}; + +#endif diff --git a/src/mixerHandler.cpp b/src/mixerHandler.cpp new file mode 100644 index 0000000..4bd520f --- /dev/null +++ b/src/mixerHandler.cpp @@ -0,0 +1,238 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * mixerHandler + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#if defined(__linux__) + #include + #include + #include +#endif + +#include "mixerHandler.h" +#include "kernelMidi.h" +#include "mixer.h" +#include "const.h" +#include "utils.h" +#include "init.h" +#include "pluginHost.h" +#include "plugin.h" +#include "waveFx.h" +#include "glue.h" +#include "conf.h" +#include "patch.h" +#include "recorder.h" +#include "channel.h" +#include "sampleChannel.h" +#include "wave.h" +#include "log.h" + + +extern Mixer G_Mixer; +extern Patch G_Patch; +extern Conf G_Conf; + +#ifdef WITH_VST +extern PluginHost G_PluginHost; +#endif + + +void mh_stopSequencer() +{ + G_Mixer.running = false; + for (unsigned i=0; istopBySeq(); +} + + +/* -------------------------------------------------------------------------- */ + + +void mh_clear() +{ + G_Mixer.running = false; + while (G_Mixer.channels.size > 0) + G_Mixer.channels.del(0U); // unsigned +} + + +/* -------------------------------------------------------------------------- */ + + +bool mh_uniqueSolo(Channel *ch) +{ + int solos = 0; + for (unsigned i=0; isolo) solos++; + if (solos > 1) return false; + } + return true; +} + + +/* -------------------------------------------------------------------------- */ + + +/** TODO - revision needed: mh should not call glue_addChannel */ + +void mh_loadPatch(bool isProject, const char *projPath) +{ + G_Mixer.init(); + G_Mixer.ready = false; // put it in wait mode + + int numChans = G_Patch.getNumChans(); + for (int i=0; i= 0.10.0 - old stuff, remove backward compatibility */ + + if (isProject && G_Patch.version >= 0.63f) + sprintf(smpPath, "%s%s%s", gDirname(projPath).c_str(), gGetSlash().c_str(), G_Patch.getSamplePath(i).c_str()); + else + sprintf(smpPath, "%s", G_Patch.getSamplePath(i).c_str()); + + ch->loadByPatch(smpPath, i); + } + + G_Mixer.outVol = G_Patch.getOutVol(); + G_Mixer.inVol = G_Patch.getInVol(); + G_Mixer.bpm = G_Patch.getBpm(); + G_Mixer.bars = G_Patch.getBars(); + G_Mixer.beats = G_Patch.getBeats(); + G_Mixer.quantize = G_Patch.getQuantize(); + G_Mixer.metronome = G_Patch.getMetronome(); + G_Patch.lastTakeId = G_Patch.getLastTakeId(); + G_Patch.samplerate = G_Patch.getSamplerate(); + + /* rewind and update frames in Mixer (it's vital) */ + + G_Mixer.rewind(); + G_Mixer.updateFrameBars(); + G_Mixer.ready = true; +} + + +/* -------------------------------------------------------------------------- */ + + +void mh_rewindSequencer() +{ + if (G_Mixer.quantize > 0 && G_Mixer.running) // quantize rewind + G_Mixer.rewindWait = true; + else + G_Mixer.rewind(); +} + + +/* -------------------------------------------------------------------------- */ + + +SampleChannel *mh_startInputRec() +{ + /* search for the next available channel */ + + SampleChannel *chan = NULL; + for (unsigned i=0; itype == CHANNEL_SAMPLE) + if (((SampleChannel*) G_Mixer.channels.at(i))->canInputRec()) { + chan = (SampleChannel*) G_Mixer.channels.at(i); + break; + } + } + + /* no chans available? */ + + if (chan == NULL) + return NULL; + + Wave *w = new Wave(); + if (!w->allocEmpty(G_Mixer.totalFrames)) + return NULL; + + /* increase lastTakeId until the sample name TAKE-[n] is unique */ + + char name[32]; + sprintf(name, "TAKE-%d", G_Patch.lastTakeId); + while (!mh_uniqueSamplename(chan, name)) { + G_Patch.lastTakeId++; + sprintf(name, "TAKE-%d", G_Patch.lastTakeId); + } + + chan->allocEmpty(G_Mixer.totalFrames, G_Patch.lastTakeId); + G_Mixer.chanInput = chan; + + /* start to write from the actualFrame, not the beginning */ + /** FIXME: move this before wave allocation*/ + + G_Mixer.inputTracker = G_Mixer.actualFrame; + + gLog( + "[mh] start input recs using chan %d with size %d, frame=%d\n", + chan->index, G_Mixer.totalFrames, G_Mixer.inputTracker + ); + + return chan; +} + + +/* -------------------------------------------------------------------------- */ + + +SampleChannel *mh_stopInputRec() +{ + gLog("[mh] stop input recs\n"); + G_Mixer.mergeVirtualInput(); + SampleChannel *ch = G_Mixer.chanInput; + G_Mixer.chanInput = NULL; + G_Mixer.waitRec = 0; // if delay compensation is in use + return ch; +} + + +/* -------------------------------------------------------------------------- */ + + +bool mh_uniqueSamplename(SampleChannel *ch, const char *name) +{ + for (unsigned i=0; itype == CHANNEL_SAMPLE) { + SampleChannel *other = (SampleChannel*) G_Mixer.channels.at(i); + if (other->wave != NULL) + if (!strcmp(name, other->wave->name.c_str())) + return false; + } + } + } + return true; +} diff --git a/src/mixerHandler.h b/src/mixerHandler.h new file mode 100644 index 0000000..f842004 --- /dev/null +++ b/src/mixerHandler.h @@ -0,0 +1,76 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * mixerHandler + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + + +#ifndef MIXERHANDLER_H +#define MIXERHANDLER_H + + +#include "recorder.h" + + +/* stopSequencer + * stop the sequencer, with special case if samplesStopOnSeqHalt is + * true. */ + +void mh_stopSequencer(); + +void mh_rewindSequencer(); + +/* clear + * stop everything and clear all channels. */ + +void mh_clear(); + +/* uniqueSolo + * true if ch is the only solo'd channel in mixer. */ + +bool mh_uniqueSolo(class Channel *ch); + +/* loadPatch + * load a path or a project (if isProject) into Mixer. If isProject, path + * must contain the address of the project folder. */ + +void mh_loadPatch(bool isProject, const char *projPath=0); + +/* startInputRec - record from line in + * creates a new empty wave in the first available channels and returns + * the chan number chosen, otherwise -1 if there are no more empty + * channels available. */ + +SampleChannel *mh_startInputRec(); + +SampleChannel *mh_stopInputRec(); + +/* uniqueSamplename + * return true if samplename 'n' is unique. Requires SampleChannel *ch + * in order to skip check against itself. */ + +bool mh_uniqueSamplename(class SampleChannel *ch, const char *name); + +#endif diff --git a/src/patch.cpp b/src/patch.cpp new file mode 100644 index 0000000..005c691 --- /dev/null +++ b/src/patch.cpp @@ -0,0 +1,744 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * patch + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include +#include "patch.h" +#include "init.h" +#include "recorder.h" +#include "utils.h" +#include "conf.h" +#include "pluginHost.h" +#include "wave.h" +#include "mixer.h" +#include "channel.h" +#include "log.h" +#include "gd_mainWindow.h" +#include "gg_keyboard.h" + + +extern Mixer G_Mixer; +extern Conf G_Conf; +#ifdef WITH_VST +extern PluginHost G_PluginHost; +#endif +extern gdMainWindow *mainWin; + + +int Patch::open(const char *file) +{ + fp = fopen(file, "r"); + if (fp == NULL) + return PATCH_UNREADABLE; + + if (getValue("header") != "GIADAPTC") + return PATCH_INVALID; + + version = atof(getValue("versionf").c_str()); + gLog("[PATCH] open patch version %f\n", version); + if (version == 0.0) + gLog("[PATCH] patch < 0.6.1, backward compatibility mode\n"); + + return PATCH_OPEN_OK; +} + + +/* -------------------------------------------------------------------------- */ + + +void Patch::setDefault() +{ + name[0] = '\0'; + lastTakeId = 0; + samplerate = DEFAULT_SAMPLERATE; +} + + +/* -------------------------------------------------------------------------- */ + + +int Patch::close() +{ + return fclose(fp); +} + + +/* -------------------------------------------------------------------------- */ + + +void Patch::getName() +{ + std::string out = getValue("patchname"); + strncpy(name, out.c_str(), MAX_PATCHNAME_LEN); +} + + +/* -------------------------------------------------------------------------- */ + + +std::string Patch::getSamplePath(int c) +{ + char tmp[16]; + sprintf(tmp, "samplepath%d", c); + return getValue(tmp); +} + + +/* -------------------------------------------------------------------------- */ + + +float Patch::getPitch(int c) +{ + char tmp[16]; + sprintf(tmp, "chanPitch%d", c); + float out = atof(getValue(tmp).c_str()); + if (out > 2.0f || out < 0.1f) + return 1.0f; + return out; +} + + +/* -------------------------------------------------------------------------- */ + + +int Patch::getNumChans() +{ + if (version == 0.0) // backward compatibility with version < 0.6.1 + return 32; + return atoi(getValue("channels").c_str()); +} + + +/* -------------------------------------------------------------------------- */ + + +int Patch::getNumColumns() +{ + return atoi(getValue("columns").c_str()); +} + + +/* -------------------------------------------------------------------------- */ + + +int Patch::getColumn(int c) +{ + if (version == 0.0) // backward compatibility with version < 0.6.1 + return 0; + char tmp[16]; + sprintf(tmp, "chanColumn%d", c); + return atoi(getValue(tmp).c_str()); +} + + +/* -------------------------------------------------------------------------- */ + + +int Patch::getIndex(int c) +{ + if (version == 0.0) // backward compatibility with version < 0.6.1 + return c; + + char tmp[16]; + sprintf(tmp, "chanIndex%d", c); + return atoi(getValue(tmp).c_str()); +} + + +/* -------------------------------------------------------------------------- */ + + +float Patch::getVol(int c) +{ + char tmp[16]; + sprintf(tmp, "chanvol%d", c); + float out = atof(getValue(tmp).c_str()); + if (out > 1.0f || out < 0.0f) + return DEFAULT_VOL; + return out; +} + + +/* -------------------------------------------------------------------------- */ + + +int Patch::getMode(int c) +{ + char tmp[16]; + sprintf(tmp, "chanmode%d", c); + int out = atoi(getValue(tmp).c_str()); + if (out & (LOOP_ANY | SINGLE_ANY)) + return out; + return DEFAULT_CHANMODE; +} + + +/* -------------------------------------------------------------------------- */ + + +int Patch::getMute(int c) +{ + char tmp[16]; + sprintf(tmp, "chanMute%d", c); + return atoi(getValue(tmp).c_str()); +} + + +/* -------------------------------------------------------------------------- */ + + +int Patch::getMute_s(int c) +{ + char tmp[16]; + sprintf(tmp, "chanMute_s%d", c); + return atoi(getValue(tmp).c_str()); +} + + +/* -------------------------------------------------------------------------- */ + + +int Patch::getSolo(int c) +{ + char tmp[16]; + sprintf(tmp, "chanSolo%d", c); + return atoi(getValue(tmp).c_str()); +} + + +/* -------------------------------------------------------------------------- */ + + +int Patch::getType(int c) +{ + char tmp[16]; + sprintf(tmp, "chanType%d", c); + int out = atoi(getValue(tmp).c_str()); + if (out == 0) + return CHANNEL_SAMPLE; + return out; +} + + +/* -------------------------------------------------------------------------- */ + + +int Patch::getBegin(int c) +{ + char tmp[16]; + if (version < 0.73f) + sprintf(tmp, "chanstart%d", c); + else + sprintf(tmp, "chanBegin%d", c); + int out = atoi(getValue(tmp).c_str()); + if (out < 0) + return 0; + return out; +} + + +/* -------------------------------------------------------------------------- */ + + +int Patch::getEnd(int c, unsigned size) +{ + char tmp[16]; + sprintf(tmp, "chanend%d", c); + + /* if chanEnd doesn't exist, it returns an atoi(empty string) == 0. + * good in theory, a disaster in practice. */ + + std::string val = getValue(tmp); + if (val == "") + return size; + + unsigned out = atoi(val.c_str()); + if (out <= 0 || out > size) + return size; + return out; +} + + +/* -------------------------------------------------------------------------- */ + + +float Patch::getBoost(int c) +{ + char tmp[16]; + sprintf(tmp, "chanBoost%d", c); + float out = atof(getValue(tmp).c_str()); + if (out < 1.0f) + return DEFAULT_BOOST; + return out; +} + + +/* -------------------------------------------------------------------------- */ + + +float Patch::getPanLeft(int c) +{ + char tmp[16]; + sprintf(tmp, "chanPanLeft%d", c); + std::string val = getValue(tmp); + if (val == "") + return 1.0f; + + float out = atof(val.c_str()); + if (out < 0.0f || out > 1.0f) + return 1.0f; + return out; +} + + +/* -------------------------------------------------------------------------- */ + + +int Patch::getKey(int c) +{ + if (version == 0.0) // backward compatibility with version < 0.6.1 + return 0; + char tmp[16]; + sprintf(tmp, "chanKey%d", c); + return atoi(getValue(tmp).c_str()); +} + + +/* -------------------------------------------------------------------------- */ + + +float Patch::getPanRight(int c) +{ + char tmp[16]; + sprintf(tmp, "chanPanRight%d", c); + std::string val = getValue(tmp); + if (val == "") + return 1.0f; + + float out = atof(val.c_str()); + if (out < 0.0f || out > 1.0f) + return 1.0f; + return out; +} + + +/* -------------------------------------------------------------------------- */ + + +bool Patch::getRecActive(int c) +{ + char tmp[16]; + sprintf(tmp, "chanRecActive%d", c); + return atoi(getValue(tmp).c_str()); +} + + +/* -------------------------------------------------------------------------- */ + + +float Patch::getOutVol() +{ + return atof(getValue("outVol").c_str()); +} + + +/* -------------------------------------------------------------------------- */ + + +float Patch::getInVol() +{ + return atof(getValue("inVol").c_str()); +} + + +/* -------------------------------------------------------------------------- */ + + +float Patch::getBpm() +{ + float out = atof(getValue("bpm").c_str()); + if (out < 20.0f || out > 999.0f) + return DEFAULT_BPM; + return out; +} + + +/* -------------------------------------------------------------------------- */ + + +int Patch::getBars() +{ + int out = atoi(getValue("bars").c_str()); + if (out <= 0 || out > 32) + return DEFAULT_BARS; + return out; +} + + +/* -------------------------------------------------------------------------- */ + + +int Patch::getBeats() +{ + int out = atoi(getValue("beats").c_str()); + if (out <= 0 || out > 32) + return DEFAULT_BEATS; + return out; +} + + +/* -------------------------------------------------------------------------- */ + + +int Patch::getQuantize() +{ + int out = atoi(getValue("quantize").c_str()); + if (out < 0 || out > 8) + return DEFAULT_QUANTIZE; + return out; +} + + +/* -------------------------------------------------------------------------- */ + + +bool Patch::getMetronome() +{ + bool out = atoi(getValue("metronome").c_str()); + if (out != true || out != false) + return false; + return out; +} + + +/* -------------------------------------------------------------------------- */ + + +int Patch::getLastTakeId() +{ + return atoi(getValue("lastTakeId").c_str()); +} + + +/* -------------------------------------------------------------------------- */ + + +int Patch::getSamplerate() +{ + int out = atoi(getValue("samplerate").c_str()); + if (out <= 0) + return DEFAULT_SAMPLERATE; + return out; +} + + +/* -------------------------------------------------------------------------- */ + + +uint32_t Patch::getMidiValue(int i, const char *c) +{ + char tmp[32]; + sprintf(tmp, "chanMidi%s%d", c, i); + return strtoul(getValue(tmp).c_str(), NULL, 10); +} + + +/* -------------------------------------------------------------------------- */ + + +int Patch::readRecs() +{ + gLog("[PATCH] Reading recs...\n"); + + unsigned numrecs = atoi(getValue("numrecs").c_str()); + + for (unsigned i=0; istatus & ~(STATUS_WRONG | STATUS_MISSING | STATUS_EMPTY)) { + if (version < 0.83f) + recorder::rec(ch->index, type, frame, iValue_fix, fValue); + else + recorder::rec(ch->index, type, frame, iValue, fValue); + } + } + } + return 1; +} + + +/* -------------------------------------------------------------------------- */ + + +#ifdef WITH_VST +int Patch::readPlugins() +{ + gLog("[PATCH] Reading plugins...\n"); + + int globalOut = 1; + + /* master plugins */ + + globalOut &= readMasterPlugins(PluginHost::MASTER_IN); + globalOut &= readMasterPlugins(PluginHost::MASTER_OUT); + + /* channel plugins */ + + for (unsigned i=0; iindex); + int np = atoi(getValue(tmp).c_str()); + + for (int j=0; jindex, j); + int out = G_PluginHost.addPlugin(getValue(tmp).c_str(), PluginHost::CHANNEL, ch); + if (out != 0) { + sprintf(tmp, "chan%d_p%dnumParams", ch->index, j); + int nparam = atoi(getValue(tmp).c_str()); + Plugin *pPlugin = G_PluginHost.getPluginByIndex(j, PluginHost::CHANNEL, ch); + sprintf(tmp, "chan%d_p%dbypass", ch->index, j); + pPlugin->bypass = atoi(getValue(tmp).c_str()); + for (int k=0; kindex, j, k); + float pval = atof(getValue(tmp).c_str()); + pPlugin->setParam(k, pval); + } + } + globalOut &= out; + } + } + return globalOut; +} +#endif + + +/* -------------------------------------------------------------------------- */ + + +int Patch::write(const char *file, const char *name, bool project) +{ + fp = fopen(file, "w"); + if (fp == NULL) + return 0; + + fprintf(fp, "# --- Giada patch file --- \n"); + fprintf(fp, "header=GIADAPTC\n"); + fprintf(fp, "version=%s\n", VERSIONE); + fprintf(fp, "versionf=%f\n", VERSIONE_FLOAT); + fprintf(fp, "patchname=%s\n", name); + fprintf(fp, "bpm=%f\n", G_Mixer.bpm); + fprintf(fp, "bars=%d\n", G_Mixer.bars); + fprintf(fp, "beats=%d\n", G_Mixer.beats); + fprintf(fp, "quantize=%d\n", G_Mixer.quantize); + fprintf(fp, "outVol=%f\n", G_Mixer.outVol); + fprintf(fp, "inVol=%f\n", G_Mixer.inVol); + fprintf(fp, "metronome=%d\n", G_Mixer.metronome); + fprintf(fp, "lastTakeId=%d\n", lastTakeId); + fprintf(fp, "samplerate=%d\n", G_Conf.samplerate); // original samplerate when the patch was saved + fprintf(fp, "channels=%d\n", G_Mixer.channels.size); + fprintf(fp, "columns=%d\n", mainWin->keyboard->getTotalColumns()); + + for (unsigned i=0; iwritePatch(fp, i, project); + } + + /* writing recs. Warning: channel index is not mixer.channels.at(chan), + * but mixer.channels.at(chan)->index! */ + + fprintf(fp, "# --- actions --- \n"); + fprintf(fp, "numrecs=%d\n", recorder::global.size); + for (unsigned i=0; ichan, + recorder::global.at(i).at(k)->type, + recorder::global.at(i).at(k)->fValue, + recorder::global.at(i).at(k)->iValue); + } + } + +#ifdef WITH_VST + + /* writing master VST parameters */ + + writeMasterPlugins(PluginHost::MASTER_IN); + writeMasterPlugins(PluginHost::MASTER_OUT); + + /* writing VST parameters, channels. chan%d is mixer::channels.at(%d)->index, + * not mixer::chanels.at(%d)! */ + + int numPlugs; + int numParams; + Plugin *pPlugin; + + fprintf(fp, "# --- VST / channels --- \n"); + for (unsigned i=0; iindex, numPlugs); + + for (int j=0; jstatus) { + gLog("[PATCH] Plugin %d is in a bad status, skip writing params\n", i); + continue; + } + fprintf(fp, "chan%d_p%dpathfile=%s\n", ch->index, j, pPlugin->pathfile); + fprintf(fp, "chan%d_p%dbypass=%d\n", ch->index, j, pPlugin->bypass); + numParams = pPlugin->getNumParams(); + fprintf(fp, "chan%d_p%dnumParams=%d\n", ch->index, j, numParams); + + for (int k=0; kindex, j, k, pPlugin->getParam(k)); + } + } + +#endif + + fclose(fp); + return 1; +} + + +/* -------------------------------------------------------------------------- */ + +#ifdef WITH_VST + +int Patch::readMasterPlugins(int type) +{ + int nmp; + char chr; + int res = 1; + + if (type == PluginHost::MASTER_IN) { + chr = 'I'; + nmp = atoi(getValue("masterIPlugins").c_str()); + } + else { + chr = 'O'; + nmp = atoi(getValue("masterOPlugins").c_str()); + } + + for (int i=0; ibypass = atoi(getValue(tmp).c_str()); + sprintf(tmp, "master%c_p%dnumParams", chr, i); + int nparam = atoi(getValue(tmp).c_str()); + for (int j=0; jsetParam(j, pval); + } + } + res &= out; + } + + return res; +} + + +/* -------------------------------------------------------------------------- */ + + +void Patch::writeMasterPlugins(int type) +{ + char chr; + + if (type == PluginHost::MASTER_IN) { + fprintf(fp, "# --- VST / master in --- \n"); + chr = 'I'; + } + else { + fprintf(fp, "# --- VST / master out --- \n"); + chr = 'O'; + } + + int nmp = G_PluginHost.countPlugins(type); + fprintf(fp, "master%cPlugins=%d\n", chr, nmp); + + for (int i=0; istatus) { + gLog("[PATCH] Plugin %d is in a bad status, skip writing params\n", i); + continue; + } + + fprintf(fp, "master%c_p%dpathfile=%s\n", chr, i, pPlugin->pathfile); + fprintf(fp, "master%c_p%dbypass=%d\n", chr, i, pPlugin->bypass); + int numParams = pPlugin->getNumParams(); + fprintf(fp, "master%c_p%dnumParams=%d\n", chr, i, numParams); + + for (int j=0; jgetParam(j)); + } +} + +#endif diff --git a/src/patch.h b/src/patch.h new file mode 100644 index 0000000..e431b85 --- /dev/null +++ b/src/patch.h @@ -0,0 +1,95 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * patch + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#ifndef __PATCH_H__ +#define __PATCH_H__ + +#include +#include +#include +#include "dataStorage.h" +#include "const.h" + + +class Patch : public DataStorage { + +private: + int readMasterPlugins(int type); + void writeMasterPlugins(int type); + +public: + + char name[MAX_PATCHNAME_LEN]; + float version; + int lastTakeId; + int samplerate; + + int open(const char *file); + void setDefault(); + int close(); + + void getName (); + int getNumChans (); + int getNumColumns (); + std::string getSamplePath (int i); + float getVol (int i); + int getMode (int i); + int getMute (int i); + int getMute_s (int i); + int getSolo (int i); + int getBegin (int i); + int getEnd (int i, unsigned sampleSize); + float getBoost (int i); + float getPanLeft (int i); + float getPanRight (int i); + float getPitch (int i); + bool getRecActive (int i); + int getColumn (int i); + int getIndex (int i); + int getType (int i); + int getKey (int i); + uint32_t getMidiValue (int i, const char *c); + float getOutVol (); + float getInVol (); + float getBpm (); + int getBars (); + int getBeats (); + int getQuantize (); + bool getMetronome (); + int getLastTakeId (); + int getSamplerate (); + + int write(const char *file, const char *name, bool isProject); + int readRecs(); +#ifdef WITH_VST + int readPlugins(); +#endif +}; + +#endif diff --git a/src/plugin.cpp b/src/plugin.cpp new file mode 100644 index 0000000..17cace3 --- /dev/null +++ b/src/plugin.cpp @@ -0,0 +1,520 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + + +#ifdef WITH_VST + + +#include "plugin.h" +#include "log.h" + + +int Plugin::id_generator = 0; + + +/* ------------------------------------------------------------------ */ + + +Plugin::Plugin() + : module (NULL), + entryPoint(NULL), + plugin (NULL), + id (id_generator++), + program (-1), + bypass (false), + suspended (false) +{} + + +/* ------------------------------------------------------------------ */ + + +Plugin::~Plugin() { + unload(); +} + + +/* ------------------------------------------------------------------ */ + + +int Plugin::unload() { + + if (module == NULL) + return 1; + +#if defined(_WIN32) + + FreeLibrary((HMODULE)module); // FIXME - error checking + return 1; + +#elif defined(__linux__) + + return dlclose(module) == 0 ? 1 : 0; + +#elif defined(__APPLE__) + + /* we must unload bundles but because bundles may be in use for other + plug-in types it is important (and mandatory on certain plug-ins, + e.g. Korg) to do a check on the retain count. */ + + CFIndex retainCount = CFGetRetainCount(module); + + if (retainCount == 1) { + gLog("[plugin] retainCount == 1, can unload dlyb\n"); + CFBundleUnloadExecutable(module); + CFRelease(module); + } + else + gLog("[plugin] retainCount > 1 (%d), leave dlyb alone\n", (int) retainCount); + + return 1; + +#endif +} + + +/* ------------------------------------------------------------------ */ + + +int Plugin::load(const char *fname) { + + strcpy(pathfile, fname); + +#if defined(_WIN32) + + module = LoadLibrary(pathfile); + +#elif defined(__linux__) + + module = dlopen(pathfile, RTLD_LAZY); + +#elif defined(__APPLE__) + + /* creates the path to the bundle. In OSX vsts are stored inside the + * so-called bundles, just a directory with '.vst' extension. Finally + * we open the bundle with CFBundleCreate. */ + + CFStringRef pathStr = CFStringCreateWithCString(NULL, pathfile, kCFStringEncodingASCII); + CFURLRef bundleUrl = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, pathStr, kCFURLPOSIXPathStyle, true); + if(bundleUrl == NULL) { + gLog("[plugin] unable to create URL reference for plugin\n"); + status = 0; + return 0; + } + module = CFBundleCreate(kCFAllocatorDefault, bundleUrl); + +#endif + + if (module) { + + /* release (free) any old string */ + +#ifdef __APPLE__ + CFRelease(pathStr); + CFRelease(bundleUrl); +#endif + //strcpy(pathfile, fname); ??????????? + status = 1; + return 1; + } + else { + +#if defined(_WIN32) + + gLog("[plugin] unable to load %s, error: %d\n", fname, (int) GetLastError()); + +#elif defined(__linux__) + + gLog("[plugin] unable to load %s, error: %s\n", fname, dlerror()); + +#elif defined(__APPLE__) + + gLog("[plugin] unable to create bundle reference\n"); + CFRelease(pathStr); + CFRelease(bundleUrl); + +#endif + status = 0; + return 0; + } +} + + +/* ------------------------------------------------------------------ */ + + +int Plugin::init(VstIntPtr VSTCALLBACK (*HostCallback) (AEffect* effect, VstInt32 opcode, VstInt32 index, VstIntPtr value, void* ptr, float opt)) { + +#if defined(_WIN32) + + entryPoint = (vstPluginFuncPtr) GetProcAddress((HMODULE)module, "VSTPluginMain"); + if (!entryPoint) + entryPoint = (vstPluginFuncPtr) GetProcAddress((HMODULE)module, "main"); + +#elif defined(__linux__) + + /* bad stuff here: main() is a function pointer, dlsym(module, "main") + * returns a pointer to an object (void*) which should be casted to + * a pointer to function (main(), precisely). Unfortunately the standard + * forbids the conversion from void* to function pointer. So we do a raw + * mem copy from tmp to entryPoint. */ + + void *tmp; + tmp = dlsym(module, "VSTPluginMain"); + if (!tmp) + tmp = dlsym(module, "main"); + memcpy(&entryPoint, &tmp, sizeof(tmp)); + +#elif defined(__APPLE__) + + /* same also for Unix/OSX. */ + + void *tmp = NULL; + tmp = CFBundleGetFunctionPointerForName(module, CFSTR("VSTPluginMain")); + + if (!tmp) { + gLog("[plugin] entryPoint 'VSTPluginMain' not found\n"); + tmp = CFBundleGetFunctionPointerForName(module, CFSTR("main_macho")); // VST SDK < 2.4 + } + if (!tmp) { + gLog("[plugin] entryPoint 'main_macho' not found\n"); + tmp = CFBundleGetFunctionPointerForName(module, CFSTR("main")); + } + if (tmp) + memcpy(&entryPoint, &tmp, sizeof(tmp)); + else + gLog("[plugin] entryPoint 'main' not found\n"); + +#endif + + /* if entry point is found, add to plugin a pointer to hostCallback. Or + * in other words bind the callback to the plugin. */ + + if (entryPoint) { + gLog("[plugin] entryPoint found\n"); + plugin = entryPoint(HostCallback); + if (!plugin) { + gLog("[plugin] failed to create effect instance!\n"); + return 0; + } + } + else { + gLog("[plugin] entryPoint not found, unable to proceed\n"); + return 0; + } + + + /* check the magicNumber */ + /** WARNING: on Windows one can load any DLL! Why!?! */ + + if(plugin->magic == kEffectMagic) { + gLog("[plugin] magic number OK\n"); + return 1; + } + else { + gLog("[plugin] magic number is bad\n"); + return 0; + } +} + + +/* ------------------------------------------------------------------ */ + + +int Plugin::setup(int samplerate, int frames) { + + /* init plugin through the dispatcher with some basic infos */ + + plugin->dispatcher(plugin, effOpen, 0, 0, 0, 0); + plugin->dispatcher(plugin, effSetSampleRate, 0, 0, 0, samplerate); + plugin->dispatcher(plugin, effSetBlockSize, 0, frames, 0, 0); + + /* check SDK compatibility */ + + if (getSDKVersion() != kVstVersion) + gLog("[plugin] warning: different VST version (host: %d, plugin: %d)\n", kVstVersion, getSDKVersion()); + + return 1; +} + + +/* ------------------------------------------------------------------ */ + + +AEffect *Plugin::getPlugin() { + return plugin; +} + + +/* ------------------------------------------------------------------ */ + + +int Plugin::getId() { return id; } + + +/* ------------------------------------------------------------------ */ + +int Plugin::getSDKVersion() { + return plugin->dispatcher(plugin, effGetVstVersion, 0, 0, 0, 0); +} + + +/* ------------------------------------------------------------------ */ + + +void Plugin::getName(char *out) { + char tmp[128] = "\0"; + plugin->dispatcher(plugin, effGetEffectName, 0, 0, tmp, 0); + tmp[kVstMaxEffectNameLen-1] = '\0'; + strncpy(out, tmp, kVstMaxEffectNameLen); +} + + +/* ------------------------------------------------------------------ */ + + +void Plugin::getVendor(char *out) { + char tmp[128] = "\0"; + plugin->dispatcher(plugin, effGetVendorString, 0, 0, tmp, 0); + tmp[kVstMaxVendorStrLen-1] = '\0'; + strncpy(out, tmp, kVstMaxVendorStrLen); +} + + +/* ------------------------------------------------------------------ */ + + +void Plugin::getProduct(char *out) { + char tmp[128] = "\0"; + plugin->dispatcher(plugin, effGetProductString, 0, 0, tmp, 0); + tmp[kVstMaxProductStrLen-1] = '\0'; + strncpy(out, tmp, kVstMaxProductStrLen); +} + + +/* ------------------------------------------------------------------ */ + + +int Plugin::getNumPrograms() { return plugin->numPrograms; } + + +/* ------------------------------------------------------------------ */ + + +int Plugin::setProgram(int index) { + plugin->dispatcher(plugin, effBeginSetProgram, 0, 0, 0, 0); + plugin->dispatcher(plugin, effSetProgram, 0, index, 0, 0); + gLog("[plugin] program changed, index %d\n", index); + program = index; + return plugin->dispatcher(plugin, effEndSetProgram, 0, 0, 0, 0); +} + + +/* ------------------------------------------------------------------ */ + + +int Plugin::getNumParams() { return plugin->numParams; } + + +/* ------------------------------------------------------------------ */ + + +int Plugin::getNumInputs() { return plugin->numInputs; } + + +/* ------------------------------------------------------------------ */ + + +int Plugin::getNumOutputs() { return plugin->numOutputs; } + + +/* ------------------------------------------------------------------ */ + + +void Plugin::getProgramName(int index, char *out) { + char tmp[128] = "\0"; + plugin->dispatcher(plugin, effGetProgramNameIndexed, index, 0, tmp, 0); + tmp[kVstMaxProgNameLen-1] = '\0'; + strncpy(out, tmp, kVstMaxProgNameLen); +} + + +/* ------------------------------------------------------------------ */ + + +void Plugin::getParamName(int index, char *out) { + char tmp[128] = "\0"; + plugin->dispatcher(plugin, effGetParamName, index, 0, tmp, 0); + tmp[kVstMaxParamStrLen-1] = '\0'; + strncpy(out, tmp, kVstMaxParamStrLen); +} + + +/* ------------------------------------------------------------------ */ + + +void Plugin::getParamLabel(int index, char *out) { + char tmp[128] = "\0"; + plugin->dispatcher(plugin, effGetParamLabel, index, 0, tmp, 0); + tmp[kVstMaxParamStrLen-1] = '\0'; + strncpy(out, tmp, kVstMaxParamStrLen); +} + + +/* ------------------------------------------------------------------ */ + + +void Plugin::getParamDisplay(int index, char *out) { + char tmp[128] = "\0"; + plugin->dispatcher(plugin, effGetParamDisplay, index, 0, tmp, 0); + tmp[kVstMaxParamStrLen-1] = '\0'; + strncpy(out, tmp, kVstMaxParamStrLen); +} + + +/* ------------------------------------------------------------------ */ + + +float Plugin::getParam(int index) { + return plugin->getParameter(plugin, index); +} + + +/* ------------------------------------------------------------------ */ + + +void Plugin::setParam(int index, float value) { + plugin->setParameter(plugin, index, value); +} + + +/* ------------------------------------------------------------------ */ + + +bool Plugin::hasGui() { + return plugin->flags & effFlagsHasEditor; +} + + +/* ------------------------------------------------------------------ */ + + +void Plugin::openGui(void *w) { + long val = 0; +#ifdef __linux__ + val = (long) w; +#endif + plugin->dispatcher(plugin, effEditOpen, 0, val, w, 0); +} + + +/* ------------------------------------------------------------------ */ + + +void Plugin::closeGui() { + plugin->dispatcher(plugin, effEditClose, 0, 0, 0, 0); +} + + +/* ------------------------------------------------------------------ */ + + +int Plugin::getGuiWidth() { + ERect *pErect = NULL; + plugin->dispatcher(plugin, effEditGetRect, 0, 0, &pErect, 0); + return pErect->top + pErect->right; +} + + +/* ------------------------------------------------------------------ */ + + +int Plugin::getGuiHeight() { + ERect *pErect = NULL; + plugin->dispatcher(plugin, effEditGetRect, 0, 0, &pErect, 0); + return pErect->top + pErect->bottom; +} + + +/* ------------------------------------------------------------------ */ + + +void Plugin::idle() { + plugin->dispatcher(plugin, effEditIdle, 0, 0, NULL, 0); +} + + +/* ------------------------------------------------------------------ */ + + +void Plugin::processAudio(float **in, float **out, long frames) { + plugin->processReplacing(plugin, in, out, frames); +} + + +/* ------------------------------------------------------------------ */ + + +void Plugin::processEvents(VstEvents *events) { + plugin->dispatcher(plugin, effProcessEvents, 0, 0, events, 0.0); +} + + +/* ------------------------------------------------------------------ */ + + +void Plugin::resume() { + plugin->dispatcher(plugin, effMainsChanged, 0, 1, 0, 0); + suspended = false; +} + + +/* ------------------------------------------------------------------ */ + + +void Plugin::suspend() { + plugin->dispatcher(plugin, effMainsChanged, 0, 0, 0, 0); + suspended = true; +} + + +/* ------------------------------------------------------------------ */ + + +void Plugin::close() { + plugin->dispatcher(plugin, effClose, 0, 0, 0, 0); +} + + +/* ------------------------------------------------------------------ */ + + +void Plugin::getRect(ERect **out) { + plugin->dispatcher(plugin, effEditGetRect, 0, 0, out, 0); +} + + +#endif diff --git a/src/plugin.h b/src/plugin.h new file mode 100644 index 0000000..0dbaef7 --- /dev/null +++ b/src/plugin.h @@ -0,0 +1,168 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * plugin + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + +#ifdef WITH_VST + +#ifndef __PLUGIN_H +#define __PLUGIN_H + +#include + +/* before including aeffetx(x).h we must define __cdecl, otherwise VST + * headers can't be compiled correctly. In windows __cdecl is already + * defined. */ + +#ifdef __GNUC__ + #ifndef _WIN32 + #define __cdecl + #endif +#endif + +#include "vst/aeffectx.h" + +#if defined(_WIN32) + #include +#elif defined(__linux__) + #include + #include +#elif defined(__APPLE__) + #include +#endif + +#include // PATH_MAX + + +// Plugin's entry point +typedef AEffect* (*vstPluginFuncPtr)(audioMasterCallback host); + + +class Plugin { + +private: + +#if defined(_WIN32) || defined(__linux__) + void *module; // dll, so, ... +#elif defined(__APPLE__) + CFBundleRef module; // OSX bundle +#endif + + vstPluginFuncPtr entryPoint; // VST entry point + AEffect *plugin; // real plugin + + /* each plugin has an unique ID */ + + static int id_generator; + int id; + + /* program + * selected program. -1 if no program available */ + + int program; + + /* unload + * free plugin from memory. Calls dlclose and similars. */ + + int unload(); + +public: + Plugin(); + ~Plugin(); + + int load(const char *fname); + int init(VstIntPtr VSTCALLBACK (*HostCallback)(AEffect*, VstInt32, VstInt32, VstIntPtr, void*, float)); + int setup(int samplerate, int frames); + + AEffect *getPlugin(); + + /* get[Item]. + * Wrappers called by host when it wants info from the plugin. */ + + int getId(); + int getSDKVersion(); + void getName (char *out); + void getVendor (char *out); + void getProduct(char *out); + int getNumPrograms(); // list all programs + int setProgram(int index); // load a program + int getNumParams(); + int getNumInputs(); + int getNumOutputs(); + void getProgramName(int index, char *out); // program = preset + void getParamName(int index, char *out); + void getParamLabel(int index, char *out); // parameter's value(0, -39, ...) + void getParamDisplay(int index, char *out); // parameter's unit measurement (dB, Pan, ...) + float getParam(int index); + void getRect(ERect **out); + void setParam(int index, float value); + + bool hasGui(); + void openGui(void *w); + void closeGui(); + int getGuiWidth(); + int getGuiHeight(); + void idle(); + + void processAudio (float **in, float **out, long frames); + void processEvents(VstEvents *events); + void resume(); + void suspend(); + void close(); + + inline int getProgram() { return program; } + + /* there's a specific opcode for the bypass, but we don't trust the + * plugin's developers. */ + + bool bypass; + + /* the status of the plugin: + * 1: ok + * 0: missing (file not found) */ + + int status; + + /* suspended + * true after suspend(), false after resume(). A suspended plugin isn't + * processed by pluginHost. */ + + bool suspended; + + /* pathfile + * full filename path */ + + char pathfile[PATH_MAX]; + + /* window + * plugin must know its window in case of a resize via opcode */ + + class gWindow *window; +}; + +#endif + +#endif // #ifdef WITH_VST diff --git a/src/pluginHost.cpp b/src/pluginHost.cpp new file mode 100644 index 0000000..f28f5aa --- /dev/null +++ b/src/pluginHost.cpp @@ -0,0 +1,679 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * pluginHost + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + + +#ifdef WITH_VST + + +#include "pluginHost.h" +#include "conf.h" +#include "const.h" +#include "mixer.h" +#include "gd_mainWindow.h" +#include "channel.h" +#include "sampleChannel.h" +#include "midiChannel.h" +#include "kernelMidi.h" +#include "log.h" + + +extern Conf G_Conf; +extern Mixer G_Mixer; +extern PluginHost G_PluginHost; +extern unsigned G_beats; +extern gdMainWindow *mainWin; + + +PluginHost::PluginHost() { + + /* initially we fill vstTimeInfo with trash. Only when the plugin requests + * the opcode we load the right infos from G_Mixer. */ + + vstTimeInfo.samplePos = 0.0; + vstTimeInfo.sampleRate = G_Conf.samplerate; + vstTimeInfo.nanoSeconds = 0.0; + vstTimeInfo.ppqPos = 0.0; + vstTimeInfo.tempo = 120.0; + vstTimeInfo.barStartPos = 0.0; + vstTimeInfo.cycleStartPos = 0.0; + vstTimeInfo.cycleEndPos = 0.0; + vstTimeInfo.timeSigNumerator = 4; + vstTimeInfo.timeSigDenominator = 4; + vstTimeInfo.smpteOffset = 0; + vstTimeInfo.smpteFrameRate = 1; + vstTimeInfo.samplesToNextClock = 0; + vstTimeInfo.flags = 0; +} + + +/* ------------------------------------------------------------------ */ + + +PluginHost::~PluginHost() {} + + +/* ------------------------------------------------------------------ */ + + +int PluginHost::allocBuffers() { + + /** FIXME - ERROR CHECKING! */ + + /* never, ever use G_Conf.buffersize to alloc these chunks of memory. + * If you use JACK, that value would be meaningless. Always refer to + * kernelAudio::realBufsize. */ + + int bufSize = kernelAudio::realBufsize*sizeof(float); + + bufferI = (float **) malloc(2 * sizeof(float*)); + bufferI[0] = (float *) malloc(bufSize); + bufferI[1] = (float *) malloc(bufSize); + + bufferO = (float **) malloc(2 * sizeof(float*)); + bufferO[0] = (float *) malloc(bufSize); + bufferO[1] = (float *) malloc(bufSize); + + memset(bufferI[0], 0, bufSize); + memset(bufferI[1], 0, bufSize); + memset(bufferO[0], 0, bufSize); + memset(bufferO[1], 0, bufSize); + + gLog("[pluginHost] buffers allocated, buffersize = %d\n", 2*kernelAudio::realBufsize); + + //printOpcodes(); + + return 1; +} + + +/* ------------------------------------------------------------------ */ + + +VstIntPtr VSTCALLBACK PluginHost::HostCallback(AEffect *effect, VstInt32 opcode, VstInt32 index, VstIntPtr value, void *ptr, float opt) { + return G_PluginHost.gHostCallback(effect, opcode, index, value, ptr, opt); +} + + +/* ------------------------------------------------------------------ */ + + +VstIntPtr PluginHost::gHostCallback(AEffect *effect, VstInt32 opcode, VstInt32 index, VstIntPtr value, void *ptr, float opt) { + + /* warning: VST headers compiled with DECLARE_VST_DEPRECATED. */ + + switch (opcode) { + + /* 0 - Called after a control has changed in the editor and when + * the associated parameter should be automated. Index contains the + * param, opt the value. Thanks, but we don't need it now. It will + * be useful when recording actions from VST (in the future). */ + + case audioMasterAutomate: + return 0; + + /* 1 - host version (2.4) */ + + case audioMasterVersion: + return kVstVersion; + + /* 3 - Give idle time to Host application, e.g. if plug-in editor is + * doing mouse tracking in a modal loop. This a is multithread app, + * we don't need it. */ + + case audioMasterIdle: + return 0; + + /* 6 - tells the host that the plugin is an instrument. Deprecated. */ + + case DECLARE_VST_DEPRECATED(audioMasterWantMidi): + return 0; + + /* 7 - time infos */ + + case audioMasterGetTime: + vstTimeInfo.samplePos = G_Mixer.actualFrame; + vstTimeInfo.sampleRate = G_Conf.samplerate; + vstTimeInfo.tempo = G_Mixer.bpm; + vstTimeInfo.timeSigNumerator = G_Mixer.beats; + vstTimeInfo.timeSigDenominator = G_Mixer.bars; + vstTimeInfo.ppqPos = (G_Mixer.actualFrame / (float) G_Conf.samplerate) * (float) G_Mixer.bpm / 60.0f; + return (VstIntPtr) &vstTimeInfo; + + /* ? - requires a pointer to VstEvents. No vstEvents so far (v0.5.4) */ + + case audioMasterProcessEvents: + return 0; + + /* 13 - tells that numInputs/numOutputs are changed. Not supported and + * not needed. */ + + case audioMasterIOChanged: + return false; + + /* 14 - plugin needs idle calls (outside its editor window). Deprecated */ + + case DECLARE_VST_DEPRECATED(audioMasterNeedIdle): + return 0; + + /* 15 - requests to resize the editor window. w = index, h = value*/ + + case audioMasterSizeWindow: { + gWindow *window = NULL; + for (unsigned i=0; igetPlugin() == effect) + window = masterOut.at(i)->window; + + for (unsigned i=0; igetPlugin() == effect) + window = masterIn.at(i)->window; + + for (unsigned i=0; iplugins.size && !window; j++) + if (ch->plugins.at(j)->getPlugin() == effect) + window = ch->plugins.at(j)->window; + } + + if (window) { + gLog("[pluginHost] audioMasterSizeWindow: resizing window from plugin %p\n", (void*) effect); + if (index == 1 || value == 1) + gLog("[pluginHost] warning: non-sense values!\n"); + else + window->size((int)index, (int)value); + return 1; + } + else { + gLog("[pluginHost] audioMasterSizeWindow: window from plugin %p not found\n", (void*) effect); + return 0; + } + } + + /* 16 - sample rate */ + + case audioMasterGetSampleRate: + return G_Conf.samplerate; + + /* ?? - buffer size */ + + case audioMasterGetBlockSize: + return kernelAudio::realBufsize; + + case audioMasterGetInputLatency: + gLog("[pluginHost] requested opcode 'audioMasterGetInputLatency' (%d)\n", opcode); + return 0; + + case audioMasterGetOutputLatency: + gLog("[pluginHost] requested opcode 'audioMasterGetOutputLatency' (%d)\n", opcode); + return 0; + + /* 23 - wants to know what kind of process is that. + * kVstProcessLevelRealtime = currently in audio thread (where + * process is called). */ + + case audioMasterGetCurrentProcessLevel: + return kVstProcessLevelRealtime; + + /* 32 - vendor name */ + + case audioMasterGetVendorString: + strcpy((char*)ptr, "Monocasual"); + return 1; + + /* 32 - product name */ + + case audioMasterGetProductString: + strcpy((char*)ptr, "Giada"); + return 1; + + /* 33 - product version */ + + case audioMasterGetVendorVersion: + return (int) VERSIONE_FLOAT * 100; + + + /* 37 - Plugin asks Host if it implements the feature text. */ + + case audioMasterCanDo: + gLog("[pluginHost] audioMasterCanDo: %s\n", (char*)ptr); + if (!strcmp((char*)ptr, "sizeWindow") || + !strcmp((char*)ptr, "sendVstTimeInfo") || + !strcmp((char*)ptr, "sendVstMidiEvent") || + !strcmp((char*)ptr, "sendVstMidiEventFlagIsRealtime")) + return 1; // we can do all of that + else + return 0; + + /* 42 - Something has changed, update the host's 'multi-fx' display. + * Not supported right now, return 0. This opcode deals with the program + * changes, more infos http://www.asseca.com/vst-24-specs/amUpdateDisplay.html */ + + case audioMasterUpdateDisplay: + return 0; + + case audioMasterGetLanguage: + return kVstLangEnglish; + + /* ?? */ + + case audioMasterGetAutomationState: + gLog("[pluginHost] requested opcode 'audioMasterGetAutomationState' (%d)\n", opcode); + return 0; + + /* 43 - It tells the Host that if it needs to, it has to record + * automation data for this control. In other words this opcode is fired + * when the user starts to tweak a parameter with the mouse. + * Useful when the plugin actions will be recorded. */ + + case audioMasterBeginEdit: + return 0; + + /* 44 - no more interaction for the user, started with the previous + * opcode. */ + + case audioMasterEndEdit: + return 0; + + default: + gLog("[pluginHost] FIXME: host callback called with opcode %d\n", opcode); + return 0; + } +} + + +/* ------------------------------------------------------------------ */ + + +int PluginHost::addPlugin(const char *fname, int stackType, Channel *ch) { + + Plugin *p = new Plugin(); + bool success = true; + + gVector *pStack; + pStack = getStack(stackType, ch); + + if (!p->load(fname)) { + //delete p; + //return 0; + success = false; + } + + /* if the load failed we add a 'dead' plugin into the stack. This is + * useful to report a missing plugin. */ + + if (!success) { + pStack->add(p); + return 0; + } + + /* otherwise let's try to initialize it. */ + + else { + + /* try to init the plugin. If fails, delete it and return error. */ + + if (!p->init(&PluginHost::HostCallback)) { + delete p; + return 0; + } + + /* plugin setup */ + + p->setup(G_Conf.samplerate, kernelAudio::realBufsize); + + /* try to add the new plugin until succeed */ + + int lockStatus; + while (true) { + lockStatus = pthread_mutex_trylock(&G_Mixer.mutex_plugins); + if (lockStatus == 0) { + pStack->add(p); + pthread_mutex_unlock(&G_Mixer.mutex_plugins); + break; + } + } + + char name[256]; p->getName(name); + gLog("[pluginHost] plugin id=%d loaded (%s), stack type=%d, stack size=%d\n", p->getId(), name, stackType, pStack->size); + + /* p->resume() is suggested. Who knows... */ + + p->resume(); + + return 1; + } +} + + +/* ------------------------------------------------------------------ */ + + +void PluginHost::processStack(float *buffer, int stackType, Channel *ch) { + + gVector *pStack = getStack(stackType, ch); + + /* empty stack, stack not found or mixer not ready: do nothing */ + /// TODO - join evaluation + + if (!G_Mixer.ready) + return; + if (pStack == NULL) + return; + if (pStack->size == 0) + return; + + /* converting buffer from Giada to VST */ + + for (unsigned i=0; isize; i++) { + /// TODO - join evaluation + + if (pStack->at(i)->status != 1) + continue; + if (pStack->at(i)->suspended) + continue; + if (pStack->at(i)->bypass) + continue; + if (ch) { // process events if it's a channel stack + if (ch->type == CHANNEL_MIDI) { + ///gLog("events: %d\n", (((MidiChannel*)ch)->getVstEvents())->numEvents); + pStack->at(i)->processEvents(((MidiChannel*)ch)->getVstEvents()); + } + } + pStack->at(i)->processAudio(bufferI, bufferO, kernelAudio::realBufsize); + bufferI = bufferO; + } + + /* converting buffer from VST to Giada. A note for the future: if we + * overwrite (=) (as we do now) it's SEND, if we add (+) it's INSERT. */ + + for (unsigned i=0; i *pStack = getStack(stackType, ch); + for (unsigned i=0; isize; i++) { + if (pStack->at(i)->getId() == id) + return pStack->at(i); + } + return NULL; +} + + +/* ------------------------------------------------------------------ */ + + +Plugin *PluginHost::getPluginByIndex(int index, int stackType, Channel *ch) { + gVector *pStack = getStack(stackType, ch); + if (pStack->size == 0) + return NULL; + if ((unsigned) index >= pStack->size) + return NULL; + return pStack->at(index); +} + + +/* ------------------------------------------------------------------ */ + + +void PluginHost::freeStack(int stackType, Channel *ch) { + + gVector *pStack; + pStack = getStack(stackType, ch); + + if (pStack->size == 0) + return; + + int lockStatus; + while (true) { + lockStatus = pthread_mutex_trylock(&G_Mixer.mutex_plugins); + if (lockStatus == 0) { + for (unsigned i=0; isize; i++) { + if (pStack->at(i)->status == 1) { // only if plugin is ok + pStack->at(i)->suspend(); + pStack->at(i)->close(); + } + delete pStack->at(i); + } + pStack->clear(); + pthread_mutex_unlock(&G_Mixer.mutex_plugins); + break; + } + } + +} + + +/* ------------------------------------------------------------------ */ + + +void PluginHost::freeAllStacks() { + freeStack(PluginHost::MASTER_OUT); + freeStack(PluginHost::MASTER_IN); + for (unsigned i=0; i *pStack; + pStack = getStack(stackType, ch); + + /* try to delete the plugin until succeed. G_Mixer has priority. */ + + for (unsigned i=0; isize; i++) + if (pStack->at(i)->getId() == id) { + + if (pStack->at(i)->status == 0) { // no frills if plugin is missing + delete pStack->at(i); + pStack->del(i); + return; + } + else { + int lockStatus; + while (true) { + lockStatus = pthread_mutex_trylock(&G_Mixer.mutex_plugins); + if (lockStatus == 0) { + pStack->at(i)->suspend(); + pStack->at(i)->close(); + delete pStack->at(i); + pStack->del(i); + pthread_mutex_unlock(&G_Mixer.mutex_plugins); + gLog("[pluginHost] plugin id=%d removed\n", id); + return; + } + //else + //gLog("[pluginHost] waiting for mutex...\n"); + } + } + } + gLog("[pluginHost] plugin id=%d not found\n", id); +} + + +/* ------------------------------------------------------------------ */ + + +void PluginHost::swapPlugin(unsigned indexA, unsigned indexB, int stackType, Channel *ch) { + + gVector *pStack = getStack(stackType, ch); + + int lockStatus; + while (true) { + lockStatus = pthread_mutex_trylock(&G_Mixer.mutex_plugins); + if (lockStatus == 0) { + pStack->swap(indexA, indexB); + pthread_mutex_unlock(&G_Mixer.mutex_plugins); + gLog("[pluginHost] plugin at index %d and %d swapped\n", indexA, indexB); + return; + } + //else + //gLog("[pluginHost] waiting for mutex...\n"); + } +} + + +/* ------------------------------------------------------------------ */ + + +int PluginHost::getPluginIndex(int id, int stackType, Channel *ch) { + + gVector *pStack = getStack(stackType, ch); + + for (unsigned i=0; isize; i++) + if (pStack->at(i)->getId() == id) + return i; + return -1; +} + + +/* ------------------------------------------------------------------ */ + + +gVector *PluginHost::getStack(int stackType, Channel *ch) { + switch(stackType) { + case MASTER_OUT: + return &masterOut; + case MASTER_IN: + return &masterIn; + case CHANNEL: + return &ch->plugins; + default: + return NULL; + } +} + + +/* ------------------------------------------------------------------ */ + + +VstMidiEvent *PluginHost::createVstMidiEvent(uint32_t msg) +{ + VstMidiEvent *e = (VstMidiEvent*) malloc(sizeof(VstMidiEvent)); + + /* type = two types of events: MIDI event and MIDI system exclusive + * (aka sysex, not implemented). */ + + e->type = kVstMidiType; + e->byteSize = sizeof(VstMidiEvent); + + /* deltaFrames = sample frames related to the current block start + * sample position. */ + + e->deltaFrames = 0; + + /* flags = kVstMidiEventIsRealtime means that this event is played + * live (not in playback from a sequencer track). This allows the + * Plug-In to handle these flagged events with higher priority, + * especially when the Plug-In has a big latency */ + + e->flags = kVstMidiEventIsRealtime; + + /* midiData = 1 to 3 MIDI bytes; midiData[3] is reserved (zero) */ + + e->midiData[0] = kernelMidi::getB1(msg); // note on/off + channel + e->midiData[1] = kernelMidi::getB2(msg); // note number + e->midiData[2] = kernelMidi::getB3(msg); // velocity + e->midiData[3] = 0; + + /* noteLength = (in sample frames) of entire note, if available, + * else 0 */ + + e->noteLength = 0; + + /* noteOffset = offset (in sample frames) into note from note start + * if available, else 0 */ + + e->noteOffset = 0; + + /* noteOffVelocity = Note Off Velocity [0, 127]. */ + + e->noteOffVelocity = 0; + + return e; +} + + +/* ------------------------------------------------------------------ */ + + +unsigned PluginHost::countPlugins(int stackType, Channel *ch) { + gVector *pStack = getStack(stackType, ch); + return pStack->size; +} + + +#endif // #ifdef WITH_VST diff --git a/src/pluginHost.h b/src/pluginHost.h new file mode 100644 index 0000000..9b4db08 --- /dev/null +++ b/src/pluginHost.h @@ -0,0 +1,132 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * pluginHost + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + +#ifdef WITH_VST + +#ifndef __PLUGIN_HOST_ +#define __PLUGIN_HOST_ + +#include "ge_window.h" +#include "plugin.h" +#include "utils.h" +#include "init.h" +#include "const.h" + + +class PluginHost { + +private: + + /* VSTs have a different buffer model: + * + * buffer[0] = channel left + * buffer[1] = channel right + * buffer[0][....] = all signals from left chan + * buffer[1][....] = all signals from right chan */ + + float **bufferI; + float **bufferO; + + /* VST struct containing infos on tempo (bpm, freq, smtpe, ...). */ + + VstTimeInfo vstTimeInfo; + +public: + + /* stack types. Use them together with getStack() in order to geta + * pointer to the right stack. */ + + enum stackType { + MASTER_OUT, + MASTER_IN, + CHANNEL + }; + + /* stack of Plugins */ + + gVector masterOut; + gVector masterIn; + + PluginHost(); + ~PluginHost(); + + int allocBuffers(); + + /* The plugin can ask the host if it supports a given capability, + * which is done through the HostCallback() function. + * + * Why static? This is a callback attached to each plugin in the stack + * and C++ callback functions need to be static when declared in class. + * + * OPCODE LIST: + * base version: vstsdk2.4/pluginterfaces/aeffect.h (vst 1.x) + * enhanced v. : vstsdk2.4/pluginterfaces/effectx.h (vst 2.x) */ + + static VstIntPtr VSTCALLBACK HostCallback(AEffect *effect, VstInt32 opcode, VstInt32 index, VstIntPtr value, void *ptr, float opt); + VstIntPtr gHostCallback(AEffect *effect, VstInt32 opcode, VstInt32 index, VstIntPtr value, void *ptr, float opt); + + int addPlugin(const char *fname, int stackType, class Channel *ch=NULL); + + void processEvents(float *buffer, class Channel *ch); + + /* processStack + * apply the fx list to the buffer. */ + + void processStack(float *buffer, int stackType, class Channel *ch=NULL); + + /* processStackOffline + * apply the fx list to a longer chunk of data */ + + void processStackOffline(float *buffer, int stackType, class Channel *ch, int size); + + /* createVstMidiEvent + * return a pointer to a new VstMidiEvent structure. */ + + VstMidiEvent *createVstMidiEvent(uint32_t msg); + + gVector *getStack(int stackType, class Channel *ch=NULL); + + Plugin *getPluginById(int id, int stackType, class Channel *ch=NULL); + + Plugin *getPluginByIndex(int index, int stackType, class Channel *ch=NULL); + + int getPluginIndex(int id, int stackType, class Channel *ch=NULL); + + unsigned countPlugins(int stackType, class Channel *ch=NULL); + + void freeStack(int stackType, class Channel *ch=NULL); + + void freeAllStacks(); + + void freePlugin(int id, int stackType, class Channel *ch=NULL); + + void swapPlugin(unsigned indexA, unsigned indexB, int stackType, class Channel *ch=NULL); +}; +#endif + +#endif // #ifdef WITH_VST diff --git a/src/recorder.cpp b/src/recorder.cpp new file mode 100644 index 0000000..a0c8f0d --- /dev/null +++ b/src/recorder.cpp @@ -0,0 +1,698 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * recorder + * Action recorder. + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + + +#include +#include "recorder.h" +#include "const.h" +#include "utils.h" +#include "mixer.h" +#include "mixerHandler.h" +#include "kernelAudio.h" +#include "pluginHost.h" +#include "kernelMidi.h" +#include "utils.h" +#include "patch.h" +#include "conf.h" +#include "channel.h" +#include "sampleChannel.h" +#include "log.h" + + +#ifdef WITH_VST +extern PluginHost G_PluginHost; +#endif + + +extern Mixer G_Mixer; +extern Patch f_patch; +extern Conf G_Conf; + + +namespace recorder +{ +gVector frames; +gVector< gVector > global; +gVector actions; + +bool active = false; +bool sortedActions = false; + +composite cmp; + + +/* ------------------------------------------------------------------ */ + + +void init() +{ + sortedActions = false; + active = false; + clearAll(); +} + + +/* ------------------------------------------------------------------ */ + + +bool canRec(Channel *ch) +{ + /* NO recording if: + * recorder is inactive + * mixer is not running + * mixer is recording a take in this channel ch + * channel is empty */ + + if (!active || !G_Mixer.running || G_Mixer.chanInput == ch || (ch->type == CHANNEL_SAMPLE && ((SampleChannel*)ch)->wave == NULL)) + return 0; + return 1; +} + + +/* ------------------------------------------------------------------ */ + + +void rec(int index, int type, int frame, uint32_t iValue, float fValue) +{ + /* make sure frame is even */ + + if (frame % 2 != 0) + frame++; + + /* allocating the action */ + + action *a = (action*) malloc(sizeof(action)); + a->chan = index; + a->type = type; + a->frame = frame; + a->iValue = iValue; + a->fValue = fValue; + + /* check if the frame exists in the stack. If it exists, we don't extend + * the stack, but we add (or push) a new action to it. */ + + int frameToExpand = frames.size; + for (int i=0; ichan == index && + ac->type == type && + ac->frame == frame && + ac->iValue == iValue && + ac->fValue == fValue) + return; + } + + global.at(frameToExpand).add(a); // expand array + } + + /* if WITH_VST create a new VST event and attach it to our action. + * Nota bene: the VST event occurs on localFrame=0: this is a + * user-generated event after all! */ + +#ifdef WITH_VST + if (type == ACTION_MIDI) + a->event = G_PluginHost.createVstMidiEvent(a->iValue); +#endif + + /* don't activate the channel (readActions == false), it's up to + * the other layers */ + + Channel *ch = G_Mixer.getChannelByIndex(index); + ch->hasActions = true; + + sortedActions = false; + + gLog("[REC] action recorded, type=%d frame=%d chan=%d iValue=%d (%X) fValue=%f\n", + a->type, a->frame, a->chan, a->iValue, a->iValue, a->fValue); + //print(); +} + + +/* ------------------------------------------------------------------ */ + + +void clearChan(int index) +{ + gLog("[REC] clearing chan %d...\n", index); + + for (unsigned i=0; ichan == index) { +#ifdef WITH_VST + if (a->type == ACTION_MIDI) + free(a->event); +#endif + free(a); + global.at(i).del(j); + } + else + j++; + } + } + + Channel *ch = G_Mixer.getChannelByIndex(index); + ch->hasActions = false; + optimize(); + //print(); +} + + +/* ------------------------------------------------------------------ */ + + +void clearAction(int index, char act) +{ + gLog("[REC] clearing action %d from chan %d...\n", act, index); + for (unsigned i=0; ichan == index && (act & a->type) == a->type) { // bitmask + free(a); + global.at(i).del(j); + } + else + j++; + } + } + Channel *ch = G_Mixer.getChannelByIndex(index); + ch->hasActions = false; /// FIXME - why this? Isn't it useless if we call chanHasActions? + optimize(); + chanHasActions(index); /// FIXME + //print(); +} + + +/* ------------------------------------------------------------------ */ + + +void deleteAction(int chan, int frame, char type, bool checkValues, uint32_t iValue, float fValue) +{ + /* make sure frame is even */ + + if (frame % 2 != 0) + frame++; + + /* find the frame 'frame' */ + + bool found = false; + for (unsigned i=0; ichan == chan && a->type == (type & a->type)); + if (checkValues) + doit &= (a->iValue == iValue && a->fValue == fValue); + + if (doit) { + int lockStatus = 0; + while (lockStatus == 0) { + lockStatus = pthread_mutex_trylock(&G_Mixer.mutex_recs); + if (lockStatus == 0) { +#ifdef WITH_VST + if (type == ACTION_MIDI) + free(a->event); +#endif + free(a); + global.at(i).del(j); + pthread_mutex_unlock(&G_Mixer.mutex_recs); + found = true; + break; + } + else + gLog("[REC] delete action: waiting for mutex...\n"); + } + } + } + } + } + if (found) { + optimize(); + chanHasActions(chan); + gLog("[REC] action deleted, type=%d frame=%d chan=%d iValue=%d (%X) fValue=%f\n", + type, frame, chan, iValue, iValue, fValue); + } + else + gLog("[REC] 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) +{ + sortActions(); + gVector dels; + + for (unsigned i=0; i frame_a && frames.at(i) < frame_b) + dels.add(frames.at(i)); + + for (unsigned i=0; i 0) { + for (unsigned i=0; itype == ACTION_MIDI) + free(global.at(i).at(k)->event); +#endif + free(global.at(i).at(k)); // free action + } + global.at(i).clear(); // free action container + global.del(i); + } + } + + for (unsigned i=0; ihasActions = false; + if (G_Mixer.channels.at(i)->type == CHANNEL_SAMPLE) + ((SampleChannel*)G_Mixer.channels.at(i))->readActions = false; + } + + 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.del(i); + frames.del(i); + } + else + i++; + } + + sortActions(); +} + + +/* ------------------------------------------------------------------ */ + + +void sortActions() +{ + if (sortedActions) + return; + for (unsigned i=0; i frames.at(i)) { + frames.swap(j, i); + global.swap(j, i); + } + sortedActions = true; + //print(); +} + + +/* ------------------------------------------------------------------ */ + + +void updateBpm(float oldval, float newval, int oldquanto) +{ + for (unsigned i=0; i 0 && scarto <= 6) + frames.at(i) = frames.at(i) + scarto; + } + + /* never ever have odd frames. */ + + if (frames.at(i) % 2 != 0) + frames.at(i)++; + } + + /* update structs */ + + for (unsigned i=0; iframe = frames.at(i); + } + } + + //print(); +} + + +/* ------------------------------------------------------------------ */ + + +void updateSamplerate(int systemRate, int patchRate) +{ + /* diff ratio: systemRate / patchRate + * e.g. 44100 / 96000 = 0.4... */ + + if (systemRate == patchRate) + return; + + gLog("[REC] systemRate (%d) != patchRate (%d), converting...\n", systemRate, patchRate); + + float ratio = systemRate / (float) patchRate; + for (unsigned i=0; iframe = frames.at(i); + } + } +} + + +/* ------------------------------------------------------------------ */ + + +void expand(int old_fpb, int new_fpb) +{ + /* this algorithm requires multiple passages if we expand from e.g. 2 + * to 16 beats, precisely 16 / 2 - 1 = 7 times (-1 is the first group, + * which exists yet). If we expand by a non-multiple, the result is zero, + * due to float->int implicit cast */ + + unsigned pass = (int) (new_fpb / old_fpb) - 1; + if (pass == 0) pass = 1; + + unsigned init_fs = frames.size; + + for (unsigned z=1; z<=pass; z++) { + for (unsigned i=0; ichan, a->type, newframe, a->iValue, a->fValue); + } + } + } + gLog("[REC] 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; khasActions = false; + return; + } + for (unsigned i=0; ihasActions; i++) { + for (unsigned j=0; jhasActions; j++) { + if (global.at(i).at(j)->chan == index) + ch->hasActions = true; + } + } +} + + +/* ------------------------------------------------------------------ */ + + +int getNextAction(int chan, char type, int frame, action **out, uint32_t iValue) +{ + sortActions(); // mandatory + + unsigned i=0; + while (i < frames.size && frames.at(i) <= frame) i++; + + if (i == frames.size) // no further actions past 'frame' + return -1; + + for (; ichan == chan && (type & a->type) == a->type) { + if (iValue == 0 || (iValue != 0 && a->iValue == iValue)) { + *out = global.at(i).at(j); + return 1; + } + } + } + + return -2; // no 'type' actions found +} + + +/* ------------------------------------------------------------------ */ + + +int getAction(int chan, char action, int frame, struct action **out) +{ + for (unsigned i=0; iframe && + action == global.at(i).at(j)->type && + chan == global.at(i).at(j)->chan) + { + *out = global.at(i).at(j); + return 1; + } + return 0; +} + + +/* ------------------------------------------------------------------ */ + + +void startOverdub(int index, char actionMask, int frame) +{ + /* prepare the composite struct */ + + if (actionMask == ACTION_KEYS) { + cmp.a1.type = ACTION_KEYPRESS; + cmp.a2.type = ACTION_KEYREL; + } + else { + cmp.a1.type = ACTION_MUTEON; + cmp.a2.type = ACTION_MUTEOFF; + } + cmp.a1.chan = index; + cmp.a2.chan = index; + cmp.a1.frame = frame; + // cmp.a2.frame doesn't exist yet + + /* avoid underlying action truncation: if action2.type == nextAction: + * you are in the middle of a composite action, truncation needed */ + + rec(index, cmp.a1.type, frame); + + action *act = NULL; + 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-kernelAudio::realBufsize; + if (truncFrame < 0) + truncFrame = 0; + gLog("[REC] add truncation at frame %d, type=%d\n", truncFrame, cmp.a2.type); + rec(index, cmp.a2.type, truncFrame); + } + } + + SampleChannel *ch = (SampleChannel*) G_Mixer.getChannelByIndex(index); + ch->readActions = false; // don't use disableRead() +} + + +/* ------------------------------------------------------------------ */ + + +void stopOverdub(int frame) +{ + cmp.a2.frame = frame; + bool ringLoop = false; + bool nullLoop = false; + + /* ring loop verification, i.e. a composite action with key_press at + * frame N and key_release at frame M, with M <= N */ + + if (cmp.a2.frame < cmp.a1.frame) { + ringLoop = true; + gLog("[REC] ring loop! frame1=%d < frame2=%d\n", cmp.a1.frame, cmp.a2.frame); + rec(cmp.a2.chan, cmp.a2.type, G_Mixer.totalFrames); // record at the end of the sequencer + } + else + if (cmp.a2.frame == cmp.a1.frame) { + nullLoop = true; + gLog("[REC] null loop! frame1=%d == frame2=%d\n", cmp.a1.frame, cmp.a2.frame); + deleteAction(cmp.a1.chan, cmp.a1.frame, cmp.a1.type, false); // false == don't check values + } + + SampleChannel *ch = (SampleChannel*) G_Mixer.getChannelByIndex(cmp.a2.chan); + ch->readActions = false; // don't use disableRead() + + /* remove any nested action between keypress----keyrel, then record */ + + if (!nullLoop) + deleteActions(cmp.a2.chan, cmp.a1.frame, cmp.a2.frame, cmp.a1.type); + deleteActions(cmp.a2.chan, cmp.a1.frame, cmp.a2.frame, cmp.a2.type); + + if (!ringLoop && !nullLoop) { + rec(cmp.a2.chan, cmp.a2.type, cmp.a2.frame); + + /* avoid underlying action truncation, if keyrel happens inside a + * composite action */ + + action *act = NULL; + int res = getNextAction(cmp.a2.chan, cmp.a1.type | cmp.a2.type, cmp.a2.frame, &act); + if (res == 1) { + if (act->type == cmp.a2.type) { + gLog("[REC] add truncation at frame %d, type=%d\n", act->frame, act->type); + deleteAction(act->chan, act->frame, act->type, false); // false == don't check values + } + } + } +} + + +/* ------------------------------------------------------------------ */ + + +void print() +{ + gLog("[REC] ** print debug **\n"); + for (unsigned i=0; itype, global.at(i).at(j)->chan, global.at(i).at(j)->frame); + } + } +} + +} // namespace diff --git a/src/recorder.h b/src/recorder.h new file mode 100644 index 0000000..0fd378d --- /dev/null +++ b/src/recorder.h @@ -0,0 +1,192 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * recorder + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + +#ifndef RECORDER_H +#define RECORDER_H + +#include +#include +#include "utils.h" +#include "const.h" +#include "mixer.h" + +#ifdef WITH_VST + +/* before including aeffetx(x).h we must define __cdecl, otherwise VST + * headers can't be compiled correctly. In windows __cdecl is already + * defined. */ + + #ifdef __GNUC__ + #ifndef _WIN32 + #define __cdecl + #endif + #endif + #include "vst/aeffectx.h" +#endif + +/* + * [global0]-->[gVector<_action*>0]-->[a0][a1][a2] 0[frames1] + * [global1]-->[gVector<_action*>1]-->[a0][a1][a2] 1[frames2] + * [global2]-->[gVector<_action*>2]-->[a0][a1][a2] 2[frames3] + * [global3]-->[gVector<_action*>3]-->[a0][a1][a2] 3[frames4] + * */ + +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 + + /* if VST store here a pointer to a vstEvent. */ + +#ifdef WITH_VST + VstMidiEvent *event; +#endif +}; + +/* composite + * a group of two actions (keypress+keyrel, muteon+muteoff) used during + * the overdub process */ + +struct composite { + action a1; + action a2; +}; + +extern gVector frames; // frame counter (sentinel) frames.size == global.size +extern gVector< gVector > global; // container of containers of actions +extern gVector actions; // container of actions + +extern bool active; +extern bool sortedActions; // are actions sorted via sortActions()? + +/* init + * everything starts from here. */ + +void init(); + +/* chanHasActions + * Check if the channel has at least one action recorded. If false, sets + * ch->hasActions = false. Used after an action deletion. */ + +void chanHasActions(int chan); + +/* canRec + * can a channel rec an action? Call this one BEFORE rec(). */ + +bool canRec(Channel *ch); + +/* 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, uint32_t iValue=0, float fValue=0.0); + +/* deleteActions + * delete A RANGE of actions from frame_a to frame_b in channel 'chan'. + * 'type' can be a bitmask. Exclusive range (frame_a, frame_b). */ + +void deleteActions(int chan, int frame_a, int frame_b, char type); + +/* 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 + * return the nearest action in chan 'chan' of type 'action' starting + * from 'frame'. Action can be a bitmask. If iValue != -1 search for + * next action with iValue == iValue: useful for MIDI key_release. */ + +int getNextAction(int chan, char action, int frame, struct action **out, uint32_t iValue=0); + +/* getAction + * return a pointer to action in chan 'chan' of type 'action' at frame + * 'frame'. */ + +int getAction(int chan, char action, int frame, struct action **out); + +/* start/endOverdub */ + +void startOverdub(int chan, char action, int frame); +void stopOverdub(int frame); + +/* print + * debug of the frame stack. */ + +void print(); + +} // namespace + +#endif diff --git a/src/resource.h b/src/resource.h new file mode 100644 index 0000000..d771ba8 --- /dev/null +++ b/src/resource.h @@ -0,0 +1 @@ +#define IDI_ICON1 101 \ No newline at end of file diff --git a/src/resource.rc b/src/resource.rc new file mode 100644 index 0000000..fb0be40 --- /dev/null +++ b/src/resource.rc @@ -0,0 +1,2 @@ +#include "resource.h" +IDI_ICON1 ICON DISCARDABLE "giada.ico" \ No newline at end of file diff --git a/src/rtaudio-mod/Makefile.in b/src/rtaudio-mod/Makefile.in new file mode 100644 index 0000000..89cacdc --- /dev/null +++ b/src/rtaudio-mod/Makefile.in @@ -0,0 +1,75 @@ +### Do not edit -- Generated by 'configure --with-whatever' from Makefile.in +### RtAudio library Makefile + +RM = /bin/rm +LN = /bin/ln + +OBJECTS = RtAudio.o @objects@ + +LIBNAME = librtaudio +STATIC = $(LIBNAME).a +SHARED = @sharedlib@ +RELEASE = 4.1.1 +MAJOR = 4 +LIBRARIES = $(STATIC) $(SHARED) + +CC = @CXX@ +AR = @AR@ +RANLIB = @RANLIB@ + +DEFS = @CPPFLAGS@ +CFLAGS = @CXXFLAGS@ -Iinclude -fPIC + +PREFIX = @prefix@ + +all : $(LIBRARIES) + +tests: + cd tests && $(MAKE) all + +$(LIBRARIES): $(OBJECTS) + $(AR) ruv $(STATIC) $(OBJECTS) + ranlib $(STATIC) + $(CC) -fPIC @libflags@ $(OBJECTS) @LIBS@ + $(LN) -sf @sharedname@ $(SHARED) + $(LN) -sf @sharedname@ $(SHARED).$(MAJOR) + +%.o : %.cpp + $(CC) $(CFLAGS) $(DEFS) -c $(<) -o $@ + +%.o : include/%.cpp + $(CC) $(CFLAGS) $(DEFS) -c $(<) -o $@ + +install: + install --mode=755 $(STATIC) $(PREFIX)/lib/ + install --mode=755 @sharedname@ $(PREFIX)/lib/ + $(LN) -sf @sharedname@ $(PREFIX)/lib/$(SHARED) + $(LN) -sf @sharedname@ $(PREFIX)/lib/$(SHARED).$(MAJOR) + install --mode=644 $(LIBNAME).pc $(PREFIX)/lib/pkgconfig + install --mode=644 RtAudio.h $(PREFIX)/include/ + install --mode=755 rtaudio-config $(PREFIX)/bin/ + +uninstall: + -@rm -vf $(patsubst %,$(PREFIX)/lib/%, $(LIBRARIES) $(SHARED).$(MAJOR) $(SHARED).$(RELEASE)) + -@rm -vf $(PREFIX)/lib/pkgconfig/$(LIBNAME).pc + -@rm -vf $(PREFIX)/bin/rtaudio-config + +clean : + $(RM) -f $(LIBRARIES) @sharedname@ $(SHARED)* + $(RM) -f $(OBJECTS) + $(RM) -f *~ + cd tests && $(MAKE) clean + +distclean: + $(RM) -f $(LIBRARIES) @sharedname@ $(SHARED)* + $(RM) -f $(OBJECTS) + $(RM) -f *~ + $(RM) -rf config.log config.status autom4te.cache Makefile rtaudio-config $(LIBNAME).pc + cd tests && $(MAKE) distclean + +strip : + strip $(LIBRARIES) + ranlib $(LIBRARIES) + cd tests && $(MAKE) strip + +.PHONY: clean distclean strip install uninstall diff --git a/src/rtaudio-mod/RtAudio.cpp b/src/rtaudio-mod/RtAudio.cpp new file mode 100644 index 0000000..5716c59 --- /dev/null +++ b/src/rtaudio-mod/RtAudio.cpp @@ -0,0 +1,10145 @@ +/************************************************************************/ +/*! \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-2014 Gary P. Scavone + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + Any person wishing to distribute modifications to the Software is + asked to send the modifications to the original developer so that + they can be incorporated into the canonical version. This is, + however, not a binding provision of this license. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +/************************************************************************/ + +// RtAudio: Version 4.1.1 + +#include "RtAudio.h" +#include +#include +#include +#include + +// Static variable definitions. +const unsigned int RtApi::MAX_SAMPLE_RATES = 14; +const unsigned int RtApi::SAMPLE_RATES[] = { + 4000, 5512, 8000, 9600, 11025, 16000, 22050, + 32000, 44100, 48000, 88200, 96000, 176400, 192000 +}; + +#if defined(__WINDOWS_DS__) || defined(__WINDOWS_ASIO__) || defined(__WINDOWS_WASAPI__) + #define MUTEX_INITIALIZE(A) InitializeCriticalSection(A) + #define MUTEX_DESTROY(A) DeleteCriticalSection(A) + #define MUTEX_LOCK(A) EnterCriticalSection(A) + #define MUTEX_UNLOCK(A) LeaveCriticalSection(A) +#elif defined(__LINUX_ALSA__) || defined(__LINUX_PULSE__) || defined(__UNIX_JACK__) || defined(__LINUX_OSS__) || defined(__MACOSX_CORE__) + // pthread API + #define MUTEX_INITIALIZE(A) pthread_mutex_init(A, NULL) + #define MUTEX_DESTROY(A) pthread_mutex_destroy(A) + #define MUTEX_LOCK(A) pthread_mutex_lock(A) + #define MUTEX_UNLOCK(A) pthread_mutex_unlock(A) +#else + #define MUTEX_INITIALIZE(A) abs(*A) // dummy definitions + #define MUTEX_DESTROY(A) abs(*A) // dummy definitions +#endif + +// *************************************************** // +// +// RtAudio definitions. +// +// *************************************************** // + +std::string RtAudio :: getVersion( void ) throw() +{ + return RTAUDIO_VERSION; +} + +void RtAudio :: getCompiledApi( std::vector &apis ) throw() +{ + apis.clear(); + + // The order here will control the order of RtAudio's API search in + // the constructor. +#if defined(__UNIX_JACK__) + apis.push_back( UNIX_JACK ); +#endif +#if defined(__LINUX_ALSA__) + apis.push_back( LINUX_ALSA ); +#endif +#if defined(__LINUX_PULSE__) + apis.push_back( LINUX_PULSE ); +#endif +#if defined(__LINUX_OSS__) + apis.push_back( LINUX_OSS ); +#endif +#if defined(__WINDOWS_ASIO__) + apis.push_back( WINDOWS_ASIO ); +#endif +#if defined(__WINDOWS_WASAPI__) + apis.push_back( WINDOWS_WASAPI ); +#endif +#if defined(__WINDOWS_DS__) + apis.push_back( WINDOWS_DS ); +#endif +#if defined(__MACOSX_CORE__) + apis.push_back( MACOSX_CORE ); +#endif +#if defined(__RTAUDIO_DUMMY__) + apis.push_back( RTAUDIO_DUMMY ); +#endif +} + +void RtAudio :: openRtApi( RtAudio::Api api ) +{ + if ( rtapi_ ) + delete rtapi_; + rtapi_ = 0; + +#if defined(__UNIX_JACK__) + if ( api == UNIX_JACK ) + rtapi_ = new RtApiJack(); +#endif +#if defined(__LINUX_ALSA__) + if ( api == LINUX_ALSA ) + rtapi_ = new RtApiAlsa(); +#endif +#if defined(__LINUX_PULSE__) + if ( api == LINUX_PULSE ) + rtapi_ = new RtApiPulse(); +#endif +#if defined(__LINUX_OSS__) + if ( api == LINUX_OSS ) + rtapi_ = new RtApiOss(); +#endif +#if defined(__WINDOWS_ASIO__) + if ( api == WINDOWS_ASIO ) + rtapi_ = new RtApiAsio(); +#endif +#if defined(__WINDOWS_WASAPI__) + if ( api == WINDOWS_WASAPI ) + rtapi_ = new RtApiWasapi(); +#endif +#if defined(__WINDOWS_DS__) + if ( api == WINDOWS_DS ) + rtapi_ = new RtApiDs(); +#endif +#if defined(__MACOSX_CORE__) + if ( api == MACOSX_CORE ) + rtapi_ = new RtApiCore(); +#endif +#if defined(__RTAUDIO_DUMMY__) + if ( api == RTAUDIO_DUMMY ) + rtapi_ = new RtApiDummy(); +#endif +} + +RtAudio :: RtAudio( RtAudio::Api api ) +{ + rtapi_ = 0; + + if ( api != UNSPECIFIED ) { + // Attempt to open the specified API. + openRtApi( api ); + if ( rtapi_ ) return; + + // No compiled support for specified API value. Issue a debug + // warning and continue as if no API was specified. + std::cerr << "\nRtAudio: no compiled support for specified API argument!\n" << std::endl; + } + + // Iterate through the compiled APIs and return as soon as we find + // one with at least one device or we reach the end of the list. + std::vector< RtAudio::Api > apis; + getCompiledApi( apis ); + for ( unsigned int i=0; igetDeviceCount() ) break; + } + + if ( rtapi_ ) return; + + // It should not be possible to get here because the preprocessor + // definition __RTAUDIO_DUMMY__ is automatically defined if no + // API-specific definitions are passed to the compiler. But just in + // case something weird happens, we'll thow an error. + std::string errorText = "\nRtAudio: no compiled API support found ... critical error!!\n\n"; + throw( RtAudioError( errorText, RtAudioError::UNSPECIFIED ) ); +} + +RtAudio :: ~RtAudio() throw() +{ + if ( rtapi_ ) + delete rtapi_; +} + +void RtAudio :: openStream( RtAudio::StreamParameters *outputParameters, + RtAudio::StreamParameters *inputParameters, + RtAudioFormat format, unsigned int sampleRate, + unsigned int *bufferFrames, + RtAudioCallback callback, void *userData, + RtAudio::StreamOptions *options, + RtAudioErrorCallback errorCallback ) +{ + return rtapi_->openStream( outputParameters, inputParameters, format, + sampleRate, bufferFrames, callback, + userData, options, errorCallback ); +} + +// *************************************************** // +// +// Public RtApi definitions (see end of file for +// private or protected utility functions). +// +// *************************************************** // + +RtApi :: RtApi() +{ + stream_.state = STREAM_CLOSED; + stream_.mode = UNINITIALIZED; + stream_.apiHandle = 0; + stream_.userBuffer[0] = 0; + stream_.userBuffer[1] = 0; + MUTEX_INITIALIZE( &stream_.mutex ); + showWarnings_ = true; + firstErrorOccurred_ = false; +} + +RtApi :: ~RtApi() +{ + MUTEX_DESTROY( &stream_.mutex ); +} + +void RtApi :: openStream( RtAudio::StreamParameters *oParams, + RtAudio::StreamParameters *iParams, + RtAudioFormat format, unsigned int sampleRate, + unsigned int *bufferFrames, + RtAudioCallback callback, void *userData, + RtAudio::StreamOptions *options, + RtAudioErrorCallback errorCallback ) +{ + if ( stream_.state != STREAM_CLOSED ) { + errorText_ = "RtApi::openStream: a stream is already open!"; + error( RtAudioError::INVALID_USE ); + return; + } + + // Clear stream information potentially left from a previously open stream. + clearStreamInfo(); + + if ( oParams && oParams->nChannels < 1 ) { + errorText_ = "RtApi::openStream: a non-NULL output StreamParameters structure cannot have an nChannels value less than one."; + error( RtAudioError::INVALID_USE ); + return; + } + + if ( iParams && iParams->nChannels < 1 ) { + errorText_ = "RtApi::openStream: a non-NULL input StreamParameters structure cannot have an nChannels value less than one."; + error( RtAudioError::INVALID_USE ); + return; + } + + if ( oParams == NULL && iParams == NULL ) { + errorText_ = "RtApi::openStream: input and output StreamParameters structures are both NULL!"; + error( RtAudioError::INVALID_USE ); + return; + } + + if ( formatBytes(format) == 0 ) { + errorText_ = "RtApi::openStream: 'format' parameter value is undefined."; + error( RtAudioError::INVALID_USE ); + return; + } + + unsigned int nDevices = getDeviceCount(); + unsigned int oChannels = 0; + if ( oParams ) { + oChannels = oParams->nChannels; + if ( oParams->deviceId >= nDevices ) { + errorText_ = "RtApi::openStream: output device parameter value is invalid."; + error( RtAudioError::INVALID_USE ); + return; + } + } + + unsigned int iChannels = 0; + if ( iParams ) { + iChannels = iParams->nChannels; + if ( iParams->deviceId >= nDevices ) { + errorText_ = "RtApi::openStream: input device parameter value is invalid."; + error( RtAudioError::INVALID_USE ); + return; + } + } + + bool result; + + if ( oChannels > 0 ) { + + result = probeDeviceOpen( oParams->deviceId, OUTPUT, oChannels, oParams->firstChannel, + sampleRate, format, bufferFrames, options ); + if ( result == false ) { + error( RtAudioError::SYSTEM_ERROR ); + return; + } + } + + if ( iChannels > 0 ) { + + result = probeDeviceOpen( iParams->deviceId, INPUT, iChannels, iParams->firstChannel, + sampleRate, format, bufferFrames, options ); + if ( result == false ) { + if ( oChannels > 0 ) closeStream(); + error( RtAudioError::SYSTEM_ERROR ); + return; + } + } + + stream_.callbackInfo.callback = (void *) callback; + stream_.callbackInfo.userData = userData; + stream_.callbackInfo.errorCallback = (void *) errorCallback; + + if ( options ) options->numberOfBuffers = stream_.nBuffers; + stream_.state = STREAM_STOPPED; +} + +unsigned int RtApi :: getDefaultInputDevice( void ) +{ + // Should be implemented in subclasses if possible. + return 0; +} + +unsigned int RtApi :: getDefaultOutputDevice( void ) +{ + // Should be implemented in subclasses if possible. + return 0; +} + +void RtApi :: closeStream( void ) +{ + // MUST be implemented in subclasses! + return; +} + +bool RtApi :: probeDeviceOpen( unsigned int /*device*/, StreamMode /*mode*/, unsigned int /*channels*/, + unsigned int /*firstChannel*/, unsigned int /*sampleRate*/, + RtAudioFormat /*format*/, unsigned int * /*bufferSize*/, + RtAudio::StreamOptions * /*options*/ ) +{ + // MUST be implemented in subclasses! + return FAILURE; +} + +void RtApi :: tickStreamTime( void ) +{ + // Subclasses that do not provide their own implementation of + // getStreamTime should call this function once per buffer I/O to + // provide basic stream time support. + + stream_.streamTime += ( stream_.bufferSize * 1.0 / stream_.sampleRate ); + +#if defined( HAVE_GETTIMEOFDAY ) + gettimeofday( &stream_.lastTickTimestamp, NULL ); +#endif +} + +long RtApi :: getStreamLatency( void ) +{ + verifyStream(); + + long totalLatency = 0; + if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) + totalLatency = stream_.latency[0]; + if ( stream_.mode == INPUT || stream_.mode == DUPLEX ) + totalLatency += stream_.latency[1]; + + return totalLatency; +} + +double RtApi :: getStreamTime( void ) +{ + verifyStream(); + +#if defined( HAVE_GETTIMEOFDAY ) + // Return a very accurate estimate of the stream time by + // adding in the elapsed time since the last tick. + struct timeval then; + struct timeval now; + + if ( stream_.state != STREAM_RUNNING || stream_.streamTime == 0.0 ) + return stream_.streamTime; + + gettimeofday( &now, NULL ); + then = stream_.lastTickTimestamp; + return stream_.streamTime + + ((now.tv_sec + 0.000001 * now.tv_usec) - + (then.tv_sec + 0.000001 * then.tv_usec)); +#else + return stream_.streamTime; +#endif +} + +void RtApi :: setStreamTime( double time ) +{ + verifyStream(); + + if ( time >= 0.0 ) + stream_.streamTime = time; +} + +unsigned int RtApi :: getStreamSampleRate( void ) +{ + verifyStream(); + + return stream_.sampleRate; +} + + +// *************************************************** // +// +// OS/API-specific methods. +// +// *************************************************** // + +#if defined(__MACOSX_CORE__) + +// The OS X CoreAudio API is designed to use a separate callback +// procedure for each of its audio devices. A single RtAudio duplex +// stream using two different devices is supported here, though it +// cannot be guaranteed to always behave correctly because we cannot +// synchronize these two callbacks. +// +// A property listener is installed for over/underrun information. +// However, no functionality is currently provided to allow property +// listeners to trigger user handlers because it is unclear what could +// be done if a critical stream parameter (buffer size, sample rate, +// device disconnect) notification arrived. The listeners entail +// quite a bit of extra code and most likely, a user program wouldn't +// be prepared for the result anyway. However, we do provide a flag +// to the client callback function to inform of an over/underrun. + +// A structure to hold various information related to the CoreAudio API +// implementation. +struct CoreHandle { + AudioDeviceID id[2]; // device ids +#if defined( MAC_OS_X_VERSION_10_5 ) && ( MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 ) + AudioDeviceIOProcID procId[2]; +#endif + UInt32 iStream[2]; // device stream index (or first if using multiple) + UInt32 nStreams[2]; // number of streams to use + bool xrun[2]; + char *deviceBuffer; + pthread_cond_t condition; + int drainCounter; // Tracks callback counts when draining + bool internalDrain; // Indicates if stop is initiated from callback or not. + + CoreHandle() + :deviceBuffer(0), drainCounter(0), internalDrain(false) { nStreams[0] = 1; nStreams[1] = 1; id[0] = 0; id[1] = 0; xrun[0] = false; xrun[1] = false; } +}; + +RtApiCore:: RtApiCore() +{ +#if defined( AVAILABLE_MAC_OS_X_VERSION_10_6_AND_LATER ) + // This is a largely undocumented but absolutely necessary + // requirement starting with OS-X 10.6. If not called, queries and + // updates to various audio device properties are not handled + // correctly. + CFRunLoopRef theRunLoop = NULL; + AudioObjectPropertyAddress property = { kAudioHardwarePropertyRunLoop, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster }; + OSStatus result = AudioObjectSetPropertyData( kAudioObjectSystemObject, &property, 0, NULL, sizeof(CFRunLoopRef), &theRunLoop); + if ( result != noErr ) { + errorText_ = "RtApiCore::RtApiCore: error setting run loop property!"; + error( RtAudioError::WARNING ); + } +#endif +} + +RtApiCore :: ~RtApiCore() +{ + // The subclass destructor gets called before the base class + // destructor, so close an existing stream before deallocating + // apiDeviceId memory. + if ( stream_.state != STREAM_CLOSED ) closeStream(); +} + +unsigned int RtApiCore :: getDeviceCount( void ) +{ + // Find out how many audio devices there are, if any. + UInt32 dataSize; + AudioObjectPropertyAddress propertyAddress = { kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; + OSStatus result = AudioObjectGetPropertyDataSize( kAudioObjectSystemObject, &propertyAddress, 0, NULL, &dataSize ); + if ( result != noErr ) { + errorText_ = "RtApiCore::getDeviceCount: OS-X error getting device info!"; + error( RtAudioError::WARNING ); + return 0; + } + + return dataSize / sizeof( AudioDeviceID ); +} + +unsigned int RtApiCore :: getDefaultInputDevice( void ) +{ + unsigned int nDevices = getDeviceCount(); + if ( nDevices <= 1 ) return 0; + + AudioDeviceID id; + UInt32 dataSize = sizeof( AudioDeviceID ); + AudioObjectPropertyAddress property = { kAudioHardwarePropertyDefaultInputDevice, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; + OSStatus result = AudioObjectGetPropertyData( kAudioObjectSystemObject, &property, 0, NULL, &dataSize, &id ); + if ( result != noErr ) { + errorText_ = "RtApiCore::getDefaultInputDevice: OS-X system error getting device."; + error( RtAudioError::WARNING ); + return 0; + } + + dataSize *= nDevices; + AudioDeviceID deviceList[ nDevices ]; + property.mSelector = kAudioHardwarePropertyDevices; + result = AudioObjectGetPropertyData( kAudioObjectSystemObject, &property, 0, NULL, &dataSize, (void *) &deviceList ); + if ( result != noErr ) { + errorText_ = "RtApiCore::getDefaultInputDevice: OS-X system error getting device IDs."; + error( RtAudioError::WARNING ); + return 0; + } + + for ( unsigned int i=0; i= nDevices ) { + errorText_ = "RtApiCore::getDeviceInfo: device ID is invalid!"; + error( RtAudioError::INVALID_USE ); + return info; + } + + AudioDeviceID deviceList[ nDevices ]; + UInt32 dataSize = sizeof( AudioDeviceID ) * nDevices; + AudioObjectPropertyAddress property = { kAudioHardwarePropertyDevices, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster }; + OSStatus result = AudioObjectGetPropertyData( kAudioObjectSystemObject, &property, + 0, NULL, &dataSize, (void *) &deviceList ); + if ( result != noErr ) { + errorText_ = "RtApiCore::getDeviceInfo: OS-X system error getting device IDs."; + error( RtAudioError::WARNING ); + return info; + } + + AudioDeviceID id = deviceList[ device ]; + + // Get the device name. + info.name.erase(); + CFStringRef cfname; + dataSize = sizeof( CFStringRef ); + property.mSelector = kAudioObjectPropertyManufacturer; + result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, &cfname ); + if ( result != noErr ) { + errorStream_ << "RtApiCore::probeDeviceInfo: system error (" << getErrorCode( result ) << ") getting device manufacturer."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + return info; + } + + //const char *mname = CFStringGetCStringPtr( cfname, CFStringGetSystemEncoding() ); + int length = CFStringGetLength(cfname); + char *mname = (char *)malloc(length * 3 + 1); +#if defined( UNICODE ) || defined( _UNICODE ) + CFStringGetCString(cfname, mname, length * 3 + 1, kCFStringEncodingUTF8); +#else + CFStringGetCString(cfname, mname, length * 3 + 1, CFStringGetSystemEncoding()); +#endif + info.name.append( (const char *)mname, strlen(mname) ); + info.name.append( ": " ); + CFRelease( cfname ); + free(mname); + + property.mSelector = kAudioObjectPropertyName; + result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, &cfname ); + if ( result != noErr ) { + errorStream_ << "RtApiCore::probeDeviceInfo: system error (" << getErrorCode( result ) << ") getting device name."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + return info; + } + + //const char *name = CFStringGetCStringPtr( cfname, CFStringGetSystemEncoding() ); + length = CFStringGetLength(cfname); + char *name = (char *)malloc(length * 3 + 1); +#if defined( UNICODE ) || defined( _UNICODE ) + CFStringGetCString(cfname, name, length * 3 + 1, kCFStringEncodingUTF8); +#else + CFStringGetCString(cfname, name, length * 3 + 1, CFStringGetSystemEncoding()); +#endif + info.name.append( (const char *)name, strlen(name) ); + CFRelease( cfname ); + free(name); + + // Get the output stream "configuration". + AudioBufferList *bufferList = nil; + property.mSelector = kAudioDevicePropertyStreamConfiguration; + property.mScope = kAudioDevicePropertyScopeOutput; + // property.mElement = kAudioObjectPropertyElementWildcard; + dataSize = 0; + result = AudioObjectGetPropertyDataSize( id, &property, 0, NULL, &dataSize ); + if ( result != noErr || dataSize == 0 ) { + errorStream_ << "RtApiCore::getDeviceInfo: system error (" << getErrorCode( result ) << ") getting output stream configuration info for device (" << device << ")."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + return info; + } + + // Allocate the AudioBufferList. + bufferList = (AudioBufferList *) malloc( dataSize ); + if ( bufferList == NULL ) { + errorText_ = "RtApiCore::getDeviceInfo: memory error allocating output AudioBufferList."; + error( RtAudioError::WARNING ); + return info; + } + + result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, bufferList ); + if ( result != noErr || dataSize == 0 ) { + free( bufferList ); + errorStream_ << "RtApiCore::getDeviceInfo: system error (" << getErrorCode( result ) << ") getting output stream configuration for device (" << device << ")."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + return info; + } + + // Get output channel information. + unsigned int i, nStreams = bufferList->mNumberBuffers; + for ( i=0; imBuffers[i].mNumberChannels; + free( bufferList ); + + // Get the input stream "configuration". + property.mScope = kAudioDevicePropertyScopeInput; + result = AudioObjectGetPropertyDataSize( id, &property, 0, NULL, &dataSize ); + if ( result != noErr || dataSize == 0 ) { + errorStream_ << "RtApiCore::getDeviceInfo: system error (" << getErrorCode( result ) << ") getting input stream configuration info for device (" << device << ")."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + return info; + } + + // Allocate the AudioBufferList. + bufferList = (AudioBufferList *) malloc( dataSize ); + if ( bufferList == NULL ) { + errorText_ = "RtApiCore::getDeviceInfo: memory error allocating input AudioBufferList."; + error( RtAudioError::WARNING ); + return info; + } + + result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, bufferList ); + if (result != noErr || dataSize == 0) { + free( bufferList ); + errorStream_ << "RtApiCore::getDeviceInfo: system error (" << getErrorCode( result ) << ") getting input stream configuration for device (" << device << ")."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + return info; + } + + // Get input channel information. + nStreams = bufferList->mNumberBuffers; + for ( i=0; imBuffers[i].mNumberChannels; + free( bufferList ); + + // If device opens for both playback and capture, we determine the channels. + if ( info.outputChannels > 0 && info.inputChannels > 0 ) + info.duplexChannels = (info.outputChannels > info.inputChannels) ? info.inputChannels : info.outputChannels; + + // Probe the device sample rates. + bool isInput = false; + if ( info.outputChannels == 0 ) isInput = true; + + // Determine the supported sample rates. + property.mSelector = kAudioDevicePropertyAvailableNominalSampleRates; + if ( isInput == false ) property.mScope = kAudioDevicePropertyScopeOutput; + result = AudioObjectGetPropertyDataSize( id, &property, 0, NULL, &dataSize ); + if ( result != kAudioHardwareNoError || dataSize == 0 ) { + errorStream_ << "RtApiCore::getDeviceInfo: system error (" << getErrorCode( result ) << ") getting sample rate info."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + return info; + } + + UInt32 nRanges = dataSize / sizeof( AudioValueRange ); + AudioValueRange rangeList[ nRanges ]; + result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, &rangeList ); + if ( result != kAudioHardwareNoError ) { + errorStream_ << "RtApiCore::getDeviceInfo: system error (" << getErrorCode( result ) << ") getting sample rates."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + return info; + } + + // The sample rate reporting mechanism is a bit of a mystery. It + // seems that it can either return individual rates or a range of + // rates. I assume that if the min / max range values are the same, + // then that represents a single supported rate and if the min / max + // range values are different, the device supports an arbitrary + // range of values (though there might be multiple ranges, so we'll + // use the most conservative range). + Float64 minimumRate = 1.0, maximumRate = 10000000000.0; + bool haveValueRange = false; + info.sampleRates.clear(); + for ( UInt32 i=0; i minimumRate ) minimumRate = rangeList[i].mMinimum; + if ( rangeList[i].mMaximum < maximumRate ) maximumRate = rangeList[i].mMaximum; + } + } + + if ( haveValueRange ) { + for ( unsigned int k=0; k= (unsigned int) minimumRate && SAMPLE_RATES[k] <= (unsigned int) maximumRate ) + info.sampleRates.push_back( SAMPLE_RATES[k] ); + } + } + + // Sort and remove any redundant values + std::sort( info.sampleRates.begin(), info.sampleRates.end() ); + info.sampleRates.erase( unique( info.sampleRates.begin(), info.sampleRates.end() ), info.sampleRates.end() ); + + if ( info.sampleRates.size() == 0 ) { + errorStream_ << "RtApiCore::probeDeviceInfo: No supported sample rates found for device (" << device << ")."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + return info; + } + + // CoreAudio always uses 32-bit floating point data for PCM streams. + // Thus, any other "physical" formats supported by the device are of + // no interest to the client. + info.nativeFormats = RTAUDIO_FLOAT32; + + if ( info.outputChannels > 0 ) + if ( getDefaultOutputDevice() == device ) info.isDefaultOutput = true; + if ( info.inputChannels > 0 ) + if ( getDefaultInputDevice() == device ) info.isDefaultInput = true; + + info.probed = true; + return info; +} + +static OSStatus callbackHandler( AudioDeviceID inDevice, + const AudioTimeStamp* /*inNow*/, + const AudioBufferList* inInputData, + const AudioTimeStamp* /*inInputTime*/, + AudioBufferList* outOutputData, + const AudioTimeStamp* /*inOutputTime*/, + void* infoPointer ) +{ + CallbackInfo *info = (CallbackInfo *) infoPointer; + + RtApiCore *object = (RtApiCore *) info->object; + if ( object->callbackEvent( inDevice, inInputData, outOutputData ) == false ) + return kAudioHardwareUnspecifiedError; + else + return kAudioHardwareNoError; +} + +static OSStatus xrunListener( AudioObjectID /*inDevice*/, + UInt32 nAddresses, + const AudioObjectPropertyAddress properties[], + void* handlePointer ) +{ + CoreHandle *handle = (CoreHandle *) handlePointer; + for ( UInt32 i=0; ixrun[1] = true; + else + handle->xrun[0] = true; + } + } + + return kAudioHardwareNoError; +} + +static OSStatus rateListener( AudioObjectID inDevice, + UInt32 /*nAddresses*/, + const AudioObjectPropertyAddress /*properties*/[], + void* ratePointer ) +{ + Float64 *rate = (Float64 *) ratePointer; + UInt32 dataSize = sizeof( Float64 ); + AudioObjectPropertyAddress property = { kAudioDevicePropertyNominalSampleRate, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster }; + AudioObjectGetPropertyData( inDevice, &property, 0, NULL, &dataSize, rate ); + return kAudioHardwareNoError; +} + +bool RtApiCore :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, + unsigned int firstChannel, unsigned int sampleRate, + RtAudioFormat format, unsigned int *bufferSize, + RtAudio::StreamOptions *options ) +{ + // Get device ID + unsigned int nDevices = getDeviceCount(); + if ( nDevices == 0 ) { + // This should not happen because a check is made before this function is called. + errorText_ = "RtApiCore::probeDeviceOpen: no devices found!"; + return FAILURE; + } + + if ( device >= nDevices ) { + // This should not happen because a check is made before this function is called. + errorText_ = "RtApiCore::probeDeviceOpen: device ID is invalid!"; + return FAILURE; + } + + AudioDeviceID deviceList[ nDevices ]; + UInt32 dataSize = sizeof( AudioDeviceID ) * nDevices; + AudioObjectPropertyAddress property = { kAudioHardwarePropertyDevices, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster }; + OSStatus result = AudioObjectGetPropertyData( kAudioObjectSystemObject, &property, + 0, NULL, &dataSize, (void *) &deviceList ); + if ( result != noErr ) { + errorText_ = "RtApiCore::probeDeviceOpen: OS-X system error getting device IDs."; + return FAILURE; + } + + AudioDeviceID id = deviceList[ device ]; + + // Setup for stream mode. + bool isInput = false; + if ( mode == INPUT ) { + isInput = true; + property.mScope = kAudioDevicePropertyScopeInput; + } + else + property.mScope = kAudioDevicePropertyScopeOutput; + + // Get the stream "configuration". + AudioBufferList *bufferList = nil; + dataSize = 0; + property.mSelector = kAudioDevicePropertyStreamConfiguration; + result = AudioObjectGetPropertyDataSize( id, &property, 0, NULL, &dataSize ); + if ( result != noErr || dataSize == 0 ) { + errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") getting stream configuration info for device (" << device << ")."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Allocate the AudioBufferList. + bufferList = (AudioBufferList *) malloc( dataSize ); + if ( bufferList == NULL ) { + errorText_ = "RtApiCore::probeDeviceOpen: memory error allocating AudioBufferList."; + return FAILURE; + } + + result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, bufferList ); + if (result != noErr || dataSize == 0) { + free( bufferList ); + errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") getting stream configuration for device (" << device << ")."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Search for one or more streams that contain the desired number of + // channels. CoreAudio devices can have an arbitrary number of + // streams and each stream can have an arbitrary number of channels. + // For each stream, a single buffer of interleaved samples is + // provided. RtAudio prefers the use of one stream of interleaved + // data or multiple consecutive single-channel streams. However, we + // now support multiple consecutive multi-channel streams of + // interleaved data as well. + UInt32 iStream, offsetCounter = firstChannel; + UInt32 nStreams = bufferList->mNumberBuffers; + bool monoMode = false; + bool foundStream = false; + + // First check that the device supports the requested number of + // channels. + UInt32 deviceChannels = 0; + for ( iStream=0; iStreammBuffers[iStream].mNumberChannels; + + if ( deviceChannels < ( channels + firstChannel ) ) { + free( bufferList ); + errorStream_ << "RtApiCore::probeDeviceOpen: the device (" << device << ") does not support the requested channel count."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Look for a single stream meeting our needs. + UInt32 firstStream, streamCount = 1, streamChannels = 0, channelOffset = 0; + for ( iStream=0; iStreammBuffers[iStream].mNumberChannels; + if ( streamChannels >= channels + offsetCounter ) { + firstStream = iStream; + channelOffset = offsetCounter; + foundStream = true; + break; + } + if ( streamChannels > offsetCounter ) break; + offsetCounter -= streamChannels; + } + + // If we didn't find a single stream above, then we should be able + // to meet the channel specification with multiple streams. + if ( foundStream == false ) { + monoMode = true; + offsetCounter = firstChannel; + for ( iStream=0; iStreammBuffers[iStream].mNumberChannels; + if ( streamChannels > offsetCounter ) break; + offsetCounter -= streamChannels; + } + + firstStream = iStream; + channelOffset = offsetCounter; + Int32 channelCounter = channels + offsetCounter - streamChannels; + + if ( streamChannels > 1 ) monoMode = false; + while ( channelCounter > 0 ) { + streamChannels = bufferList->mBuffers[++iStream].mNumberChannels; + if ( streamChannels > 1 ) monoMode = false; + channelCounter -= streamChannels; + streamCount++; + } + } + + free( bufferList ); + + // Determine the buffer size. + AudioValueRange bufferRange; + dataSize = sizeof( AudioValueRange ); + property.mSelector = kAudioDevicePropertyBufferFrameSizeRange; + result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, &bufferRange ); + + if ( result != noErr ) { + errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") getting buffer size range for device (" << device << ")."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + if ( bufferRange.mMinimum > *bufferSize ) *bufferSize = (unsigned long) bufferRange.mMinimum; + else if ( bufferRange.mMaximum < *bufferSize ) *bufferSize = (unsigned long) bufferRange.mMaximum; + if ( options && options->flags & RTAUDIO_MINIMIZE_LATENCY ) *bufferSize = (unsigned long) bufferRange.mMinimum; + + // Set the buffer size. For multiple streams, I'm assuming we only + // need to make this setting for the master channel. + UInt32 theSize = (UInt32) *bufferSize; + dataSize = sizeof( UInt32 ); + property.mSelector = kAudioDevicePropertyBufferFrameSize; + result = AudioObjectSetPropertyData( id, &property, 0, NULL, dataSize, &theSize ); + + if ( result != noErr ) { + errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") setting the buffer size for device (" << device << ")."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // If attempting to setup a duplex stream, the bufferSize parameter + // MUST be the same in both directions! + *bufferSize = theSize; + if ( stream_.mode == OUTPUT && mode == INPUT && *bufferSize != stream_.bufferSize ) { + errorStream_ << "RtApiCore::probeDeviceOpen: system error setting buffer size for duplex stream on device (" << device << ")."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + stream_.bufferSize = *bufferSize; + stream_.nBuffers = 1; + + // Try to set "hog" mode ... it's not clear to me this is working. + if ( options && options->flags & RTAUDIO_HOG_DEVICE ) { + pid_t hog_pid; + dataSize = sizeof( hog_pid ); + property.mSelector = kAudioDevicePropertyHogMode; + result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, &hog_pid ); + if ( result != noErr ) { + errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") getting 'hog' state!"; + errorText_ = errorStream_.str(); + return FAILURE; + } + + if ( hog_pid != getpid() ) { + hog_pid = getpid(); + result = AudioObjectSetPropertyData( id, &property, 0, NULL, dataSize, &hog_pid ); + if ( result != noErr ) { + errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") setting 'hog' state!"; + errorText_ = errorStream_.str(); + return FAILURE; + } + } + } + + // Check and if necessary, change the sample rate for the device. + Float64 nominalRate; + dataSize = sizeof( Float64 ); + property.mSelector = kAudioDevicePropertyNominalSampleRate; + result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, &nominalRate ); + if ( result != noErr ) { + errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") getting current sample rate."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Only change the sample rate if off by more than 1 Hz. + if ( fabs( nominalRate - (double)sampleRate ) > 1.0 ) { + + // Set a property listener for the sample rate change + Float64 reportedRate = 0.0; + AudioObjectPropertyAddress tmp = { kAudioDevicePropertyNominalSampleRate, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; + result = AudioObjectAddPropertyListener( id, &tmp, rateListener, (void *) &reportedRate ); + if ( result != noErr ) { + errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") setting sample rate property listener for device (" << device << ")."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + nominalRate = (Float64) sampleRate; + result = AudioObjectSetPropertyData( id, &property, 0, NULL, dataSize, &nominalRate ); + if ( result != noErr ) { + AudioObjectRemovePropertyListener( id, &tmp, rateListener, (void *) &reportedRate ); + errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") setting sample rate for device (" << device << ")."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Now wait until the reported nominal rate is what we just set. + UInt32 microCounter = 0; + while ( reportedRate != nominalRate ) { + microCounter += 5000; + if ( microCounter > 5000000 ) break; + usleep( 5000 ); + } + + // Remove the property listener. + AudioObjectRemovePropertyListener( id, &tmp, rateListener, (void *) &reportedRate ); + + if ( microCounter > 5000000 ) { + errorStream_ << "RtApiCore::probeDeviceOpen: timeout waiting for sample rate update for device (" << device << ")."; + errorText_ = errorStream_.str(); + return FAILURE; + } + } + + // Now set the stream format for all streams. Also, check the + // physical format of the device and change that if necessary. + AudioStreamBasicDescription description; + dataSize = sizeof( AudioStreamBasicDescription ); + property.mSelector = kAudioStreamPropertyVirtualFormat; + result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, &description ); + if ( result != noErr ) { + errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") getting stream format for device (" << device << ")."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Set the sample rate and data format id. However, only make the + // change if the sample rate is not within 1.0 of the desired + // rate and the format is not linear pcm. + bool updateFormat = false; + if ( fabs( description.mSampleRate - (Float64)sampleRate ) > 1.0 ) { + description.mSampleRate = (Float64) sampleRate; + updateFormat = true; + } + + if ( description.mFormatID != kAudioFormatLinearPCM ) { + description.mFormatID = kAudioFormatLinearPCM; + updateFormat = true; + } + + if ( updateFormat ) { + result = AudioObjectSetPropertyData( id, &property, 0, NULL, dataSize, &description ); + if ( result != noErr ) { + errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") setting sample rate or data format for device (" << device << ")."; + errorText_ = errorStream_.str(); + return FAILURE; + } + } + + // Now check the physical format. + property.mSelector = kAudioStreamPropertyPhysicalFormat; + result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, &description ); + if ( result != noErr ) { + errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") getting stream physical format for device (" << device << ")."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + //std::cout << "Current physical stream format:" << std::endl; + //std::cout << " mBitsPerChan = " << description.mBitsPerChannel << std::endl; + //std::cout << " aligned high = " << (description.mFormatFlags & kAudioFormatFlagIsAlignedHigh) << ", isPacked = " << (description.mFormatFlags & kAudioFormatFlagIsPacked) << std::endl; + //std::cout << " bytesPerFrame = " << description.mBytesPerFrame << std::endl; + //std::cout << " sample rate = " << description.mSampleRate << std::endl; + + if ( description.mFormatID != kAudioFormatLinearPCM || description.mBitsPerChannel < 16 ) { + description.mFormatID = kAudioFormatLinearPCM; + //description.mSampleRate = (Float64) sampleRate; + AudioStreamBasicDescription testDescription = description; + UInt32 formatFlags; + + // We'll try higher bit rates first and then work our way down. + std::vector< std::pair > physicalFormats; + formatFlags = (description.mFormatFlags | kLinearPCMFormatFlagIsFloat) & ~kLinearPCMFormatFlagIsSignedInteger; + physicalFormats.push_back( std::pair( 32, formatFlags ) ); + formatFlags = (description.mFormatFlags | kLinearPCMFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked) & ~kLinearPCMFormatFlagIsFloat; + physicalFormats.push_back( std::pair( 32, formatFlags ) ); + physicalFormats.push_back( std::pair( 24, formatFlags ) ); // 24-bit packed + formatFlags &= ~( kAudioFormatFlagIsPacked | kAudioFormatFlagIsAlignedHigh ); + physicalFormats.push_back( std::pair( 24.2, formatFlags ) ); // 24-bit in 4 bytes, aligned low + formatFlags |= kAudioFormatFlagIsAlignedHigh; + physicalFormats.push_back( std::pair( 24.4, formatFlags ) ); // 24-bit in 4 bytes, aligned high + formatFlags = (description.mFormatFlags | kLinearPCMFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked) & ~kLinearPCMFormatFlagIsFloat; + physicalFormats.push_back( std::pair( 16, formatFlags ) ); + physicalFormats.push_back( std::pair( 8, formatFlags ) ); + + bool setPhysicalFormat = false; + for( unsigned int i=0; iflags & RTAUDIO_NONINTERLEAVED ) stream_.userInterleaved = false; + else stream_.userInterleaved = true; + stream_.deviceInterleaved[mode] = true; + if ( monoMode == true ) stream_.deviceInterleaved[mode] = false; + + // Set flags for buffer conversion. + stream_.doConvertBuffer[mode] = false; + if ( stream_.userFormat != stream_.deviceFormat[mode] ) + stream_.doConvertBuffer[mode] = true; + if ( stream_.nUserChannels[mode] < stream_.nDeviceChannels[mode] ) + stream_.doConvertBuffer[mode] = true; + if ( streamCount == 1 ) { + if ( stream_.nUserChannels[mode] > 1 && + stream_.userInterleaved != stream_.deviceInterleaved[mode] ) + stream_.doConvertBuffer[mode] = true; + } + else if ( monoMode && stream_.userInterleaved ) + stream_.doConvertBuffer[mode] = true; + + // Allocate our CoreHandle structure for the stream. + CoreHandle *handle = 0; + if ( stream_.apiHandle == 0 ) { + try { + handle = new CoreHandle; + } + catch ( std::bad_alloc& ) { + errorText_ = "RtApiCore::probeDeviceOpen: error allocating CoreHandle memory."; + goto error; + } + + if ( pthread_cond_init( &handle->condition, NULL ) ) { + errorText_ = "RtApiCore::probeDeviceOpen: error initializing pthread condition variable."; + goto error; + } + stream_.apiHandle = (void *) handle; + } + else + handle = (CoreHandle *) stream_.apiHandle; + handle->iStream[mode] = firstStream; + handle->nStreams[mode] = streamCount; + handle->id[mode] = id; + + // Allocate necessary internal buffers. + unsigned long bufferBytes; + bufferBytes = stream_.nUserChannels[mode] * *bufferSize * formatBytes( stream_.userFormat ); + // stream_.userBuffer[mode] = (char *) calloc( bufferBytes, 1 ); + stream_.userBuffer[mode] = (char *) malloc( bufferBytes * sizeof(char) ); + memset( stream_.userBuffer[mode], 0, bufferBytes * sizeof(char) ); + if ( stream_.userBuffer[mode] == NULL ) { + errorText_ = "RtApiCore::probeDeviceOpen: error allocating user buffer memory."; + goto error; + } + + // If possible, we will make use of the CoreAudio stream buffers as + // "device buffers". However, we can't do this if using multiple + // streams. + if ( stream_.doConvertBuffer[mode] && handle->nStreams[mode] > 1 ) { + + bool makeBuffer = true; + bufferBytes = stream_.nDeviceChannels[mode] * formatBytes( stream_.deviceFormat[mode] ); + if ( mode == INPUT ) { + if ( stream_.mode == OUTPUT && stream_.deviceBuffer ) { + unsigned long bytesOut = stream_.nDeviceChannels[0] * formatBytes( stream_.deviceFormat[0] ); + if ( bufferBytes <= bytesOut ) makeBuffer = false; + } + } + + if ( makeBuffer ) { + bufferBytes *= *bufferSize; + if ( stream_.deviceBuffer ) free( stream_.deviceBuffer ); + stream_.deviceBuffer = (char *) calloc( bufferBytes, 1 ); + if ( stream_.deviceBuffer == NULL ) { + errorText_ = "RtApiCore::probeDeviceOpen: error allocating device buffer memory."; + goto error; + } + } + } + + stream_.sampleRate = sampleRate; + stream_.device[mode] = device; + stream_.state = STREAM_STOPPED; + stream_.callbackInfo.object = (void *) this; + + // Setup the buffer conversion information structure. + if ( stream_.doConvertBuffer[mode] ) { + if ( streamCount > 1 ) setConvertInfo( mode, 0 ); + else setConvertInfo( mode, channelOffset ); + } + + if ( mode == INPUT && stream_.mode == OUTPUT && stream_.device[0] == device ) + // Only one callback procedure per device. + stream_.mode = DUPLEX; + else { +#if defined( MAC_OS_X_VERSION_10_5 ) && ( MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 ) + result = AudioDeviceCreateIOProcID( id, callbackHandler, (void *) &stream_.callbackInfo, &handle->procId[mode] ); +#else + // deprecated in favor of AudioDeviceCreateIOProcID() + result = AudioDeviceAddIOProc( id, callbackHandler, (void *) &stream_.callbackInfo ); +#endif + if ( result != noErr ) { + errorStream_ << "RtApiCore::probeDeviceOpen: system error setting callback for device (" << device << ")."; + errorText_ = errorStream_.str(); + goto error; + } + if ( stream_.mode == OUTPUT && mode == INPUT ) + stream_.mode = DUPLEX; + else + stream_.mode = mode; + } + + // Setup the device property listener for over/underload. + property.mSelector = kAudioDeviceProcessorOverload; + property.mScope = kAudioObjectPropertyScopeGlobal; + result = AudioObjectAddPropertyListener( id, &property, xrunListener, (void *) handle ); + + return SUCCESS; + + error: + if ( handle ) { + pthread_cond_destroy( &handle->condition ); + delete handle; + stream_.apiHandle = 0; + } + + for ( int i=0; i<2; i++ ) { + if ( stream_.userBuffer[i] ) { + free( stream_.userBuffer[i] ); + stream_.userBuffer[i] = 0; + } + } + + if ( stream_.deviceBuffer ) { + free( stream_.deviceBuffer ); + stream_.deviceBuffer = 0; + } + + stream_.state = STREAM_CLOSED; + return FAILURE; +} + +void RtApiCore :: closeStream( void ) +{ + if ( stream_.state == STREAM_CLOSED ) { + errorText_ = "RtApiCore::closeStream(): no open stream to close!"; + error( RtAudioError::WARNING ); + return; + } + + CoreHandle *handle = (CoreHandle *) stream_.apiHandle; + if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { + if ( stream_.state == STREAM_RUNNING ) + AudioDeviceStop( handle->id[0], callbackHandler ); +#if defined( MAC_OS_X_VERSION_10_5 ) && ( MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 ) + AudioDeviceDestroyIOProcID( handle->id[0], handle->procId[0] ); +#else + // deprecated in favor of AudioDeviceDestroyIOProcID() + AudioDeviceRemoveIOProc( handle->id[0], callbackHandler ); +#endif + } + + if ( stream_.mode == INPUT || ( stream_.mode == DUPLEX && stream_.device[0] != stream_.device[1] ) ) { + if ( stream_.state == STREAM_RUNNING ) + AudioDeviceStop( handle->id[1], callbackHandler ); +#if defined( MAC_OS_X_VERSION_10_5 ) && ( MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 ) + AudioDeviceDestroyIOProcID( handle->id[1], handle->procId[1] ); +#else + // deprecated in favor of AudioDeviceDestroyIOProcID() + AudioDeviceRemoveIOProc( handle->id[1], callbackHandler ); +#endif + } + + for ( int i=0; i<2; i++ ) { + if ( stream_.userBuffer[i] ) { + free( stream_.userBuffer[i] ); + stream_.userBuffer[i] = 0; + } + } + + if ( stream_.deviceBuffer ) { + free( stream_.deviceBuffer ); + stream_.deviceBuffer = 0; + } + + // Destroy pthread condition variable. + pthread_cond_destroy( &handle->condition ); + delete handle; + stream_.apiHandle = 0; + + stream_.mode = UNINITIALIZED; + stream_.state = STREAM_CLOSED; +} + +void RtApiCore :: startStream( void ) +{ + verifyStream(); + if ( stream_.state == STREAM_RUNNING ) { + errorText_ = "RtApiCore::startStream(): the stream is already running!"; + error( RtAudioError::WARNING ); + return; + } + + OSStatus result = noErr; + CoreHandle *handle = (CoreHandle *) stream_.apiHandle; + if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { + + result = AudioDeviceStart( handle->id[0], callbackHandler ); + if ( result != noErr ) { + errorStream_ << "RtApiCore::startStream: system error (" << getErrorCode( result ) << ") starting callback procedure on device (" << stream_.device[0] << ")."; + errorText_ = errorStream_.str(); + goto unlock; + } + } + + if ( stream_.mode == INPUT || + ( stream_.mode == DUPLEX && stream_.device[0] != stream_.device[1] ) ) { + + result = AudioDeviceStart( handle->id[1], callbackHandler ); + if ( result != noErr ) { + errorStream_ << "RtApiCore::startStream: system error starting input callback procedure on device (" << stream_.device[1] << ")."; + errorText_ = errorStream_.str(); + goto unlock; + } + } + + handle->drainCounter = 0; + handle->internalDrain = false; + stream_.state = STREAM_RUNNING; + + unlock: + if ( result == noErr ) return; + error( RtAudioError::SYSTEM_ERROR ); +} + +void RtApiCore :: stopStream( void ) +{ + verifyStream(); + if ( stream_.state == STREAM_STOPPED ) { + errorText_ = "RtApiCore::stopStream(): the stream is already stopped!"; + error( RtAudioError::WARNING ); + return; + } + + OSStatus result = noErr; + CoreHandle *handle = (CoreHandle *) stream_.apiHandle; + if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { + + if ( handle->drainCounter == 0 ) { + handle->drainCounter = 2; + pthread_cond_wait( &handle->condition, &stream_.mutex ); // block until signaled + } + + result = AudioDeviceStop( handle->id[0], callbackHandler ); + if ( result != noErr ) { + errorStream_ << "RtApiCore::stopStream: system error (" << getErrorCode( result ) << ") stopping callback procedure on device (" << stream_.device[0] << ")."; + errorText_ = errorStream_.str(); + goto unlock; + } + } + + if ( stream_.mode == INPUT || ( stream_.mode == DUPLEX && stream_.device[0] != stream_.device[1] ) ) { + + result = AudioDeviceStop( handle->id[1], callbackHandler ); + if ( result != noErr ) { + errorStream_ << "RtApiCore::stopStream: system error (" << getErrorCode( result ) << ") stopping input callback procedure on device (" << stream_.device[1] << ")."; + errorText_ = errorStream_.str(); + goto unlock; + } + } + + stream_.state = STREAM_STOPPED; + + unlock: + if ( result == noErr ) return; + error( RtAudioError::SYSTEM_ERROR ); +} + +void RtApiCore :: abortStream( void ) +{ + verifyStream(); + if ( stream_.state == STREAM_STOPPED ) { + errorText_ = "RtApiCore::abortStream(): the stream is already stopped!"; + error( RtAudioError::WARNING ); + return; + } + + CoreHandle *handle = (CoreHandle *) stream_.apiHandle; + handle->drainCounter = 2; + + stopStream(); +} + +// This function will be called by a spawned thread when the user +// callback function signals that the stream should be stopped or +// aborted. It is better to handle it this way because the +// callbackEvent() function probably should return before the AudioDeviceStop() +// function is called. +static void *coreStopStream( void *ptr ) +{ + CallbackInfo *info = (CallbackInfo *) ptr; + RtApiCore *object = (RtApiCore *) info->object; + + object->stopStream(); + pthread_exit( NULL ); +} + +bool RtApiCore :: callbackEvent( AudioDeviceID deviceId, + const AudioBufferList *inBufferList, + const AudioBufferList *outBufferList ) +{ + if ( stream_.state == STREAM_STOPPED || stream_.state == STREAM_STOPPING ) return SUCCESS; + if ( stream_.state == STREAM_CLOSED ) { + errorText_ = "RtApiCore::callbackEvent(): the stream is closed ... this shouldn't happen!"; + error( RtAudioError::WARNING ); + return FAILURE; + } + + CallbackInfo *info = (CallbackInfo *) &stream_.callbackInfo; + CoreHandle *handle = (CoreHandle *) stream_.apiHandle; + + // Check if we were draining the stream and signal is finished. + if ( handle->drainCounter > 3 ) { + ThreadHandle threadId; + + stream_.state = STREAM_STOPPING; + if ( handle->internalDrain == true ) + pthread_create( &threadId, NULL, coreStopStream, info ); + else // external call to stopStream() + pthread_cond_signal( &handle->condition ); + return SUCCESS; + } + + AudioDeviceID outputDevice = handle->id[0]; + + // Invoke user callback to get fresh output data UNLESS we are + // draining stream or duplex mode AND the input/output devices are + // different AND this function is called for the input device. + if ( handle->drainCounter == 0 && ( stream_.mode != DUPLEX || deviceId == outputDevice ) ) { + RtAudioCallback callback = (RtAudioCallback) info->callback; + double streamTime = getStreamTime(); + RtAudioStreamStatus status = 0; + if ( stream_.mode != INPUT && handle->xrun[0] == true ) { + status |= RTAUDIO_OUTPUT_UNDERFLOW; + handle->xrun[0] = false; + } + if ( stream_.mode != OUTPUT && handle->xrun[1] == true ) { + status |= RTAUDIO_INPUT_OVERFLOW; + handle->xrun[1] = false; + } + + int cbReturnValue = callback( stream_.userBuffer[0], stream_.userBuffer[1], + stream_.bufferSize, streamTime, status, info->userData ); + if ( cbReturnValue == 2 ) { + stream_.state = STREAM_STOPPING; + handle->drainCounter = 2; + abortStream(); + return SUCCESS; + } + else if ( cbReturnValue == 1 ) { + handle->drainCounter = 1; + handle->internalDrain = true; + } + } + + if ( stream_.mode == OUTPUT || ( stream_.mode == DUPLEX && deviceId == outputDevice ) ) { + + if ( handle->drainCounter > 1 ) { // write zeros to the output stream + + if ( handle->nStreams[0] == 1 ) { + memset( outBufferList->mBuffers[handle->iStream[0]].mData, + 0, + outBufferList->mBuffers[handle->iStream[0]].mDataByteSize ); + } + else { // fill multiple streams with zeros + for ( unsigned int i=0; inStreams[0]; i++ ) { + memset( outBufferList->mBuffers[handle->iStream[0]+i].mData, + 0, + outBufferList->mBuffers[handle->iStream[0]+i].mDataByteSize ); + } + } + } + else if ( handle->nStreams[0] == 1 ) { + if ( stream_.doConvertBuffer[0] ) { // convert directly to CoreAudio stream buffer + convertBuffer( (char *) outBufferList->mBuffers[handle->iStream[0]].mData, + stream_.userBuffer[0], stream_.convertInfo[0] ); + } + else { // copy from user buffer + memcpy( outBufferList->mBuffers[handle->iStream[0]].mData, + stream_.userBuffer[0], + outBufferList->mBuffers[handle->iStream[0]].mDataByteSize ); + } + } + else { // fill multiple streams + Float32 *inBuffer = (Float32 *) stream_.userBuffer[0]; + if ( stream_.doConvertBuffer[0] ) { + convertBuffer( stream_.deviceBuffer, stream_.userBuffer[0], stream_.convertInfo[0] ); + inBuffer = (Float32 *) stream_.deviceBuffer; + } + + if ( stream_.deviceInterleaved[0] == false ) { // mono mode + UInt32 bufferBytes = outBufferList->mBuffers[handle->iStream[0]].mDataByteSize; + for ( unsigned int i=0; imBuffers[handle->iStream[0]+i].mData, + (void *)&inBuffer[i*stream_.bufferSize], bufferBytes ); + } + } + else { // fill multiple multi-channel streams with interleaved data + UInt32 streamChannels, channelsLeft, inJump, outJump, inOffset; + Float32 *out, *in; + + bool inInterleaved = ( stream_.userInterleaved ) ? true : false; + UInt32 inChannels = stream_.nUserChannels[0]; + if ( stream_.doConvertBuffer[0] ) { + inInterleaved = true; // device buffer will always be interleaved for nStreams > 1 and not mono mode + inChannels = stream_.nDeviceChannels[0]; + } + + if ( inInterleaved ) inOffset = 1; + else inOffset = stream_.bufferSize; + + channelsLeft = inChannels; + for ( unsigned int i=0; inStreams[0]; i++ ) { + in = inBuffer; + out = (Float32 *) outBufferList->mBuffers[handle->iStream[0]+i].mData; + streamChannels = outBufferList->mBuffers[handle->iStream[0]+i].mNumberChannels; + + outJump = 0; + // Account for possible channel offset in first stream + if ( i == 0 && stream_.channelOffset[0] > 0 ) { + streamChannels -= stream_.channelOffset[0]; + outJump = stream_.channelOffset[0]; + out += outJump; + } + + // Account for possible unfilled channels at end of the last stream + if ( streamChannels > channelsLeft ) { + outJump = streamChannels - channelsLeft; + streamChannels = channelsLeft; + } + + // Determine input buffer offsets and skips + if ( inInterleaved ) { + inJump = inChannels; + in += inChannels - channelsLeft; + } + else { + inJump = 1; + in += (inChannels - channelsLeft) * inOffset; + } + + for ( unsigned int i=0; idrainCounter ) { + handle->drainCounter++; + goto unlock; + } + + AudioDeviceID inputDevice; + inputDevice = handle->id[1]; + if ( stream_.mode == INPUT || ( stream_.mode == DUPLEX && deviceId == inputDevice ) ) { + + if ( handle->nStreams[1] == 1 ) { + if ( stream_.doConvertBuffer[1] ) { // convert directly from CoreAudio stream buffer + convertBuffer( stream_.userBuffer[1], + (char *) inBufferList->mBuffers[handle->iStream[1]].mData, + stream_.convertInfo[1] ); + } + else { // copy to user buffer + memcpy( stream_.userBuffer[1], + inBufferList->mBuffers[handle->iStream[1]].mData, + inBufferList->mBuffers[handle->iStream[1]].mDataByteSize ); + } + } + else { // read from multiple streams + Float32 *outBuffer = (Float32 *) stream_.userBuffer[1]; + if ( stream_.doConvertBuffer[1] ) outBuffer = (Float32 *) stream_.deviceBuffer; + + if ( stream_.deviceInterleaved[1] == false ) { // mono mode + UInt32 bufferBytes = inBufferList->mBuffers[handle->iStream[1]].mDataByteSize; + for ( unsigned int i=0; imBuffers[handle->iStream[1]+i].mData, bufferBytes ); + } + } + else { // read from multiple multi-channel streams + UInt32 streamChannels, channelsLeft, inJump, outJump, outOffset; + Float32 *out, *in; + + bool outInterleaved = ( stream_.userInterleaved ) ? true : false; + UInt32 outChannels = stream_.nUserChannels[1]; + if ( stream_.doConvertBuffer[1] ) { + outInterleaved = true; // device buffer will always be interleaved for nStreams > 1 and not mono mode + outChannels = stream_.nDeviceChannels[1]; + } + + if ( outInterleaved ) outOffset = 1; + else outOffset = stream_.bufferSize; + + channelsLeft = outChannels; + for ( unsigned int i=0; inStreams[1]; i++ ) { + out = outBuffer; + in = (Float32 *) inBufferList->mBuffers[handle->iStream[1]+i].mData; + streamChannels = inBufferList->mBuffers[handle->iStream[1]+i].mNumberChannels; + + inJump = 0; + // Account for possible channel offset in first stream + if ( i == 0 && stream_.channelOffset[1] > 0 ) { + streamChannels -= stream_.channelOffset[1]; + inJump = stream_.channelOffset[1]; + in += inJump; + } + + // Account for possible unread channels at end of the last stream + if ( streamChannels > channelsLeft ) { + inJump = streamChannels - channelsLeft; + streamChannels = channelsLeft; + } + + // Determine output buffer offsets and skips + if ( outInterleaved ) { + outJump = outChannels; + out += outChannels - channelsLeft; + } + else { + outJump = 1; + out += (outChannels - channelsLeft) * outOffset; + } + + for ( unsigned int i=0; i +#include +#include + +// A structure to hold various information related to the Jack API +// implementation. +struct JackHandle { + jack_client_t *client; + jack_port_t **ports[2]; + std::string deviceName[2]; + bool xrun[2]; + pthread_cond_t condition; + int drainCounter; // Tracks callback counts when draining + bool internalDrain; // Indicates if stop is initiated from callback or not. + + JackHandle() + :client(0), drainCounter(0), internalDrain(false) { ports[0] = 0; ports[1] = 0; xrun[0] = false; xrun[1] = false; } +}; + +/* --- Monocasual hack ---------------------------------------------- */ +#ifdef __linux__ +void *RtApi :: __HACK__getJackClient() { + JackHandle *handle = (JackHandle *) stream_.apiHandle; + return (void*) handle->client; +} +#endif +/* ------------------------------------------------------------------ */ + +static void jackSilentError( const char * ) {}; + +RtApiJack :: RtApiJack() +{ + // Nothing to do here. +#if !defined(__RTAUDIO_DEBUG__) + // Turn off Jack's internal error reporting. + jack_set_error_function( &jackSilentError ); +#endif +} + +RtApiJack :: ~RtApiJack() +{ + if ( stream_.state != STREAM_CLOSED ) closeStream(); +} + +unsigned int RtApiJack :: getDeviceCount( void ) +{ + // See if we can become a jack client. + jack_options_t options = (jack_options_t) ( JackNoStartServer ); //JackNullOption; + jack_status_t *status = NULL; + jack_client_t *client = jack_client_open( "RtApiJackCount", options, status ); + if ( client == 0 ) return 0; + + const char **ports; + std::string port, previousPort; + unsigned int nChannels = 0, nDevices = 0; + ports = jack_get_ports( client, NULL, NULL, 0 ); + if ( ports ) { + // Parse the port names up to the first colon (:). + size_t iColon = 0; + do { + port = (char *) ports[ nChannels ]; + iColon = port.find(":"); + if ( iColon != std::string::npos ) { + port = port.substr( 0, iColon + 1 ); + if ( port != previousPort ) { + nDevices++; + previousPort = port; + } + } + } while ( ports[++nChannels] ); + free( ports ); + } + + jack_client_close( client ); + return nDevices; +} + +RtAudio::DeviceInfo RtApiJack :: getDeviceInfo( unsigned int device ) +{ + RtAudio::DeviceInfo info; + info.probed = false; + + jack_options_t options = (jack_options_t) ( JackNoStartServer ); //JackNullOption + jack_status_t *status = NULL; + jack_client_t *client = jack_client_open( "RtApiJackInfo", options, status ); + if ( client == 0 ) { + errorText_ = "RtApiJack::getDeviceInfo: Jack server not found or connection error!"; + error( RtAudioError::WARNING ); + return info; + } + + const char **ports; + std::string port, previousPort; + unsigned int nPorts = 0, nDevices = 0; + ports = jack_get_ports( client, NULL, NULL, 0 ); + if ( ports ) { + // Parse the port names up to the first colon (:). + size_t iColon = 0; + do { + port = (char *) ports[ nPorts ]; + iColon = port.find(":"); + if ( iColon != std::string::npos ) { + port = port.substr( 0, iColon ); + if ( port != previousPort ) { + if ( nDevices == device ) info.name = port; + nDevices++; + previousPort = port; + } + } + } while ( ports[++nPorts] ); + free( ports ); + } + + if ( device >= nDevices ) { + jack_client_close( client ); + errorText_ = "RtApiJack::getDeviceInfo: device ID is invalid!"; + error( RtAudioError::INVALID_USE ); + return info; + } + + // Get the current jack server sample rate. + info.sampleRates.clear(); + info.sampleRates.push_back( jack_get_sample_rate( client ) ); + + // Count the available ports containing the client name as device + // channels. Jack "input ports" equal RtAudio output channels. + unsigned int nChannels = 0; + ports = jack_get_ports( client, info.name.c_str(), NULL, JackPortIsInput ); + if ( ports ) { + while ( ports[ nChannels ] ) nChannels++; + free( ports ); + info.outputChannels = nChannels; + } + + // Jack "output ports" equal RtAudio input channels. + nChannels = 0; + ports = jack_get_ports( client, info.name.c_str(), NULL, JackPortIsOutput ); + if ( ports ) { + while ( ports[ nChannels ] ) nChannels++; + free( ports ); + info.inputChannels = nChannels; + } + + if ( info.outputChannels == 0 && info.inputChannels == 0 ) { + jack_client_close(client); + errorText_ = "RtApiJack::getDeviceInfo: error determining Jack input/output channels!"; + error( RtAudioError::WARNING ); + return info; + } + + // If device opens for both playback and capture, we determine the channels. + if ( info.outputChannels > 0 && info.inputChannels > 0 ) + info.duplexChannels = (info.outputChannels > info.inputChannels) ? info.inputChannels : info.outputChannels; + + // Jack always uses 32-bit floats. + info.nativeFormats = RTAUDIO_FLOAT32; + + // Jack doesn't provide default devices so we'll use the first available one. + if ( device == 0 && info.outputChannels > 0 ) + info.isDefaultOutput = true; + if ( device == 0 && info.inputChannels > 0 ) + info.isDefaultInput = true; + + jack_client_close(client); + info.probed = true; + return info; +} + +static int jackCallbackHandler( jack_nframes_t nframes, void *infoPointer ) +{ + CallbackInfo *info = (CallbackInfo *) infoPointer; + + RtApiJack *object = (RtApiJack *) info->object; + if ( object->callbackEvent( (unsigned long) nframes ) == false ) return 1; + + return 0; +} + +// This function will be called by a spawned thread when the Jack +// server signals that it is shutting down. It is necessary to handle +// it this way because the jackShutdown() function must return before +// the jack_deactivate() function (in closeStream()) will return. +static void *jackCloseStream( void *ptr ) +{ + CallbackInfo *info = (CallbackInfo *) ptr; + RtApiJack *object = (RtApiJack *) info->object; + + object->closeStream(); + + pthread_exit( NULL ); +} +static void jackShutdown( void *infoPointer ) +{ + CallbackInfo *info = (CallbackInfo *) infoPointer; + RtApiJack *object = (RtApiJack *) info->object; + + // Check current stream state. If stopped, then we'll assume this + // was called as a result of a call to RtApiJack::stopStream (the + // deactivation of a client handle causes this function to be called). + // If not, we'll assume the Jack server is shutting down or some + // other problem occurred and we should close the stream. + if ( object->isStreamRunning() == false ) return; + + ThreadHandle threadId; + pthread_create( &threadId, NULL, jackCloseStream, info ); + std::cerr << "\nRtApiJack: the Jack server is shutting down this client ... stream stopped and closed!!\n" << std::endl; +} + +static int jackXrun( void *infoPointer ) +{ + JackHandle *handle = (JackHandle *) infoPointer; + + if ( handle->ports[0] ) handle->xrun[0] = true; + if ( handle->ports[1] ) handle->xrun[1] = true; + + return 0; +} + +bool RtApiJack :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, + unsigned int firstChannel, unsigned int sampleRate, + RtAudioFormat format, unsigned int *bufferSize, + RtAudio::StreamOptions *options ) +{ + JackHandle *handle = (JackHandle *) stream_.apiHandle; + + // Look for jack server and try to become a client (only do once per stream). + jack_client_t *client = 0; + if ( mode == OUTPUT || ( mode == INPUT && stream_.mode != OUTPUT ) ) { + jack_options_t jackoptions = (jack_options_t) ( JackNoStartServer ); //JackNullOption; + jack_status_t *status = NULL; + if ( options && !options->streamName.empty() ) + client = jack_client_open( options->streamName.c_str(), jackoptions, status ); + else + client = jack_client_open( "RtApiJack", jackoptions, status ); + if ( client == 0 ) { + errorText_ = "RtApiJack::probeDeviceOpen: Jack server not found or connection error!"; + error( RtAudioError::WARNING ); + return FAILURE; + } + } + else { + // The handle must have been created on an earlier pass. + client = handle->client; + } + + const char **ports; + std::string port, previousPort, deviceName; + unsigned int nPorts = 0, nDevices = 0; + ports = jack_get_ports( client, NULL, NULL, 0 ); + if ( ports ) { + // Parse the port names up to the first colon (:). + size_t iColon = 0; + do { + port = (char *) ports[ nPorts ]; + iColon = port.find(":"); + if ( iColon != std::string::npos ) { + port = port.substr( 0, iColon ); + if ( port != previousPort ) { + if ( nDevices == device ) deviceName = port; + nDevices++; + previousPort = port; + } + } + } while ( ports[++nPorts] ); + free( ports ); + } + + if ( device >= nDevices ) { + errorText_ = "RtApiJack::probeDeviceOpen: device ID is invalid!"; + return FAILURE; + } + + // Count the available ports containing the client name as device + // channels. Jack "input ports" equal RtAudio output channels. + unsigned int nChannels = 0; + unsigned long flag = JackPortIsInput; + if ( mode == INPUT ) flag = JackPortIsOutput; + ports = jack_get_ports( client, deviceName.c_str(), NULL, flag ); + if ( ports ) { + while ( ports[ nChannels ] ) nChannels++; + free( ports ); + } + + // Compare the jack ports for specified client to the requested number of channels. + if ( nChannels < (channels + firstChannel) ) { + errorStream_ << "RtApiJack::probeDeviceOpen: requested number of channels (" << channels << ") + offset (" << firstChannel << ") not found for specified device (" << device << ":" << deviceName << ")."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Check the jack server sample rate. + unsigned int jackRate = jack_get_sample_rate( client ); + if ( sampleRate != jackRate ) { + jack_client_close( client ); + errorStream_ << "RtApiJack::probeDeviceOpen: the requested sample rate (" << sampleRate << ") is different than the JACK server rate (" << jackRate << ")."; + errorText_ = errorStream_.str(); + return FAILURE; + } + stream_.sampleRate = jackRate; + + // Get the latency of the JACK port. + ports = jack_get_ports( client, deviceName.c_str(), NULL, flag ); + if ( ports[ firstChannel ] ) { + // Added by Ge Wang + jack_latency_callback_mode_t cbmode = (mode == INPUT ? JackCaptureLatency : JackPlaybackLatency); + // the range (usually the min and max are equal) + jack_latency_range_t latrange; latrange.min = latrange.max = 0; + // get the latency range + jack_port_get_latency_range( jack_port_by_name( client, ports[firstChannel] ), cbmode, &latrange ); + // be optimistic, use the min! + stream_.latency[mode] = latrange.min; + //stream_.latency[mode] = jack_port_get_latency( jack_port_by_name( client, ports[ firstChannel ] ) ); + } + free( ports ); + + // The jack server always uses 32-bit floating-point data. + stream_.deviceFormat[mode] = RTAUDIO_FLOAT32; + stream_.userFormat = format; + + if ( options && options->flags & RTAUDIO_NONINTERLEAVED ) stream_.userInterleaved = false; + else stream_.userInterleaved = true; + + // Jack always uses non-interleaved buffers. + stream_.deviceInterleaved[mode] = false; + + // Jack always provides host byte-ordered data. + stream_.doByteSwap[mode] = false; + + // Get the buffer size. The buffer size and number of buffers + // (periods) is set when the jack server is started. + stream_.bufferSize = (int) jack_get_buffer_size( client ); + *bufferSize = stream_.bufferSize; + + stream_.nDeviceChannels[mode] = channels; + stream_.nUserChannels[mode] = channels; + + // Set flags for buffer conversion. + stream_.doConvertBuffer[mode] = false; + if ( stream_.userFormat != stream_.deviceFormat[mode] ) + stream_.doConvertBuffer[mode] = true; + if ( stream_.userInterleaved != stream_.deviceInterleaved[mode] && + stream_.nUserChannels[mode] > 1 ) + stream_.doConvertBuffer[mode] = true; + + // Allocate our JackHandle structure for the stream. + if ( handle == 0 ) { + try { + handle = new JackHandle; + } + catch ( std::bad_alloc& ) { + errorText_ = "RtApiJack::probeDeviceOpen: error allocating JackHandle memory."; + goto error; + } + + if ( pthread_cond_init(&handle->condition, NULL) ) { + errorText_ = "RtApiJack::probeDeviceOpen: error initializing pthread condition variable."; + goto error; + } + stream_.apiHandle = (void *) handle; + handle->client = client; + } + handle->deviceName[mode] = deviceName; + + // Allocate necessary internal buffers. + unsigned long bufferBytes; + bufferBytes = stream_.nUserChannels[mode] * *bufferSize * formatBytes( stream_.userFormat ); + stream_.userBuffer[mode] = (char *) calloc( bufferBytes, 1 ); + if ( stream_.userBuffer[mode] == NULL ) { + errorText_ = "RtApiJack::probeDeviceOpen: error allocating user buffer memory."; + goto error; + } + + if ( stream_.doConvertBuffer[mode] ) { + + bool makeBuffer = true; + if ( mode == OUTPUT ) + bufferBytes = stream_.nDeviceChannels[0] * formatBytes( stream_.deviceFormat[0] ); + else { // mode == INPUT + bufferBytes = stream_.nDeviceChannels[1] * formatBytes( stream_.deviceFormat[1] ); + if ( stream_.mode == OUTPUT && stream_.deviceBuffer ) { + unsigned long bytesOut = stream_.nDeviceChannels[0] * formatBytes(stream_.deviceFormat[0]); + if ( bufferBytes < bytesOut ) makeBuffer = false; + } + } + + if ( makeBuffer ) { + bufferBytes *= *bufferSize; + if ( stream_.deviceBuffer ) free( stream_.deviceBuffer ); + stream_.deviceBuffer = (char *) calloc( bufferBytes, 1 ); + if ( stream_.deviceBuffer == NULL ) { + errorText_ = "RtApiJack::probeDeviceOpen: error allocating device buffer memory."; + goto error; + } + } + } + + // Allocate memory for the Jack ports (channels) identifiers. + handle->ports[mode] = (jack_port_t **) malloc ( sizeof (jack_port_t *) * channels ); + if ( handle->ports[mode] == NULL ) { + errorText_ = "RtApiJack::probeDeviceOpen: error allocating port memory."; + goto error; + } + + stream_.device[mode] = device; + stream_.channelOffset[mode] = firstChannel; + stream_.state = STREAM_STOPPED; + stream_.callbackInfo.object = (void *) this; + + if ( stream_.mode == OUTPUT && mode == INPUT ) + // We had already set up the stream for output. + stream_.mode = DUPLEX; + else { + stream_.mode = mode; + jack_set_process_callback( handle->client, jackCallbackHandler, (void *) &stream_.callbackInfo ); + jack_set_xrun_callback( handle->client, jackXrun, (void *) &handle ); + jack_on_shutdown( handle->client, jackShutdown, (void *) &stream_.callbackInfo ); + } + + // Register our ports. + char label[64]; + if ( mode == OUTPUT ) { + for ( unsigned int i=0; iports[0][i] = jack_port_register( handle->client, (const char *)label, + JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0 ); + } + } + else { + for ( unsigned int i=0; iports[1][i] = jack_port_register( handle->client, (const char *)label, + JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0 ); + } + } + + // Setup the buffer conversion information structure. We don't use + // buffers to do channel offsets, so we override that parameter + // here. + if ( stream_.doConvertBuffer[mode] ) setConvertInfo( mode, 0 ); + + return SUCCESS; + + error: + if ( handle ) { + pthread_cond_destroy( &handle->condition ); + jack_client_close( handle->client ); + + if ( handle->ports[0] ) free( handle->ports[0] ); + if ( handle->ports[1] ) free( handle->ports[1] ); + + delete handle; + stream_.apiHandle = 0; + } + + for ( int i=0; i<2; i++ ) { + if ( stream_.userBuffer[i] ) { + free( stream_.userBuffer[i] ); + stream_.userBuffer[i] = 0; + } + } + + if ( stream_.deviceBuffer ) { + free( stream_.deviceBuffer ); + stream_.deviceBuffer = 0; + } + + return FAILURE; +} + +void RtApiJack :: closeStream( void ) +{ + if ( stream_.state == STREAM_CLOSED ) { + errorText_ = "RtApiJack::closeStream(): no open stream to close!"; + error( RtAudioError::WARNING ); + return; + } + + JackHandle *handle = (JackHandle *) stream_.apiHandle; + if ( handle ) { + + if ( stream_.state == STREAM_RUNNING ) + jack_deactivate( handle->client ); + + jack_client_close( handle->client ); + } + + if ( handle ) { + if ( handle->ports[0] ) free( handle->ports[0] ); + if ( handle->ports[1] ) free( handle->ports[1] ); + pthread_cond_destroy( &handle->condition ); + delete handle; + stream_.apiHandle = 0; + } + + for ( int i=0; i<2; i++ ) { + if ( stream_.userBuffer[i] ) { + free( stream_.userBuffer[i] ); + stream_.userBuffer[i] = 0; + } + } + + if ( stream_.deviceBuffer ) { + free( stream_.deviceBuffer ); + stream_.deviceBuffer = 0; + } + + stream_.mode = UNINITIALIZED; + stream_.state = STREAM_CLOSED; +} + +void RtApiJack :: startStream( void ) +{ + verifyStream(); + if ( stream_.state == STREAM_RUNNING ) { + errorText_ = "RtApiJack::startStream(): the stream is already running!"; + error( RtAudioError::WARNING ); + return; + } + + JackHandle *handle = (JackHandle *) stream_.apiHandle; + int result = jack_activate( handle->client ); + if ( result ) { + errorText_ = "RtApiJack::startStream(): unable to activate JACK client!"; + goto unlock; + } + + const char **ports; + + // Get the list of available ports. + if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { + result = 1; + ports = jack_get_ports( handle->client, handle->deviceName[0].c_str(), NULL, JackPortIsInput); + if ( ports == NULL) { + errorText_ = "RtApiJack::startStream(): error determining available JACK input ports!"; + goto unlock; + } + + // Now make the port connections. Since RtAudio wasn't designed to + // allow the user to select particular channels of a device, we'll + // just open the first "nChannels" ports with offset. + for ( unsigned int i=0; iclient, jack_port_name( handle->ports[0][i] ), ports[ stream_.channelOffset[0] + i ] ); + if ( result ) { + free( ports ); + errorText_ = "RtApiJack::startStream(): error connecting output ports!"; + goto unlock; + } + } + free(ports); + } + + if ( stream_.mode == INPUT || stream_.mode == DUPLEX ) { + result = 1; + ports = jack_get_ports( handle->client, handle->deviceName[1].c_str(), NULL, JackPortIsOutput ); + if ( ports == NULL) { + errorText_ = "RtApiJack::startStream(): error determining available JACK output ports!"; + goto unlock; + } + + // Now make the port connections. See note above. + for ( unsigned int i=0; iclient, ports[ stream_.channelOffset[1] + i ], jack_port_name( handle->ports[1][i] ) ); + if ( result ) { + free( ports ); + errorText_ = "RtApiJack::startStream(): error connecting input ports!"; + goto unlock; + } + } + free(ports); + } + + handle->drainCounter = 0; + handle->internalDrain = false; + stream_.state = STREAM_RUNNING; + + unlock: + if ( result == 0 ) return; + error( RtAudioError::SYSTEM_ERROR ); +} + +void RtApiJack :: stopStream( void ) +{ + verifyStream(); + if ( stream_.state == STREAM_STOPPED ) { + errorText_ = "RtApiJack::stopStream(): the stream is already stopped!"; + error( RtAudioError::WARNING ); + return; + } + + JackHandle *handle = (JackHandle *) stream_.apiHandle; + if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { + + if ( handle->drainCounter == 0 ) { + handle->drainCounter = 2; + pthread_cond_wait( &handle->condition, &stream_.mutex ); // block until signaled + } + } + + jack_deactivate( handle->client ); + stream_.state = STREAM_STOPPED; +} + +void RtApiJack :: abortStream( void ) +{ + verifyStream(); + if ( stream_.state == STREAM_STOPPED ) { + errorText_ = "RtApiJack::abortStream(): the stream is already stopped!"; + error( RtAudioError::WARNING ); + return; + } + + JackHandle *handle = (JackHandle *) stream_.apiHandle; + handle->drainCounter = 2; + + stopStream(); +} + +// This function will be called by a spawned thread when the user +// callback function signals that the stream should be stopped or +// aborted. It is necessary to handle it this way because the +// callbackEvent() function must return before the jack_deactivate() +// function will return. +static void *jackStopStream( void *ptr ) +{ + CallbackInfo *info = (CallbackInfo *) ptr; + RtApiJack *object = (RtApiJack *) info->object; + + object->stopStream(); + pthread_exit( NULL ); +} + +bool RtApiJack :: callbackEvent( unsigned long nframes ) +{ + if ( stream_.state == STREAM_STOPPED || stream_.state == STREAM_STOPPING ) return SUCCESS; + if ( stream_.state == STREAM_CLOSED ) { + errorText_ = "RtApiCore::callbackEvent(): the stream is closed ... this shouldn't happen!"; + error( RtAudioError::WARNING ); + return FAILURE; + } + if ( stream_.bufferSize != nframes ) { + errorText_ = "RtApiCore::callbackEvent(): the JACK buffer size has changed ... cannot process!"; + error( RtAudioError::WARNING ); + return FAILURE; + } + + CallbackInfo *info = (CallbackInfo *) &stream_.callbackInfo; + JackHandle *handle = (JackHandle *) stream_.apiHandle; + + // Check if we were draining the stream and signal is finished. + if ( handle->drainCounter > 3 ) { + ThreadHandle threadId; + + stream_.state = STREAM_STOPPING; + if ( handle->internalDrain == true ) + pthread_create( &threadId, NULL, jackStopStream, info ); + else + pthread_cond_signal( &handle->condition ); + return SUCCESS; + } + + // Invoke user callback first, to get fresh output data. + if ( handle->drainCounter == 0 ) { + RtAudioCallback callback = (RtAudioCallback) info->callback; + double streamTime = getStreamTime(); + RtAudioStreamStatus status = 0; + if ( stream_.mode != INPUT && handle->xrun[0] == true ) { + status |= RTAUDIO_OUTPUT_UNDERFLOW; + handle->xrun[0] = false; + } + if ( stream_.mode != OUTPUT && handle->xrun[1] == true ) { + status |= RTAUDIO_INPUT_OVERFLOW; + handle->xrun[1] = false; + } + int cbReturnValue = callback( stream_.userBuffer[0], stream_.userBuffer[1], + stream_.bufferSize, streamTime, status, info->userData ); + if ( cbReturnValue == 2 ) { + stream_.state = STREAM_STOPPING; + handle->drainCounter = 2; + ThreadHandle id; + pthread_create( &id, NULL, jackStopStream, info ); + return SUCCESS; + } + else if ( cbReturnValue == 1 ) { + handle->drainCounter = 1; + handle->internalDrain = true; + } + } + + jack_default_audio_sample_t *jackbuffer; + unsigned long bufferBytes = nframes * sizeof( jack_default_audio_sample_t ); + if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { + + if ( handle->drainCounter > 1 ) { // write zeros to the output stream + + for ( unsigned int i=0; iports[0][i], (jack_nframes_t) nframes ); + memset( jackbuffer, 0, bufferBytes ); + } + + } + else if ( stream_.doConvertBuffer[0] ) { + + convertBuffer( stream_.deviceBuffer, stream_.userBuffer[0], stream_.convertInfo[0] ); + + for ( unsigned int i=0; iports[0][i], (jack_nframes_t) nframes ); + memcpy( jackbuffer, &stream_.deviceBuffer[i*bufferBytes], bufferBytes ); + } + } + else { // no buffer conversion + for ( unsigned int i=0; iports[0][i], (jack_nframes_t) nframes ); + memcpy( jackbuffer, &stream_.userBuffer[0][i*bufferBytes], bufferBytes ); + } + } + } + + // Don't bother draining input + if ( handle->drainCounter ) { + handle->drainCounter++; + goto unlock; + } + + if ( stream_.mode == INPUT || stream_.mode == DUPLEX ) { + + if ( stream_.doConvertBuffer[1] ) { + for ( unsigned int i=0; iports[1][i], (jack_nframes_t) nframes ); + memcpy( &stream_.deviceBuffer[i*bufferBytes], jackbuffer, bufferBytes ); + } + convertBuffer( stream_.userBuffer[1], stream_.deviceBuffer, stream_.convertInfo[1] ); + } + else { // no buffer conversion + for ( unsigned int i=0; iports[1][i], (jack_nframes_t) nframes ); + memcpy( &stream_.userBuffer[1][i*bufferBytes], jackbuffer, bufferBytes ); + } + } + } + + unlock: + RtApi::tickStreamTime(); + return SUCCESS; +} + //******************** End of __UNIX_JACK__ *********************// +#endif + +#if defined(__WINDOWS_ASIO__) // ASIO API on Windows + +// The ASIO API is designed around a callback scheme, so this +// implementation is similar to that used for OS-X CoreAudio and Linux +// Jack. The primary constraint with ASIO is that it only allows +// access to a single driver at a time. Thus, it is not possible to +// have more than one simultaneous RtAudio stream. +// +// This implementation also requires a number of external ASIO files +// and a few global variables. The ASIO callback scheme does not +// allow for the passing of user data, so we must create a global +// pointer to our callbackInfo structure. +// +// On unix systems, we make use of a pthread condition variable. +// Since there is no equivalent in Windows, I hacked something based +// on information found in +// http://www.cs.wustl.edu/~schmidt/win32-cv-1.html. + +#include "asiosys.h" +#include "asio.h" +#include "iasiothiscallresolver.h" +#include "asiodrivers.h" +#include + +static AsioDrivers drivers; +static ASIOCallbacks asioCallbacks; +static ASIODriverInfo driverInfo; +static CallbackInfo *asioCallbackInfo; +static bool asioXRun; + +struct AsioHandle { + int drainCounter; // Tracks callback counts when draining + bool internalDrain; // Indicates if stop is initiated from callback or not. + ASIOBufferInfo *bufferInfos; + HANDLE condition; + + AsioHandle() + :drainCounter(0), internalDrain(false), bufferInfos(0) {} +}; + +// Function declarations (definitions at end of section) +static const char* getAsioErrorString( ASIOError result ); +static void sampleRateChanged( ASIOSampleRate sRate ); +static long asioMessages( long selector, long value, void* message, double* opt ); + +RtApiAsio :: RtApiAsio() +{ + // ASIO cannot run on a multi-threaded appartment. You can call + // CoInitialize beforehand, but it must be for appartment threading + // (in which case, CoInitilialize will return S_FALSE here). + coInitialized_ = false; + HRESULT hr = CoInitialize( NULL ); + if ( FAILED(hr) ) { + errorText_ = "RtApiAsio::ASIO requires a single-threaded appartment. Call CoInitializeEx(0,COINIT_APARTMENTTHREADED)"; + error( RtAudioError::WARNING ); + } + coInitialized_ = true; + + drivers.removeCurrentDriver(); + driverInfo.asioVersion = 2; + + // See note in DirectSound implementation about GetDesktopWindow(). + driverInfo.sysRef = GetForegroundWindow(); +} + +RtApiAsio :: ~RtApiAsio() +{ + if ( stream_.state != STREAM_CLOSED ) closeStream(); + if ( coInitialized_ ) CoUninitialize(); +} + +unsigned int RtApiAsio :: getDeviceCount( void ) +{ + return (unsigned int) drivers.asioGetNumDev(); +} + +RtAudio::DeviceInfo RtApiAsio :: getDeviceInfo( unsigned int device ) +{ + RtAudio::DeviceInfo info; + info.probed = false; + + // Get device ID + unsigned int nDevices = getDeviceCount(); + if ( nDevices == 0 ) { + errorText_ = "RtApiAsio::getDeviceInfo: no devices found!"; + error( RtAudioError::INVALID_USE ); + return info; + } + + if ( device >= nDevices ) { + errorText_ = "RtApiAsio::getDeviceInfo: device ID is invalid!"; + error( RtAudioError::INVALID_USE ); + return info; + } + + // If a stream is already open, we cannot probe other devices. Thus, use the saved results. + if ( stream_.state != STREAM_CLOSED ) { + if ( device >= devices_.size() ) { + errorText_ = "RtApiAsio::getDeviceInfo: device ID was not present before stream was opened."; + error( RtAudioError::WARNING ); + return info; + } + return devices_[ device ]; + } + + char driverName[32]; + ASIOError result = drivers.asioGetDriverName( (int) device, driverName, 32 ); + if ( result != ASE_OK ) { + errorStream_ << "RtApiAsio::getDeviceInfo: unable to get driver name (" << getAsioErrorString( result ) << ")."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + return info; + } + + info.name = driverName; + + if ( !drivers.loadDriver( driverName ) ) { + errorStream_ << "RtApiAsio::getDeviceInfo: unable to load driver (" << driverName << ")."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + return info; + } + + result = ASIOInit( &driverInfo ); + if ( result != ASE_OK ) { + errorStream_ << "RtApiAsio::getDeviceInfo: error (" << getAsioErrorString( result ) << ") initializing driver (" << driverName << ")."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + return info; + } + + // Determine the device channel information. + long inputChannels, outputChannels; + result = ASIOGetChannels( &inputChannels, &outputChannels ); + if ( result != ASE_OK ) { + drivers.removeCurrentDriver(); + errorStream_ << "RtApiAsio::getDeviceInfo: error (" << getAsioErrorString( result ) << ") getting channel count (" << driverName << ")."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + return info; + } + + info.outputChannels = outputChannels; + info.inputChannels = inputChannels; + if ( info.outputChannels > 0 && info.inputChannels > 0 ) + info.duplexChannels = (info.outputChannels > info.inputChannels) ? info.inputChannels : info.outputChannels; + + // Determine the supported sample rates. + info.sampleRates.clear(); + for ( unsigned int i=0; i 0 ) + if ( getDefaultOutputDevice() == device ) info.isDefaultOutput = true; + if ( info.inputChannels > 0 ) + if ( getDefaultInputDevice() == device ) info.isDefaultInput = true; + + info.probed = true; + drivers.removeCurrentDriver(); + return info; +} + +static void bufferSwitch( long index, ASIOBool /*processNow*/ ) +{ + RtApiAsio *object = (RtApiAsio *) asioCallbackInfo->object; + object->callbackEvent( index ); +} + +void RtApiAsio :: saveDeviceInfo( void ) +{ + devices_.clear(); + + unsigned int nDevices = getDeviceCount(); + devices_.resize( nDevices ); + for ( unsigned int i=0; isaveDeviceInfo(); + + if ( !drivers.loadDriver( driverName ) ) { + errorStream_ << "RtApiAsio::probeDeviceOpen: unable to load driver (" << driverName << ")."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + result = ASIOInit( &driverInfo ); + if ( result != ASE_OK ) { + errorStream_ << "RtApiAsio::probeDeviceOpen: error (" << getAsioErrorString( result ) << ") initializing driver (" << driverName << ")."; + errorText_ = errorStream_.str(); + return FAILURE; + } + } + + // Check the device channel count. + long inputChannels, outputChannels; + result = ASIOGetChannels( &inputChannels, &outputChannels ); + if ( result != ASE_OK ) { + drivers.removeCurrentDriver(); + errorStream_ << "RtApiAsio::probeDeviceOpen: error (" << getAsioErrorString( result ) << ") getting channel count (" << driverName << ")."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + if ( ( mode == OUTPUT && (channels+firstChannel) > (unsigned int) outputChannels) || + ( mode == INPUT && (channels+firstChannel) > (unsigned int) inputChannels) ) { + drivers.removeCurrentDriver(); + errorStream_ << "RtApiAsio::probeDeviceOpen: driver (" << driverName << ") does not support requested channel count (" << channels << ") + offset (" << firstChannel << ")."; + errorText_ = errorStream_.str(); + return FAILURE; + } + stream_.nDeviceChannels[mode] = channels; + stream_.nUserChannels[mode] = channels; + stream_.channelOffset[mode] = firstChannel; + + // Verify the sample rate is supported. + result = ASIOCanSampleRate( (ASIOSampleRate) sampleRate ); + if ( result != ASE_OK ) { + drivers.removeCurrentDriver(); + errorStream_ << "RtApiAsio::probeDeviceOpen: driver (" << driverName << ") does not support requested sample rate (" << sampleRate << ")."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Get the current sample rate + ASIOSampleRate currentRate; + result = ASIOGetSampleRate( ¤tRate ); + if ( result != ASE_OK ) { + drivers.removeCurrentDriver(); + errorStream_ << "RtApiAsio::probeDeviceOpen: driver (" << driverName << ") error getting sample rate."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Set the sample rate only if necessary + if ( currentRate != sampleRate ) { + result = ASIOSetSampleRate( (ASIOSampleRate) sampleRate ); + if ( result != ASE_OK ) { + drivers.removeCurrentDriver(); + errorStream_ << "RtApiAsio::probeDeviceOpen: driver (" << driverName << ") error setting sample rate (" << sampleRate << ")."; + errorText_ = errorStream_.str(); + return FAILURE; + } + } + + // Determine the driver data type. + ASIOChannelInfo channelInfo; + channelInfo.channel = 0; + if ( mode == OUTPUT ) channelInfo.isInput = false; + else channelInfo.isInput = true; + result = ASIOGetChannelInfo( &channelInfo ); + if ( result != ASE_OK ) { + drivers.removeCurrentDriver(); + errorStream_ << "RtApiAsio::probeDeviceOpen: driver (" << driverName << ") error (" << getAsioErrorString( result ) << ") getting data format."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Assuming WINDOWS host is always little-endian. + stream_.doByteSwap[mode] = false; + stream_.userFormat = format; + stream_.deviceFormat[mode] = 0; + if ( channelInfo.type == ASIOSTInt16MSB || channelInfo.type == ASIOSTInt16LSB ) { + stream_.deviceFormat[mode] = RTAUDIO_SINT16; + if ( channelInfo.type == ASIOSTInt16MSB ) stream_.doByteSwap[mode] = true; + } + else if ( channelInfo.type == ASIOSTInt32MSB || channelInfo.type == ASIOSTInt32LSB ) { + stream_.deviceFormat[mode] = RTAUDIO_SINT32; + if ( channelInfo.type == ASIOSTInt32MSB ) stream_.doByteSwap[mode] = true; + } + else if ( channelInfo.type == ASIOSTFloat32MSB || channelInfo.type == ASIOSTFloat32LSB ) { + stream_.deviceFormat[mode] = RTAUDIO_FLOAT32; + if ( channelInfo.type == ASIOSTFloat32MSB ) stream_.doByteSwap[mode] = true; + } + else if ( channelInfo.type == ASIOSTFloat64MSB || channelInfo.type == ASIOSTFloat64LSB ) { + stream_.deviceFormat[mode] = RTAUDIO_FLOAT64; + if ( channelInfo.type == ASIOSTFloat64MSB ) stream_.doByteSwap[mode] = true; + } + else if ( channelInfo.type == ASIOSTInt24MSB || channelInfo.type == ASIOSTInt24LSB ) { + stream_.deviceFormat[mode] = RTAUDIO_SINT24; + if ( channelInfo.type == ASIOSTInt24MSB ) stream_.doByteSwap[mode] = true; + } + + if ( stream_.deviceFormat[mode] == 0 ) { + drivers.removeCurrentDriver(); + errorStream_ << "RtApiAsio::probeDeviceOpen: driver (" << driverName << ") data format not supported by RtAudio."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Set the buffer size. For a duplex stream, this will end up + // setting the buffer size based on the input constraints, which + // should be ok. + long minSize, maxSize, preferSize, granularity; + result = ASIOGetBufferSize( &minSize, &maxSize, &preferSize, &granularity ); + if ( result != ASE_OK ) { + drivers.removeCurrentDriver(); + errorStream_ << "RtApiAsio::probeDeviceOpen: driver (" << driverName << ") error (" << getAsioErrorString( result ) << ") getting buffer size."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + if ( *bufferSize < (unsigned int) minSize ) *bufferSize = (unsigned int) minSize; + else if ( *bufferSize > (unsigned int) maxSize ) *bufferSize = (unsigned int) maxSize; + else if ( granularity == -1 ) { + // Make sure bufferSize is a power of two. + int log2_of_min_size = 0; + int log2_of_max_size = 0; + + for ( unsigned int i = 0; i < sizeof(long) * 8; i++ ) { + if ( minSize & ((long)1 << i) ) log2_of_min_size = i; + if ( maxSize & ((long)1 << i) ) log2_of_max_size = i; + } + + long min_delta = std::abs( (long)*bufferSize - ((long)1 << log2_of_min_size) ); + int min_delta_num = log2_of_min_size; + + for (int i = log2_of_min_size + 1; i <= log2_of_max_size; i++) { + long current_delta = std::abs( (long)*bufferSize - ((long)1 << i) ); + if (current_delta < min_delta) { + min_delta = current_delta; + min_delta_num = i; + } + } + + *bufferSize = ( (unsigned int)1 << min_delta_num ); + if ( *bufferSize < (unsigned int) minSize ) *bufferSize = (unsigned int) minSize; + else if ( *bufferSize > (unsigned int) maxSize ) *bufferSize = (unsigned int) maxSize; + } + else if ( granularity != 0 ) { + // Set to an even multiple of granularity, rounding up. + *bufferSize = (*bufferSize + granularity-1) / granularity * granularity; + } + + if ( mode == INPUT && stream_.mode == OUTPUT && stream_.bufferSize != *bufferSize ) { + drivers.removeCurrentDriver(); + errorText_ = "RtApiAsio::probeDeviceOpen: input/output buffersize discrepancy!"; + return FAILURE; + } + + stream_.bufferSize = *bufferSize; + stream_.nBuffers = 2; + + if ( options && options->flags & RTAUDIO_NONINTERLEAVED ) stream_.userInterleaved = false; + else stream_.userInterleaved = true; + + // ASIO always uses non-interleaved buffers. + stream_.deviceInterleaved[mode] = false; + + // Allocate, if necessary, our AsioHandle structure for the stream. + AsioHandle *handle = (AsioHandle *) stream_.apiHandle; + if ( handle == 0 ) { + try { + handle = new AsioHandle; + } + catch ( std::bad_alloc& ) { + //if ( handle == NULL ) { + drivers.removeCurrentDriver(); + errorText_ = "RtApiAsio::probeDeviceOpen: error allocating AsioHandle memory."; + return FAILURE; + } + handle->bufferInfos = 0; + + // Create a manual-reset event. + handle->condition = CreateEvent( NULL, // no security + TRUE, // manual-reset + FALSE, // non-signaled initially + NULL ); // unnamed + stream_.apiHandle = (void *) handle; + } + + // Create the ASIO internal buffers. Since RtAudio sets up input + // and output separately, we'll have to dispose of previously + // created output buffers for a duplex stream. + long inputLatency, outputLatency; + if ( mode == INPUT && stream_.mode == OUTPUT ) { + ASIODisposeBuffers(); + if ( handle->bufferInfos ) free( handle->bufferInfos ); + } + + // Allocate, initialize, and save the bufferInfos in our stream callbackInfo structure. + bool buffersAllocated = false; + unsigned int i, nChannels = stream_.nDeviceChannels[0] + stream_.nDeviceChannels[1]; + handle->bufferInfos = (ASIOBufferInfo *) malloc( nChannels * sizeof(ASIOBufferInfo) ); + if ( handle->bufferInfos == NULL ) { + errorStream_ << "RtApiAsio::probeDeviceOpen: error allocating bufferInfo memory for driver (" << driverName << ")."; + errorText_ = errorStream_.str(); + goto error; + } + + ASIOBufferInfo *infos; + infos = handle->bufferInfos; + for ( i=0; iisInput = ASIOFalse; + infos->channelNum = i + stream_.channelOffset[0]; + infos->buffers[0] = infos->buffers[1] = 0; + } + for ( i=0; iisInput = ASIOTrue; + infos->channelNum = i + stream_.channelOffset[1]; + infos->buffers[0] = infos->buffers[1] = 0; + } + + // Set up the ASIO callback structure and create the ASIO data buffers. + asioCallbacks.bufferSwitch = &bufferSwitch; + asioCallbacks.sampleRateDidChange = &sampleRateChanged; + asioCallbacks.asioMessage = &asioMessages; + asioCallbacks.bufferSwitchTimeInfo = NULL; + result = ASIOCreateBuffers( handle->bufferInfos, nChannels, stream_.bufferSize, &asioCallbacks ); + if ( result != ASE_OK ) { + errorStream_ << "RtApiAsio::probeDeviceOpen: driver (" << driverName << ") error (" << getAsioErrorString( result ) << ") creating buffers."; + errorText_ = errorStream_.str(); + goto error; + } + buffersAllocated = true; + + // Set flags for buffer conversion. + stream_.doConvertBuffer[mode] = false; + if ( stream_.userFormat != stream_.deviceFormat[mode] ) + stream_.doConvertBuffer[mode] = true; + if ( stream_.userInterleaved != stream_.deviceInterleaved[mode] && + stream_.nUserChannels[mode] > 1 ) + stream_.doConvertBuffer[mode] = true; + + // Allocate necessary internal buffers + unsigned long bufferBytes; + bufferBytes = stream_.nUserChannels[mode] * *bufferSize * formatBytes( stream_.userFormat ); + stream_.userBuffer[mode] = (char *) calloc( bufferBytes, 1 ); + if ( stream_.userBuffer[mode] == NULL ) { + errorText_ = "RtApiAsio::probeDeviceOpen: error allocating user buffer memory."; + goto error; + } + + if ( stream_.doConvertBuffer[mode] ) { + + bool makeBuffer = true; + bufferBytes = stream_.nDeviceChannels[mode] * formatBytes( stream_.deviceFormat[mode] ); + if ( mode == INPUT ) { + if ( stream_.mode == OUTPUT && stream_.deviceBuffer ) { + unsigned long bytesOut = stream_.nDeviceChannels[0] * formatBytes( stream_.deviceFormat[0] ); + if ( bufferBytes <= bytesOut ) makeBuffer = false; + } + } + + if ( makeBuffer ) { + bufferBytes *= *bufferSize; + if ( stream_.deviceBuffer ) free( stream_.deviceBuffer ); + stream_.deviceBuffer = (char *) calloc( bufferBytes, 1 ); + if ( stream_.deviceBuffer == NULL ) { + errorText_ = "RtApiAsio::probeDeviceOpen: error allocating device buffer memory."; + goto error; + } + } + } + + stream_.sampleRate = sampleRate; + stream_.device[mode] = device; + stream_.state = STREAM_STOPPED; + asioCallbackInfo = &stream_.callbackInfo; + stream_.callbackInfo.object = (void *) this; + if ( stream_.mode == OUTPUT && mode == INPUT ) + // We had already set up an output stream. + stream_.mode = DUPLEX; + else + stream_.mode = mode; + + // Determine device latencies + result = ASIOGetLatencies( &inputLatency, &outputLatency ); + if ( result != ASE_OK ) { + errorStream_ << "RtApiAsio::probeDeviceOpen: driver (" << driverName << ") error (" << getAsioErrorString( result ) << ") getting latency."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING); // warn but don't fail + } + else { + stream_.latency[0] = outputLatency; + stream_.latency[1] = inputLatency; + } + + // Setup the buffer conversion information structure. We don't use + // buffers to do channel offsets, so we override that parameter + // here. + if ( stream_.doConvertBuffer[mode] ) setConvertInfo( mode, 0 ); + + return SUCCESS; + + error: + if ( buffersAllocated ) + ASIODisposeBuffers(); + drivers.removeCurrentDriver(); + + if ( handle ) { + CloseHandle( handle->condition ); + if ( handle->bufferInfos ) + free( handle->bufferInfos ); + delete handle; + stream_.apiHandle = 0; + } + + for ( int i=0; i<2; i++ ) { + if ( stream_.userBuffer[i] ) { + free( stream_.userBuffer[i] ); + stream_.userBuffer[i] = 0; + } + } + + if ( stream_.deviceBuffer ) { + free( stream_.deviceBuffer ); + stream_.deviceBuffer = 0; + } + + return FAILURE; +} + +void RtApiAsio :: closeStream() +{ + if ( stream_.state == STREAM_CLOSED ) { + errorText_ = "RtApiAsio::closeStream(): no open stream to close!"; + error( RtAudioError::WARNING ); + return; + } + + if ( stream_.state == STREAM_RUNNING ) { + stream_.state = STREAM_STOPPED; + ASIOStop(); + } + ASIODisposeBuffers(); + drivers.removeCurrentDriver(); + + AsioHandle *handle = (AsioHandle *) stream_.apiHandle; + if ( handle ) { + CloseHandle( handle->condition ); + if ( handle->bufferInfos ) + free( handle->bufferInfos ); + delete handle; + stream_.apiHandle = 0; + } + + for ( int i=0; i<2; i++ ) { + if ( stream_.userBuffer[i] ) { + free( stream_.userBuffer[i] ); + stream_.userBuffer[i] = 0; + } + } + + if ( stream_.deviceBuffer ) { + free( stream_.deviceBuffer ); + stream_.deviceBuffer = 0; + } + + stream_.mode = UNINITIALIZED; + stream_.state = STREAM_CLOSED; +} + +bool stopThreadCalled = false; + +void RtApiAsio :: startStream() +{ + verifyStream(); + if ( stream_.state == STREAM_RUNNING ) { + errorText_ = "RtApiAsio::startStream(): the stream is already running!"; + error( RtAudioError::WARNING ); + return; + } + + AsioHandle *handle = (AsioHandle *) stream_.apiHandle; + ASIOError result = ASIOStart(); + if ( result != ASE_OK ) { + errorStream_ << "RtApiAsio::startStream: error (" << getAsioErrorString( result ) << ") starting device."; + errorText_ = errorStream_.str(); + goto unlock; + } + + handle->drainCounter = 0; + handle->internalDrain = false; + ResetEvent( handle->condition ); + stream_.state = STREAM_RUNNING; + asioXRun = false; + + unlock: + stopThreadCalled = false; + + if ( result == ASE_OK ) return; + error( RtAudioError::SYSTEM_ERROR ); +} + +void RtApiAsio :: stopStream() +{ + verifyStream(); + if ( stream_.state == STREAM_STOPPED ) { + errorText_ = "RtApiAsio::stopStream(): the stream is already stopped!"; + error( RtAudioError::WARNING ); + return; + } + + AsioHandle *handle = (AsioHandle *) stream_.apiHandle; + if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { + if ( handle->drainCounter == 0 ) { + handle->drainCounter = 2; + WaitForSingleObject( handle->condition, INFINITE ); // block until signaled + } + } + + stream_.state = STREAM_STOPPED; + + ASIOError result = ASIOStop(); + if ( result != ASE_OK ) { + errorStream_ << "RtApiAsio::stopStream: error (" << getAsioErrorString( result ) << ") stopping device."; + errorText_ = errorStream_.str(); + } + + if ( result == ASE_OK ) return; + error( RtAudioError::SYSTEM_ERROR ); +} + +void RtApiAsio :: abortStream() +{ + verifyStream(); + if ( stream_.state == STREAM_STOPPED ) { + errorText_ = "RtApiAsio::abortStream(): the stream is already stopped!"; + error( RtAudioError::WARNING ); + return; + } + + // The following lines were commented-out because some behavior was + // noted where the device buffers need to be zeroed to avoid + // continuing sound, even when the device buffers are completely + // disposed. So now, calling abort is the same as calling stop. + // AsioHandle *handle = (AsioHandle *) stream_.apiHandle; + // handle->drainCounter = 2; + stopStream(); +} + +// This function will be called by a spawned thread when the user +// callback function signals that the stream should be stopped or +// aborted. It is necessary to handle it this way because the +// callbackEvent() function must return before the ASIOStop() +// function will return. +static unsigned __stdcall asioStopStream( void *ptr ) +{ + CallbackInfo *info = (CallbackInfo *) ptr; + RtApiAsio *object = (RtApiAsio *) info->object; + + object->stopStream(); + _endthreadex( 0 ); + return 0; +} + +bool RtApiAsio :: callbackEvent( long bufferIndex ) +{ + if ( stream_.state == STREAM_STOPPED || stream_.state == STREAM_STOPPING ) return SUCCESS; + if ( stream_.state == STREAM_CLOSED ) { + errorText_ = "RtApiAsio::callbackEvent(): the stream is closed ... this shouldn't happen!"; + error( RtAudioError::WARNING ); + return FAILURE; + } + + CallbackInfo *info = (CallbackInfo *) &stream_.callbackInfo; + AsioHandle *handle = (AsioHandle *) stream_.apiHandle; + + // Check if we were draining the stream and signal if finished. + if ( handle->drainCounter > 3 ) { + + stream_.state = STREAM_STOPPING; + if ( handle->internalDrain == false ) + SetEvent( handle->condition ); + else { // spawn a thread to stop the stream + unsigned threadId; + stream_.callbackInfo.thread = _beginthreadex( NULL, 0, &asioStopStream, + &stream_.callbackInfo, 0, &threadId ); + } + return SUCCESS; + } + + // Invoke user callback to get fresh output data UNLESS we are + // draining stream. + if ( handle->drainCounter == 0 ) { + RtAudioCallback callback = (RtAudioCallback) info->callback; + double streamTime = getStreamTime(); + RtAudioStreamStatus status = 0; + if ( stream_.mode != INPUT && asioXRun == true ) { + status |= RTAUDIO_OUTPUT_UNDERFLOW; + asioXRun = false; + } + if ( stream_.mode != OUTPUT && asioXRun == true ) { + status |= RTAUDIO_INPUT_OVERFLOW; + asioXRun = false; + } + int cbReturnValue = callback( stream_.userBuffer[0], stream_.userBuffer[1], + stream_.bufferSize, streamTime, status, info->userData ); + if ( cbReturnValue == 2 ) { + stream_.state = STREAM_STOPPING; + handle->drainCounter = 2; + unsigned threadId; + stream_.callbackInfo.thread = _beginthreadex( NULL, 0, &asioStopStream, + &stream_.callbackInfo, 0, &threadId ); + return SUCCESS; + } + else if ( cbReturnValue == 1 ) { + handle->drainCounter = 1; + handle->internalDrain = true; + } + } + + unsigned int nChannels, bufferBytes, i, j; + nChannels = stream_.nDeviceChannels[0] + stream_.nDeviceChannels[1]; + if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { + + bufferBytes = stream_.bufferSize * formatBytes( stream_.deviceFormat[0] ); + + if ( handle->drainCounter > 1 ) { // write zeros to the output stream + + for ( i=0, j=0; ibufferInfos[i].isInput != ASIOTrue ) + memset( handle->bufferInfos[i].buffers[bufferIndex], 0, bufferBytes ); + } + + } + else if ( stream_.doConvertBuffer[0] ) { + + convertBuffer( stream_.deviceBuffer, stream_.userBuffer[0], stream_.convertInfo[0] ); + if ( stream_.doByteSwap[0] ) + byteSwapBuffer( stream_.deviceBuffer, + stream_.bufferSize * stream_.nDeviceChannels[0], + stream_.deviceFormat[0] ); + + for ( i=0, j=0; ibufferInfos[i].isInput != ASIOTrue ) + memcpy( handle->bufferInfos[i].buffers[bufferIndex], + &stream_.deviceBuffer[j++*bufferBytes], bufferBytes ); + } + + } + else { + + if ( stream_.doByteSwap[0] ) + byteSwapBuffer( stream_.userBuffer[0], + stream_.bufferSize * stream_.nUserChannels[0], + stream_.userFormat ); + + for ( i=0, j=0; ibufferInfos[i].isInput != ASIOTrue ) + memcpy( handle->bufferInfos[i].buffers[bufferIndex], + &stream_.userBuffer[0][bufferBytes*j++], bufferBytes ); + } + + } + } + + // Don't bother draining input + if ( handle->drainCounter ) { + handle->drainCounter++; + goto unlock; + } + + if ( stream_.mode == INPUT || stream_.mode == DUPLEX ) { + + bufferBytes = stream_.bufferSize * formatBytes(stream_.deviceFormat[1]); + + if (stream_.doConvertBuffer[1]) { + + // Always interleave ASIO input data. + for ( i=0, j=0; ibufferInfos[i].isInput == ASIOTrue ) + memcpy( &stream_.deviceBuffer[j++*bufferBytes], + handle->bufferInfos[i].buffers[bufferIndex], + bufferBytes ); + } + + if ( stream_.doByteSwap[1] ) + byteSwapBuffer( stream_.deviceBuffer, + stream_.bufferSize * stream_.nDeviceChannels[1], + stream_.deviceFormat[1] ); + convertBuffer( stream_.userBuffer[1], stream_.deviceBuffer, stream_.convertInfo[1] ); + + } + else { + for ( i=0, j=0; ibufferInfos[i].isInput == ASIOTrue ) { + memcpy( &stream_.userBuffer[1][bufferBytes*j++], + handle->bufferInfos[i].buffers[bufferIndex], + bufferBytes ); + } + } + + if ( stream_.doByteSwap[1] ) + byteSwapBuffer( stream_.userBuffer[1], + stream_.bufferSize * stream_.nUserChannels[1], + stream_.userFormat ); + } + } + + unlock: + // The following call was suggested by Malte Clasen. While the API + // documentation indicates it should not be required, some device + // drivers apparently do not function correctly without it. + ASIOOutputReady(); + + RtApi::tickStreamTime(); + return SUCCESS; +} + +static void sampleRateChanged( ASIOSampleRate sRate ) +{ + // The ASIO documentation says that this usually only happens during + // external sync. Audio processing is not stopped by the driver, + // actual sample rate might not have even changed, maybe only the + // sample rate status of an AES/EBU or S/PDIF digital input at the + // audio device. + + RtApi *object = (RtApi *) asioCallbackInfo->object; + try { + object->stopStream(); + } + catch ( RtAudioError &exception ) { + std::cerr << "\nRtApiAsio: sampleRateChanged() error (" << exception.getMessage() << ")!\n" << std::endl; + return; + } + + std::cerr << "\nRtApiAsio: driver reports sample rate changed to " << sRate << " ... stream stopped!!!\n" << std::endl; +} + +static long asioMessages( long selector, long value, void* /*message*/, double* /*opt*/ ) +{ + long ret = 0; + + switch( selector ) { + case kAsioSelectorSupported: + if ( value == kAsioResetRequest + || value == kAsioEngineVersion + || value == kAsioResyncRequest + || value == kAsioLatenciesChanged + // The following three were added for ASIO 2.0, you don't + // necessarily have to support them. + || value == kAsioSupportsTimeInfo + || value == kAsioSupportsTimeCode + || value == kAsioSupportsInputMonitor) + ret = 1L; + break; + case kAsioResetRequest: + // Defer the task and perform the reset of the driver during the + // next "safe" situation. You cannot reset the driver right now, + // as this code is called from the driver. Reset the driver is + // done by completely destruct is. I.e. ASIOStop(), + // ASIODisposeBuffers(), Destruction Afterwards you initialize the + // driver again. + std::cerr << "\nRtApiAsio: driver reset requested!!!" << std::endl; + ret = 1L; + break; + case kAsioResyncRequest: + // This informs the application that the driver encountered some + // non-fatal data loss. It is used for synchronization purposes + // of different media. Added mainly to work around the Win16Mutex + // problems in Windows 95/98 with the Windows Multimedia system, + // which could lose data because the Mutex was held too long by + // another thread. However a driver can issue it in other + // situations, too. + // std::cerr << "\nRtApiAsio: driver resync requested!!!" << std::endl; + asioXRun = true; + ret = 1L; + break; + case kAsioLatenciesChanged: + // This will inform the host application that the drivers were + // latencies changed. Beware, it this does not mean that the + // buffer sizes have changed! You might need to update internal + // delay data. + std::cerr << "\nRtApiAsio: driver latency may have changed!!!" << std::endl; + ret = 1L; + break; + case kAsioEngineVersion: + // Return the supported ASIO version of the host application. If + // a host application does not implement this selector, ASIO 1.0 + // is assumed by the driver. + ret = 2L; + break; + case kAsioSupportsTimeInfo: + // Informs the driver whether the + // asioCallbacks.bufferSwitchTimeInfo() callback is supported. + // For compatibility with ASIO 1.0 drivers the host application + // should always support the "old" bufferSwitch method, too. + ret = 0; + break; + case kAsioSupportsTimeCode: + // Informs the driver whether application is interested in time + // code info. If an application does not need to know about time + // code, the driver has less work to do. + ret = 0; + break; + } + return ret; +} + +static const char* getAsioErrorString( ASIOError result ) +{ + struct Messages + { + ASIOError value; + const char*message; + }; + + static const Messages m[] = + { + { ASE_NotPresent, "Hardware input or output is not present or available." }, + { ASE_HWMalfunction, "Hardware is malfunctioning." }, + { ASE_InvalidParameter, "Invalid input parameter." }, + { ASE_InvalidMode, "Invalid mode." }, + { ASE_SPNotAdvancing, "Sample position not advancing." }, + { ASE_NoClock, "Sample clock or rate cannot be determined or is not present." }, + { ASE_NoMemory, "Not enough memory to complete the request." } + }; + + for ( unsigned int i = 0; i < sizeof(m)/sizeof(m[0]); ++i ) + if ( m[i].value == result ) return m[i].message; + + return "Unknown error."; +} + +//******************** End of __WINDOWS_ASIO__ *********************// +#endif + + +#if defined(__WINDOWS_WASAPI__) // Windows WASAPI API + +// Authored by Marcus Tomlinson , April 2014 +// - Introduces support for the Windows WASAPI API +// - Aims to deliver bit streams to and from hardware at the lowest possible latency, via the absolute minimum buffer sizes required +// - Provides flexible stream configuration to an otherwise strict and inflexible WASAPI interface +// - Includes automatic internal conversion of sample rate and buffer size between hardware and the user + +#ifndef INITGUID + #define INITGUID +#endif +#include +#include +#include +#include + +//============================================================================= + +#define SAFE_RELEASE( objectPtr )\ +if ( objectPtr )\ +{\ + objectPtr->Release();\ + objectPtr = NULL;\ +} + +typedef HANDLE ( __stdcall *TAvSetMmThreadCharacteristicsPtr )( LPCWSTR TaskName, LPDWORD TaskIndex ); + +//----------------------------------------------------------------------------- + +// WASAPI dictates stream sample rate, format, channel count, and in some cases, buffer size. +// Therefore we must perform all necessary conversions to user buffers in order to satisfy these +// requirements. WasapiBuffer ring buffers are used between HwIn->UserIn and UserOut->HwOut to +// provide intermediate storage for read / write synchronization. +class WasapiBuffer +{ +public: + WasapiBuffer() + : buffer_( NULL ), + bufferSize_( 0 ), + inIndex_( 0 ), + outIndex_( 0 ) {} + + ~WasapiBuffer() { + delete buffer_; + } + + // sets the length of the internal ring buffer + void setBufferSize( unsigned int bufferSize, unsigned int formatBytes ) { + delete buffer_; + + buffer_ = ( char* ) calloc( bufferSize, formatBytes ); + + bufferSize_ = bufferSize; + inIndex_ = 0; + outIndex_ = 0; + } + + // attempt to push a buffer into the ring buffer at the current "in" index + bool pushBuffer( char* buffer, unsigned int bufferSize, RtAudioFormat format ) + { + if ( !buffer || // incoming buffer is NULL + bufferSize == 0 || // incoming buffer has no data + bufferSize > bufferSize_ ) // incoming buffer too large + { + return false; + } + + unsigned int relOutIndex = outIndex_; + unsigned int inIndexEnd = inIndex_ + bufferSize; + if ( relOutIndex < inIndex_ && inIndexEnd >= bufferSize_ ) { + relOutIndex += bufferSize_; + } + + // "in" index can end on the "out" index but cannot begin at it + if ( inIndex_ <= relOutIndex && inIndexEnd > relOutIndex ) { + return false; // not enough space between "in" index and "out" index + } + + // copy buffer from external to internal + int fromZeroSize = inIndex_ + bufferSize - bufferSize_; + fromZeroSize = fromZeroSize < 0 ? 0 : fromZeroSize; + int fromInSize = bufferSize - fromZeroSize; + + switch( format ) + { + case RTAUDIO_SINT8: + memcpy( &( ( char* ) buffer_ )[inIndex_], buffer, fromInSize * sizeof( char ) ); + memcpy( buffer_, &( ( char* ) buffer )[fromInSize], fromZeroSize * sizeof( char ) ); + break; + case RTAUDIO_SINT16: + memcpy( &( ( short* ) buffer_ )[inIndex_], buffer, fromInSize * sizeof( short ) ); + memcpy( buffer_, &( ( short* ) buffer )[fromInSize], fromZeroSize * sizeof( short ) ); + break; + case RTAUDIO_SINT24: + memcpy( &( ( S24* ) buffer_ )[inIndex_], buffer, fromInSize * sizeof( S24 ) ); + memcpy( buffer_, &( ( S24* ) buffer )[fromInSize], fromZeroSize * sizeof( S24 ) ); + break; + case RTAUDIO_SINT32: + memcpy( &( ( int* ) buffer_ )[inIndex_], buffer, fromInSize * sizeof( int ) ); + memcpy( buffer_, &( ( int* ) buffer )[fromInSize], fromZeroSize * sizeof( int ) ); + break; + case RTAUDIO_FLOAT32: + memcpy( &( ( float* ) buffer_ )[inIndex_], buffer, fromInSize * sizeof( float ) ); + memcpy( buffer_, &( ( float* ) buffer )[fromInSize], fromZeroSize * sizeof( float ) ); + break; + case RTAUDIO_FLOAT64: + memcpy( &( ( double* ) buffer_ )[inIndex_], buffer, fromInSize * sizeof( double ) ); + memcpy( buffer_, &( ( double* ) buffer )[fromInSize], fromZeroSize * sizeof( double ) ); + break; + } + + // update "in" index + inIndex_ += bufferSize; + inIndex_ %= bufferSize_; + + return true; + } + + // attempt to pull a buffer from the ring buffer from the current "out" index + bool pullBuffer( char* buffer, unsigned int bufferSize, RtAudioFormat format ) + { + if ( !buffer || // incoming buffer is NULL + bufferSize == 0 || // incoming buffer has no data + bufferSize > bufferSize_ ) // incoming buffer too large + { + return false; + } + + unsigned int relInIndex = inIndex_; + unsigned int outIndexEnd = outIndex_ + bufferSize; + if ( relInIndex < outIndex_ && outIndexEnd >= bufferSize_ ) { + relInIndex += bufferSize_; + } + + // "out" index can begin at and end on the "in" index + if ( outIndex_ < relInIndex && outIndexEnd > relInIndex ) { + return false; // not enough space between "out" index and "in" index + } + + // copy buffer from internal to external + int fromZeroSize = outIndex_ + bufferSize - bufferSize_; + fromZeroSize = fromZeroSize < 0 ? 0 : fromZeroSize; + int fromOutSize = bufferSize - fromZeroSize; + + switch( format ) + { + case RTAUDIO_SINT8: + memcpy( buffer, &( ( char* ) buffer_ )[outIndex_], fromOutSize * sizeof( char ) ); + memcpy( &( ( char* ) buffer )[fromOutSize], buffer_, fromZeroSize * sizeof( char ) ); + break; + case RTAUDIO_SINT16: + memcpy( buffer, &( ( short* ) buffer_ )[outIndex_], fromOutSize * sizeof( short ) ); + memcpy( &( ( short* ) buffer )[fromOutSize], buffer_, fromZeroSize * sizeof( short ) ); + break; + case RTAUDIO_SINT24: + memcpy( buffer, &( ( S24* ) buffer_ )[outIndex_], fromOutSize * sizeof( S24 ) ); + memcpy( &( ( S24* ) buffer )[fromOutSize], buffer_, fromZeroSize * sizeof( S24 ) ); + break; + case RTAUDIO_SINT32: + memcpy( buffer, &( ( int* ) buffer_ )[outIndex_], fromOutSize * sizeof( int ) ); + memcpy( &( ( int* ) buffer )[fromOutSize], buffer_, fromZeroSize * sizeof( int ) ); + break; + case RTAUDIO_FLOAT32: + memcpy( buffer, &( ( float* ) buffer_ )[outIndex_], fromOutSize * sizeof( float ) ); + memcpy( &( ( float* ) buffer )[fromOutSize], buffer_, fromZeroSize * sizeof( float ) ); + break; + case RTAUDIO_FLOAT64: + memcpy( buffer, &( ( double* ) buffer_ )[outIndex_], fromOutSize * sizeof( double ) ); + memcpy( &( ( double* ) buffer )[fromOutSize], buffer_, fromZeroSize * sizeof( double ) ); + break; + } + + // update "out" index + outIndex_ += bufferSize; + outIndex_ %= bufferSize_; + + return true; + } + +private: + char* buffer_; + unsigned int bufferSize_; + unsigned int inIndex_; + unsigned int outIndex_; +}; + +//----------------------------------------------------------------------------- + +// In order to satisfy WASAPI's buffer requirements, we need a means of converting sample rate +// between HW and the user. The convertBufferWasapi function is used to perform this conversion +// between HwIn->UserIn and UserOut->HwOut during the stream callback loop. +// This sample rate converter favors speed over quality, and works best with conversions between +// one rate and its multiple. +void convertBufferWasapi( char* outBuffer, + const char* inBuffer, + const unsigned int& channelCount, + const unsigned int& inSampleRate, + const unsigned int& outSampleRate, + const unsigned int& inSampleCount, + unsigned int& outSampleCount, + const RtAudioFormat& format ) +{ + // calculate the new outSampleCount and relative sampleStep + float sampleRatio = ( float ) outSampleRate / inSampleRate; + float sampleStep = 1.0f / sampleRatio; + float inSampleFraction = 0.0f; + + outSampleCount = ( unsigned int ) ( inSampleCount * sampleRatio ); + + // frame-by-frame, copy each relative input sample into it's corresponding output sample + for ( unsigned int outSample = 0; outSample < outSampleCount; outSample++ ) + { + unsigned int inSample = ( unsigned int ) inSampleFraction; + + switch ( format ) + { + case RTAUDIO_SINT8: + memcpy( &( ( char* ) outBuffer )[ outSample * channelCount ], &( ( char* ) inBuffer )[ inSample * channelCount ], channelCount * sizeof( char ) ); + break; + case RTAUDIO_SINT16: + memcpy( &( ( short* ) outBuffer )[ outSample * channelCount ], &( ( short* ) inBuffer )[ inSample * channelCount ], channelCount * sizeof( short ) ); + break; + case RTAUDIO_SINT24: + memcpy( &( ( S24* ) outBuffer )[ outSample * channelCount ], &( ( S24* ) inBuffer )[ inSample * channelCount ], channelCount * sizeof( S24 ) ); + break; + case RTAUDIO_SINT32: + memcpy( &( ( int* ) outBuffer )[ outSample * channelCount ], &( ( int* ) inBuffer )[ inSample * channelCount ], channelCount * sizeof( int ) ); + break; + case RTAUDIO_FLOAT32: + memcpy( &( ( float* ) outBuffer )[ outSample * channelCount ], &( ( float* ) inBuffer )[ inSample * channelCount ], channelCount * sizeof( float ) ); + break; + case RTAUDIO_FLOAT64: + memcpy( &( ( double* ) outBuffer )[ outSample * channelCount ], &( ( double* ) inBuffer )[ inSample * channelCount ], channelCount * sizeof( double ) ); + break; + } + + // jump to next in sample + inSampleFraction += sampleStep; + } +} + +//----------------------------------------------------------------------------- + +// A structure to hold various information related to the WASAPI implementation. +struct WasapiHandle +{ + IAudioClient* captureAudioClient; + IAudioClient* renderAudioClient; + IAudioCaptureClient* captureClient; + IAudioRenderClient* renderClient; + HANDLE captureEvent; + HANDLE renderEvent; + + WasapiHandle() + : captureAudioClient( NULL ), + renderAudioClient( NULL ), + captureClient( NULL ), + renderClient( NULL ), + captureEvent( NULL ), + renderEvent( NULL ) {} +}; + +//============================================================================= + +RtApiWasapi::RtApiWasapi() + : coInitialized_( false ), deviceEnumerator_( NULL ) +{ + // WASAPI can run either apartment or multi-threaded + HRESULT hr = CoInitialize( NULL ); + if ( !FAILED( hr ) ) + coInitialized_ = true; + + // Instantiate device enumerator + hr = CoCreateInstance( __uuidof( MMDeviceEnumerator ), NULL, + CLSCTX_ALL, __uuidof( IMMDeviceEnumerator ), + ( void** ) &deviceEnumerator_ ); + + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::RtApiWasapi: Unable to instantiate device enumerator"; + error( RtAudioError::DRIVER_ERROR ); + } +} + +//----------------------------------------------------------------------------- + +RtApiWasapi::~RtApiWasapi() +{ + if ( stream_.state != STREAM_CLOSED ) + closeStream(); + + SAFE_RELEASE( deviceEnumerator_ ); + + // If this object previously called CoInitialize() + if ( coInitialized_ ) + CoUninitialize(); +} + +//============================================================================= + +unsigned int RtApiWasapi::getDeviceCount( void ) +{ + unsigned int captureDeviceCount = 0; + unsigned int renderDeviceCount = 0; + + IMMDeviceCollection* captureDevices = NULL; + IMMDeviceCollection* renderDevices = NULL; + + // Count capture devices + errorText_.clear(); + HRESULT hr = deviceEnumerator_->EnumAudioEndpoints( eCapture, DEVICE_STATE_ACTIVE, &captureDevices ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::getDeviceCount: Unable to retrieve capture device collection."; + goto Exit; + } + + hr = captureDevices->GetCount( &captureDeviceCount ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::getDeviceCount: Unable to retrieve capture device count."; + goto Exit; + } + + // Count render devices + hr = deviceEnumerator_->EnumAudioEndpoints( eRender, DEVICE_STATE_ACTIVE, &renderDevices ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::getDeviceCount: Unable to retrieve render device collection."; + goto Exit; + } + + hr = renderDevices->GetCount( &renderDeviceCount ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::getDeviceCount: Unable to retrieve render device count."; + goto Exit; + } + +Exit: + // release all references + SAFE_RELEASE( captureDevices ); + SAFE_RELEASE( renderDevices ); + + if ( errorText_.empty() ) + return captureDeviceCount + renderDeviceCount; + + error( RtAudioError::DRIVER_ERROR ); + return 0; +} + +//----------------------------------------------------------------------------- + +RtAudio::DeviceInfo RtApiWasapi::getDeviceInfo( unsigned int device ) +{ + RtAudio::DeviceInfo info; + unsigned int captureDeviceCount = 0; + unsigned int renderDeviceCount = 0; + std::wstring deviceName; + std::string defaultDeviceName; + bool isCaptureDevice = false; + + PROPVARIANT deviceNameProp; + PROPVARIANT defaultDeviceNameProp; + + IMMDeviceCollection* captureDevices = NULL; + IMMDeviceCollection* renderDevices = NULL; + IMMDevice* devicePtr = NULL; + IMMDevice* defaultDevicePtr = NULL; + IAudioClient* audioClient = NULL; + IPropertyStore* devicePropStore = NULL; + IPropertyStore* defaultDevicePropStore = NULL; + + WAVEFORMATEX* deviceFormat = NULL; + WAVEFORMATEX* closestMatchFormat = NULL; + + // probed + info.probed = false; + + // Count capture devices + errorText_.clear(); + RtAudioError::Type errorType = RtAudioError::DRIVER_ERROR; + HRESULT hr = deviceEnumerator_->EnumAudioEndpoints( eCapture, DEVICE_STATE_ACTIVE, &captureDevices ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve capture device collection."; + goto Exit; + } + + hr = captureDevices->GetCount( &captureDeviceCount ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve capture device count."; + goto Exit; + } + + // Count render devices + hr = deviceEnumerator_->EnumAudioEndpoints( eRender, DEVICE_STATE_ACTIVE, &renderDevices ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve render device collection."; + goto Exit; + } + + hr = renderDevices->GetCount( &renderDeviceCount ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve render device count."; + goto Exit; + } + + // validate device index + if ( device >= captureDeviceCount + renderDeviceCount ) { + errorText_ = "RtApiWasapi::getDeviceInfo: Invalid device index."; + errorType = RtAudioError::INVALID_USE; + goto Exit; + } + + // determine whether index falls within capture or render devices + if ( device >= renderDeviceCount ) { + hr = captureDevices->Item( device - renderDeviceCount, &devicePtr ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve capture device handle."; + goto Exit; + } + isCaptureDevice = true; + } + else { + hr = renderDevices->Item( device, &devicePtr ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve render device handle."; + goto Exit; + } + isCaptureDevice = false; + } + + // get default device name + if ( isCaptureDevice ) { + hr = deviceEnumerator_->GetDefaultAudioEndpoint( eCapture, eConsole, &defaultDevicePtr ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve default capture device handle."; + goto Exit; + } + } + else { + hr = deviceEnumerator_->GetDefaultAudioEndpoint( eRender, eConsole, &defaultDevicePtr ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve default render device handle."; + goto Exit; + } + } + + hr = defaultDevicePtr->OpenPropertyStore( STGM_READ, &defaultDevicePropStore ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::getDeviceInfo: Unable to open default device property store."; + goto Exit; + } + PropVariantInit( &defaultDeviceNameProp ); + + hr = defaultDevicePropStore->GetValue( PKEY_Device_FriendlyName, &defaultDeviceNameProp ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve default device property: PKEY_Device_FriendlyName."; + goto Exit; + } + + deviceName = defaultDeviceNameProp.pwszVal; + defaultDeviceName = std::string( deviceName.begin(), deviceName.end() ); + + // name + hr = devicePtr->OpenPropertyStore( STGM_READ, &devicePropStore ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::getDeviceInfo: Unable to open device property store."; + goto Exit; + } + + PropVariantInit( &deviceNameProp ); + + hr = devicePropStore->GetValue( PKEY_Device_FriendlyName, &deviceNameProp ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve device property: PKEY_Device_FriendlyName."; + goto Exit; + } + + deviceName = deviceNameProp.pwszVal; + info.name = std::string( deviceName.begin(), deviceName.end() ); + + // is default + if ( isCaptureDevice ) { + info.isDefaultInput = info.name == defaultDeviceName; + info.isDefaultOutput = false; + } + else { + info.isDefaultInput = false; + info.isDefaultOutput = info.name == defaultDeviceName; + } + + // channel count + hr = devicePtr->Activate( __uuidof( IAudioClient ), CLSCTX_ALL, NULL, ( void** ) &audioClient ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve device audio client."; + goto Exit; + } + + hr = audioClient->GetMixFormat( &deviceFormat ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve device mix format."; + goto Exit; + } + + if ( isCaptureDevice ) { + info.inputChannels = deviceFormat->nChannels; + info.outputChannels = 0; + info.duplexChannels = 0; + } + else { + info.inputChannels = 0; + info.outputChannels = deviceFormat->nChannels; + info.duplexChannels = 0; + } + + // sample rates + info.sampleRates.clear(); + + // allow support for all sample rates as we have a built-in sample rate converter + for ( unsigned int i = 0; i < MAX_SAMPLE_RATES; i++ ) { + info.sampleRates.push_back( SAMPLE_RATES[i] ); + } + + // native format + info.nativeFormats = 0; + + if ( deviceFormat->wFormatTag == WAVE_FORMAT_IEEE_FLOAT || + ( deviceFormat->wFormatTag == WAVE_FORMAT_EXTENSIBLE && + ( ( WAVEFORMATEXTENSIBLE* ) deviceFormat )->SubFormat == KSDATAFORMAT_SUBTYPE_IEEE_FLOAT ) ) + { + if ( deviceFormat->wBitsPerSample == 32 ) { + info.nativeFormats |= RTAUDIO_FLOAT32; + } + else if ( deviceFormat->wBitsPerSample == 64 ) { + info.nativeFormats |= RTAUDIO_FLOAT64; + } + } + else if ( deviceFormat->wFormatTag == WAVE_FORMAT_PCM || + ( deviceFormat->wFormatTag == WAVE_FORMAT_EXTENSIBLE && + ( ( WAVEFORMATEXTENSIBLE* ) deviceFormat )->SubFormat == KSDATAFORMAT_SUBTYPE_PCM ) ) + { + if ( deviceFormat->wBitsPerSample == 8 ) { + info.nativeFormats |= RTAUDIO_SINT8; + } + else if ( deviceFormat->wBitsPerSample == 16 ) { + info.nativeFormats |= RTAUDIO_SINT16; + } + else if ( deviceFormat->wBitsPerSample == 24 ) { + info.nativeFormats |= RTAUDIO_SINT24; + } + else if ( deviceFormat->wBitsPerSample == 32 ) { + info.nativeFormats |= RTAUDIO_SINT32; + } + } + + // probed + info.probed = true; + +Exit: + // release all references + PropVariantClear( &deviceNameProp ); + PropVariantClear( &defaultDeviceNameProp ); + + SAFE_RELEASE( captureDevices ); + SAFE_RELEASE( renderDevices ); + SAFE_RELEASE( devicePtr ); + SAFE_RELEASE( defaultDevicePtr ); + SAFE_RELEASE( audioClient ); + SAFE_RELEASE( devicePropStore ); + SAFE_RELEASE( defaultDevicePropStore ); + + CoTaskMemFree( deviceFormat ); + CoTaskMemFree( closestMatchFormat ); + + if ( !errorText_.empty() ) + error( errorType ); + return info; +} + +//----------------------------------------------------------------------------- + +unsigned int RtApiWasapi::getDefaultOutputDevice( void ) +{ + for ( unsigned int i = 0; i < getDeviceCount(); i++ ) { + if ( getDeviceInfo( i ).isDefaultOutput ) { + return i; + } + } + + return 0; +} + +//----------------------------------------------------------------------------- + +unsigned int RtApiWasapi::getDefaultInputDevice( void ) +{ + for ( unsigned int i = 0; i < getDeviceCount(); i++ ) { + if ( getDeviceInfo( i ).isDefaultInput ) { + return i; + } + } + + return 0; +} + +//----------------------------------------------------------------------------- + +void RtApiWasapi::closeStream( void ) +{ + if ( stream_.state == STREAM_CLOSED ) { + errorText_ = "RtApiWasapi::closeStream: No open stream to close."; + error( RtAudioError::WARNING ); + return; + } + + if ( stream_.state != STREAM_STOPPED ) + stopStream(); + + // clean up stream memory + SAFE_RELEASE( ( ( WasapiHandle* ) stream_.apiHandle )->captureAudioClient ) + SAFE_RELEASE( ( ( WasapiHandle* ) stream_.apiHandle )->renderAudioClient ) + + SAFE_RELEASE( ( ( WasapiHandle* ) stream_.apiHandle )->captureClient ) + SAFE_RELEASE( ( ( WasapiHandle* ) stream_.apiHandle )->renderClient ) + + if ( ( ( WasapiHandle* ) stream_.apiHandle )->captureEvent ) + CloseHandle( ( ( WasapiHandle* ) stream_.apiHandle )->captureEvent ); + + if ( ( ( WasapiHandle* ) stream_.apiHandle )->renderEvent ) + CloseHandle( ( ( WasapiHandle* ) stream_.apiHandle )->renderEvent ); + + delete ( WasapiHandle* ) stream_.apiHandle; + stream_.apiHandle = NULL; + + for ( int i = 0; i < 2; i++ ) { + if ( stream_.userBuffer[i] ) { + free( stream_.userBuffer[i] ); + stream_.userBuffer[i] = 0; + } + } + + if ( stream_.deviceBuffer ) { + free( stream_.deviceBuffer ); + stream_.deviceBuffer = 0; + } + + // update stream state + stream_.state = STREAM_CLOSED; +} + +//----------------------------------------------------------------------------- + +void RtApiWasapi::startStream( void ) +{ + verifyStream(); + + if ( stream_.state == STREAM_RUNNING ) { + errorText_ = "RtApiWasapi::startStream: The stream is already running."; + error( RtAudioError::WARNING ); + return; + } + + // update stream state + stream_.state = STREAM_RUNNING; + + // create WASAPI stream thread + stream_.callbackInfo.thread = ( ThreadHandle ) CreateThread( NULL, 0, runWasapiThread, this, CREATE_SUSPENDED, NULL ); + + if ( !stream_.callbackInfo.thread ) { + errorText_ = "RtApiWasapi::startStream: Unable to instantiate callback thread."; + error( RtAudioError::THREAD_ERROR ); + } + else { + SetThreadPriority( ( void* ) stream_.callbackInfo.thread, stream_.callbackInfo.priority ); + ResumeThread( ( void* ) stream_.callbackInfo.thread ); + } +} + +//----------------------------------------------------------------------------- + +void RtApiWasapi::stopStream( void ) +{ + verifyStream(); + + if ( stream_.state == STREAM_STOPPED ) { + errorText_ = "RtApiWasapi::stopStream: The stream is already stopped."; + error( RtAudioError::WARNING ); + return; + } + + // inform stream thread by setting stream state to STREAM_STOPPING + stream_.state = STREAM_STOPPING; + + // wait until stream thread is stopped + while( stream_.state != STREAM_STOPPED ) { + Sleep( 1 ); + } + + // Wait for the last buffer to play before stopping. + Sleep( 1000 * stream_.bufferSize / stream_.sampleRate ); + + // stop capture client if applicable + if ( ( ( WasapiHandle* ) stream_.apiHandle )->captureAudioClient ) { + HRESULT hr = ( ( WasapiHandle* ) stream_.apiHandle )->captureAudioClient->Stop(); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::stopStream: Unable to stop capture stream."; + error( RtAudioError::DRIVER_ERROR ); + return; + } + } + + // stop render client if applicable + if ( ( ( WasapiHandle* ) stream_.apiHandle )->renderAudioClient ) { + HRESULT hr = ( ( WasapiHandle* ) stream_.apiHandle )->renderAudioClient->Stop(); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::stopStream: Unable to stop render stream."; + error( RtAudioError::DRIVER_ERROR ); + return; + } + } + + // close thread handle + if ( stream_.callbackInfo.thread && !CloseHandle( ( void* ) stream_.callbackInfo.thread ) ) { + errorText_ = "RtApiWasapi::stopStream: Unable to close callback thread."; + error( RtAudioError::THREAD_ERROR ); + return; + } + + stream_.callbackInfo.thread = (ThreadHandle) NULL; +} + +//----------------------------------------------------------------------------- + +void RtApiWasapi::abortStream( void ) +{ + verifyStream(); + + if ( stream_.state == STREAM_STOPPED ) { + errorText_ = "RtApiWasapi::abortStream: The stream is already stopped."; + error( RtAudioError::WARNING ); + return; + } + + // inform stream thread by setting stream state to STREAM_STOPPING + stream_.state = STREAM_STOPPING; + + // wait until stream thread is stopped + while ( stream_.state != STREAM_STOPPED ) { + Sleep( 1 ); + } + + // stop capture client if applicable + if ( ( ( WasapiHandle* ) stream_.apiHandle )->captureAudioClient ) { + HRESULT hr = ( ( WasapiHandle* ) stream_.apiHandle )->captureAudioClient->Stop(); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::abortStream: Unable to stop capture stream."; + error( RtAudioError::DRIVER_ERROR ); + return; + } + } + + // stop render client if applicable + if ( ( ( WasapiHandle* ) stream_.apiHandle )->renderAudioClient ) { + HRESULT hr = ( ( WasapiHandle* ) stream_.apiHandle )->renderAudioClient->Stop(); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::abortStream: Unable to stop render stream."; + error( RtAudioError::DRIVER_ERROR ); + return; + } + } + + // close thread handle + if ( stream_.callbackInfo.thread && !CloseHandle( ( void* ) stream_.callbackInfo.thread ) ) { + errorText_ = "RtApiWasapi::abortStream: Unable to close callback thread."; + error( RtAudioError::THREAD_ERROR ); + return; + } + + stream_.callbackInfo.thread = (ThreadHandle) NULL; +} + +//----------------------------------------------------------------------------- + +bool RtApiWasapi::probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, + unsigned int firstChannel, unsigned int sampleRate, + RtAudioFormat format, unsigned int* bufferSize, + RtAudio::StreamOptions* options ) +{ + bool methodResult = FAILURE; + unsigned int captureDeviceCount = 0; + unsigned int renderDeviceCount = 0; + + IMMDeviceCollection* captureDevices = NULL; + IMMDeviceCollection* renderDevices = NULL; + IMMDevice* devicePtr = NULL; + WAVEFORMATEX* deviceFormat = NULL; + unsigned int bufferBytes; + stream_.state = STREAM_STOPPED; + + // create API Handle if not already created + if ( !stream_.apiHandle ) + stream_.apiHandle = ( void* ) new WasapiHandle(); + + // Count capture devices + errorText_.clear(); + RtAudioError::Type errorType = RtAudioError::DRIVER_ERROR; + HRESULT hr = deviceEnumerator_->EnumAudioEndpoints( eCapture, DEVICE_STATE_ACTIVE, &captureDevices ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve capture device collection."; + goto Exit; + } + + hr = captureDevices->GetCount( &captureDeviceCount ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve capture device count."; + goto Exit; + } + + // Count render devices + hr = deviceEnumerator_->EnumAudioEndpoints( eRender, DEVICE_STATE_ACTIVE, &renderDevices ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve render device collection."; + goto Exit; + } + + hr = renderDevices->GetCount( &renderDeviceCount ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve render device count."; + goto Exit; + } + + // validate device index + if ( device >= captureDeviceCount + renderDeviceCount ) { + errorType = RtAudioError::INVALID_USE; + errorText_ = "RtApiWasapi::probeDeviceOpen: Invalid device index."; + goto Exit; + } + + // determine whether index falls within capture or render devices + if ( device >= renderDeviceCount ) { + if ( mode != INPUT ) { + errorType = RtAudioError::INVALID_USE; + errorText_ = "RtApiWasapi::probeDeviceOpen: Capture device selected as output device."; + goto Exit; + } + + // retrieve captureAudioClient from devicePtr + IAudioClient*& captureAudioClient = ( ( WasapiHandle* ) stream_.apiHandle )->captureAudioClient; + + hr = captureDevices->Item( device - renderDeviceCount, &devicePtr ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve capture device handle."; + goto Exit; + } + + hr = devicePtr->Activate( __uuidof( IAudioClient ), CLSCTX_ALL, + NULL, ( void** ) &captureAudioClient ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve device audio client."; + goto Exit; + } + + hr = captureAudioClient->GetMixFormat( &deviceFormat ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve device mix format."; + goto Exit; + } + + stream_.nDeviceChannels[mode] = deviceFormat->nChannels; + captureAudioClient->GetStreamLatency( ( long long* ) &stream_.latency[mode] ); + } + else { + if ( mode != OUTPUT ) { + errorType = RtAudioError::INVALID_USE; + errorText_ = "RtApiWasapi::probeDeviceOpen: Render device selected as input device."; + goto Exit; + } + + // retrieve renderAudioClient from devicePtr + IAudioClient*& renderAudioClient = ( ( WasapiHandle* ) stream_.apiHandle )->renderAudioClient; + + hr = renderDevices->Item( device, &devicePtr ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve render device handle."; + goto Exit; + } + + hr = devicePtr->Activate( __uuidof( IAudioClient ), CLSCTX_ALL, + NULL, ( void** ) &renderAudioClient ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve device audio client."; + goto Exit; + } + + hr = renderAudioClient->GetMixFormat( &deviceFormat ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve device mix format."; + goto Exit; + } + + stream_.nDeviceChannels[mode] = deviceFormat->nChannels; + renderAudioClient->GetStreamLatency( ( long long* ) &stream_.latency[mode] ); + } + + // fill stream data + if ( ( stream_.mode == OUTPUT && mode == INPUT ) || + ( stream_.mode == INPUT && mode == OUTPUT ) ) { + stream_.mode = DUPLEX; + } + else { + stream_.mode = mode; + } + + stream_.device[mode] = device; + stream_.doByteSwap[mode] = false; + stream_.sampleRate = sampleRate; + stream_.bufferSize = *bufferSize; + stream_.nBuffers = 1; + stream_.nUserChannels[mode] = channels; + stream_.channelOffset[mode] = firstChannel; + stream_.userFormat = format; + stream_.deviceFormat[mode] = getDeviceInfo( device ).nativeFormats; + + if ( options && options->flags & RTAUDIO_NONINTERLEAVED ) + stream_.userInterleaved = false; + else + stream_.userInterleaved = true; + stream_.deviceInterleaved[mode] = true; + + // Set flags for buffer conversion. + stream_.doConvertBuffer[mode] = false; + if ( stream_.userFormat != stream_.deviceFormat[mode] || + stream_.nUserChannels != stream_.nDeviceChannels ) + stream_.doConvertBuffer[mode] = true; + else if ( stream_.userInterleaved != stream_.deviceInterleaved[mode] && + stream_.nUserChannels[mode] > 1 ) + stream_.doConvertBuffer[mode] = true; + + if ( stream_.doConvertBuffer[mode] ) + setConvertInfo( mode, 0 ); + + // Allocate necessary internal buffers + bufferBytes = stream_.nUserChannels[mode] * stream_.bufferSize * formatBytes( stream_.userFormat ); + + stream_.userBuffer[mode] = ( char* ) calloc( bufferBytes, 1 ); + if ( !stream_.userBuffer[mode] ) { + errorType = RtAudioError::MEMORY_ERROR; + errorText_ = "RtApiWasapi::probeDeviceOpen: Error allocating user buffer memory."; + goto Exit; + } + + if ( options && options->flags & RTAUDIO_SCHEDULE_REALTIME ) + stream_.callbackInfo.priority = 15; + else + stream_.callbackInfo.priority = 0; + + ///! TODO: RTAUDIO_MINIMIZE_LATENCY // Provide stream buffers directly to callback + ///! TODO: RTAUDIO_HOG_DEVICE // Exclusive mode + + methodResult = SUCCESS; + +Exit: + //clean up + SAFE_RELEASE( captureDevices ); + SAFE_RELEASE( renderDevices ); + SAFE_RELEASE( devicePtr ); + CoTaskMemFree( deviceFormat ); + + // if method failed, close the stream + if ( methodResult == FAILURE ) + closeStream(); + + if ( !errorText_.empty() ) + error( errorType ); + return methodResult; +} + +//============================================================================= + +DWORD WINAPI RtApiWasapi::runWasapiThread( void* wasapiPtr ) +{ + if ( wasapiPtr ) + ( ( RtApiWasapi* ) wasapiPtr )->wasapiThread(); + + return 0; +} + +DWORD WINAPI RtApiWasapi::stopWasapiThread( void* wasapiPtr ) +{ + if ( wasapiPtr ) + ( ( RtApiWasapi* ) wasapiPtr )->stopStream(); + + return 0; +} + +DWORD WINAPI RtApiWasapi::abortWasapiThread( void* wasapiPtr ) +{ + if ( wasapiPtr ) + ( ( RtApiWasapi* ) wasapiPtr )->abortStream(); + + return 0; +} + +//----------------------------------------------------------------------------- + +void RtApiWasapi::wasapiThread() +{ + // as this is a new thread, we must CoInitialize it + CoInitialize( NULL ); + + HRESULT hr; + + IAudioClient* captureAudioClient = ( ( WasapiHandle* ) stream_.apiHandle )->captureAudioClient; + IAudioClient* renderAudioClient = ( ( WasapiHandle* ) stream_.apiHandle )->renderAudioClient; + IAudioCaptureClient* captureClient = ( ( WasapiHandle* ) stream_.apiHandle )->captureClient; + IAudioRenderClient* renderClient = ( ( WasapiHandle* ) stream_.apiHandle )->renderClient; + HANDLE captureEvent = ( ( WasapiHandle* ) stream_.apiHandle )->captureEvent; + HANDLE renderEvent = ( ( WasapiHandle* ) stream_.apiHandle )->renderEvent; + + WAVEFORMATEX* captureFormat = NULL; + WAVEFORMATEX* renderFormat = NULL; + float captureSrRatio = 0.0f; + float renderSrRatio = 0.0f; + WasapiBuffer captureBuffer; + WasapiBuffer renderBuffer; + + // declare local stream variables + RtAudioCallback callback = ( RtAudioCallback ) stream_.callbackInfo.callback; + BYTE* streamBuffer = NULL; + unsigned long captureFlags = 0; + unsigned int bufferFrameCount = 0; + unsigned int numFramesPadding = 0; + unsigned int convBufferSize = 0; + bool callbackPushed = false; + bool callbackPulled = false; + bool callbackStopped = false; + int callbackResult = 0; + + // convBuffer is used to store converted buffers between WASAPI and the user + char* convBuffer = NULL; + unsigned int convBuffSize = 0; + unsigned int deviceBuffSize = 0; + + errorText_.clear(); + RtAudioError::Type errorType = RtAudioError::DRIVER_ERROR; + + // Attempt to assign "Pro Audio" characteristic to thread + HMODULE AvrtDll = LoadLibrary( (LPCTSTR) "AVRT.dll" ); + if ( AvrtDll ) { + DWORD taskIndex = 0; + TAvSetMmThreadCharacteristicsPtr AvSetMmThreadCharacteristicsPtr = ( TAvSetMmThreadCharacteristicsPtr ) GetProcAddress( AvrtDll, "AvSetMmThreadCharacteristicsW" ); + AvSetMmThreadCharacteristicsPtr( L"Pro Audio", &taskIndex ); + FreeLibrary( AvrtDll ); + } + + // start capture stream if applicable + if ( captureAudioClient ) { + hr = captureAudioClient->GetMixFormat( &captureFormat ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::wasapiThread: Unable to retrieve device mix format."; + goto Exit; + } + + captureSrRatio = ( ( float ) captureFormat->nSamplesPerSec / stream_.sampleRate ); + + // initialize capture stream according to desire buffer size + float desiredBufferSize = stream_.bufferSize * captureSrRatio; + REFERENCE_TIME desiredBufferPeriod = ( REFERENCE_TIME ) ( ( float ) desiredBufferSize * 10000000 / captureFormat->nSamplesPerSec ); + + if ( !captureClient ) { + hr = captureAudioClient->Initialize( AUDCLNT_SHAREMODE_SHARED, + AUDCLNT_STREAMFLAGS_EVENTCALLBACK, + desiredBufferPeriod, + desiredBufferPeriod, + captureFormat, + NULL ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::wasapiThread: Unable to initialize capture audio client."; + goto Exit; + } + + hr = captureAudioClient->GetService( __uuidof( IAudioCaptureClient ), + ( void** ) &captureClient ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::wasapiThread: Unable to retrieve capture client handle."; + goto Exit; + } + + // configure captureEvent to trigger on every available capture buffer + captureEvent = CreateEvent( NULL, FALSE, FALSE, NULL ); + if ( !captureEvent ) { + errorType = RtAudioError::SYSTEM_ERROR; + errorText_ = "RtApiWasapi::wasapiThread: Unable to create capture event."; + goto Exit; + } + + hr = captureAudioClient->SetEventHandle( captureEvent ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::wasapiThread: Unable to set capture event handle."; + goto Exit; + } + + ( ( WasapiHandle* ) stream_.apiHandle )->captureClient = captureClient; + ( ( WasapiHandle* ) stream_.apiHandle )->captureEvent = captureEvent; + } + + unsigned int inBufferSize = 0; + hr = captureAudioClient->GetBufferSize( &inBufferSize ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::wasapiThread: Unable to get capture buffer size."; + goto Exit; + } + + // scale outBufferSize according to stream->user sample rate ratio + unsigned int outBufferSize = ( unsigned int ) ( stream_.bufferSize * captureSrRatio ) * stream_.nDeviceChannels[INPUT]; + inBufferSize *= stream_.nDeviceChannels[INPUT]; + + // set captureBuffer size + captureBuffer.setBufferSize( inBufferSize + outBufferSize, formatBytes( stream_.deviceFormat[INPUT] ) ); + + // reset the capture stream + hr = captureAudioClient->Reset(); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::wasapiThread: Unable to reset capture stream."; + goto Exit; + } + + // start the capture stream + hr = captureAudioClient->Start(); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::wasapiThread: Unable to start capture stream."; + goto Exit; + } + } + + // start render stream if applicable + if ( renderAudioClient ) { + hr = renderAudioClient->GetMixFormat( &renderFormat ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::wasapiThread: Unable to retrieve device mix format."; + goto Exit; + } + + renderSrRatio = ( ( float ) renderFormat->nSamplesPerSec / stream_.sampleRate ); + + // initialize render stream according to desire buffer size + float desiredBufferSize = stream_.bufferSize * renderSrRatio; + REFERENCE_TIME desiredBufferPeriod = ( REFERENCE_TIME ) ( ( float ) desiredBufferSize * 10000000 / renderFormat->nSamplesPerSec ); + + if ( !renderClient ) { + hr = renderAudioClient->Initialize( AUDCLNT_SHAREMODE_SHARED, + AUDCLNT_STREAMFLAGS_EVENTCALLBACK, + desiredBufferPeriod, + desiredBufferPeriod, + renderFormat, + NULL ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::wasapiThread: Unable to initialize render audio client."; + goto Exit; + } + + hr = renderAudioClient->GetService( __uuidof( IAudioRenderClient ), + ( void** ) &renderClient ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::wasapiThread: Unable to retrieve render client handle."; + goto Exit; + } + + // configure renderEvent to trigger on every available render buffer + renderEvent = CreateEvent( NULL, FALSE, FALSE, NULL ); + if ( !renderEvent ) { + errorType = RtAudioError::SYSTEM_ERROR; + errorText_ = "RtApiWasapi::wasapiThread: Unable to create render event."; + goto Exit; + } + + hr = renderAudioClient->SetEventHandle( renderEvent ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::wasapiThread: Unable to set render event handle."; + goto Exit; + } + + ( ( WasapiHandle* ) stream_.apiHandle )->renderClient = renderClient; + ( ( WasapiHandle* ) stream_.apiHandle )->renderEvent = renderEvent; + } + + unsigned int outBufferSize = 0; + hr = renderAudioClient->GetBufferSize( &outBufferSize ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::wasapiThread: Unable to get render buffer size."; + goto Exit; + } + + // scale inBufferSize according to user->stream sample rate ratio + unsigned int inBufferSize = ( unsigned int ) ( stream_.bufferSize * renderSrRatio ) * stream_.nDeviceChannels[OUTPUT]; + outBufferSize *= stream_.nDeviceChannels[OUTPUT]; + + // set renderBuffer size + renderBuffer.setBufferSize( inBufferSize + outBufferSize, formatBytes( stream_.deviceFormat[OUTPUT] ) ); + + // reset the render stream + hr = renderAudioClient->Reset(); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::wasapiThread: Unable to reset render stream."; + goto Exit; + } + + // start the render stream + hr = renderAudioClient->Start(); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::wasapiThread: Unable to start render stream."; + goto Exit; + } + } + + if ( stream_.mode == INPUT ) { + convBuffSize = ( size_t ) ( stream_.bufferSize * captureSrRatio ) * stream_.nDeviceChannels[INPUT] * formatBytes( stream_.deviceFormat[INPUT] ); + deviceBuffSize = stream_.bufferSize * stream_.nDeviceChannels[INPUT] * formatBytes( stream_.deviceFormat[INPUT] ); + } + else if ( stream_.mode == OUTPUT ) { + convBuffSize = ( size_t ) ( stream_.bufferSize * renderSrRatio ) * stream_.nDeviceChannels[OUTPUT] * formatBytes( stream_.deviceFormat[OUTPUT] ); + deviceBuffSize = stream_.bufferSize * stream_.nDeviceChannels[OUTPUT] * formatBytes( stream_.deviceFormat[OUTPUT] ); + } + else if ( stream_.mode == DUPLEX ) { + convBuffSize = std::max( ( size_t ) ( stream_.bufferSize * captureSrRatio ) * stream_.nDeviceChannels[INPUT] * formatBytes( stream_.deviceFormat[INPUT] ), + ( size_t ) ( stream_.bufferSize * renderSrRatio ) * stream_.nDeviceChannels[OUTPUT] * formatBytes( stream_.deviceFormat[OUTPUT] ) ); + deviceBuffSize = std::max( stream_.bufferSize * stream_.nDeviceChannels[INPUT] * formatBytes( stream_.deviceFormat[INPUT] ), + stream_.bufferSize * stream_.nDeviceChannels[OUTPUT] * formatBytes( stream_.deviceFormat[OUTPUT] ) ); + } + + convBuffer = ( char* ) malloc( convBuffSize ); + stream_.deviceBuffer = ( char* ) malloc( deviceBuffSize ); + if ( !convBuffer || !stream_.deviceBuffer ) { + errorType = RtAudioError::MEMORY_ERROR; + errorText_ = "RtApiWasapi::wasapiThread: Error allocating device buffer memory."; + goto Exit; + } + + // stream process loop + while ( stream_.state != STREAM_STOPPING ) { + if ( !callbackPulled ) { + // Callback Input + // ============== + // 1. Pull callback buffer from inputBuffer + // 2. If 1. was successful: Convert callback buffer to user sample rate and channel count + // Convert callback buffer to user format + + if ( captureAudioClient ) { + // Pull callback buffer from inputBuffer + callbackPulled = captureBuffer.pullBuffer( convBuffer, + ( unsigned int ) ( stream_.bufferSize * captureSrRatio ) * stream_.nDeviceChannels[INPUT], + stream_.deviceFormat[INPUT] ); + + if ( callbackPulled ) { + // Convert callback buffer to user sample rate + convertBufferWasapi( stream_.deviceBuffer, + convBuffer, + stream_.nDeviceChannels[INPUT], + captureFormat->nSamplesPerSec, + stream_.sampleRate, + ( unsigned int ) ( stream_.bufferSize * captureSrRatio ), + convBufferSize, + stream_.deviceFormat[INPUT] ); + + if ( stream_.doConvertBuffer[INPUT] ) { + // Convert callback buffer to user format + convertBuffer( stream_.userBuffer[INPUT], + stream_.deviceBuffer, + stream_.convertInfo[INPUT] ); + } + else { + // no further conversion, simple copy deviceBuffer to userBuffer + memcpy( stream_.userBuffer[INPUT], + stream_.deviceBuffer, + stream_.bufferSize * stream_.nUserChannels[INPUT] * formatBytes( stream_.userFormat ) ); + } + } + } + else { + // if there is no capture stream, set callbackPulled flag + callbackPulled = true; + } + + // Execute Callback + // ================ + // 1. Execute user callback method + // 2. Handle return value from callback + + // if callback has not requested the stream to stop + if ( callbackPulled && !callbackStopped ) { + // Execute user callback method + callbackResult = callback( stream_.userBuffer[OUTPUT], + stream_.userBuffer[INPUT], + stream_.bufferSize, + getStreamTime(), + captureFlags & AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY ? RTAUDIO_INPUT_OVERFLOW : 0, + stream_.callbackInfo.userData ); + + // Handle return value from callback + if ( callbackResult == 1 ) { + // instantiate a thread to stop this thread + HANDLE threadHandle = CreateThread( NULL, 0, stopWasapiThread, this, 0, NULL ); + if ( !threadHandle ) { + errorType = RtAudioError::THREAD_ERROR; + errorText_ = "RtApiWasapi::wasapiThread: Unable to instantiate stream stop thread."; + goto Exit; + } + else if ( !CloseHandle( threadHandle ) ) { + errorType = RtAudioError::THREAD_ERROR; + errorText_ = "RtApiWasapi::wasapiThread: Unable to close stream stop thread handle."; + goto Exit; + } + + callbackStopped = true; + } + else if ( callbackResult == 2 ) { + // instantiate a thread to stop this thread + HANDLE threadHandle = CreateThread( NULL, 0, abortWasapiThread, this, 0, NULL ); + if ( !threadHandle ) { + errorType = RtAudioError::THREAD_ERROR; + errorText_ = "RtApiWasapi::wasapiThread: Unable to instantiate stream abort thread."; + goto Exit; + } + else if ( !CloseHandle( threadHandle ) ) { + errorType = RtAudioError::THREAD_ERROR; + errorText_ = "RtApiWasapi::wasapiThread: Unable to close stream abort thread handle."; + goto Exit; + } + + callbackStopped = true; + } + } + } + + // Callback Output + // =============== + // 1. Convert callback buffer to stream format + // 2. Convert callback buffer to stream sample rate and channel count + // 3. Push callback buffer into outputBuffer + + if ( renderAudioClient && callbackPulled ) { + if ( stream_.doConvertBuffer[OUTPUT] ) { + // Convert callback buffer to stream format + convertBuffer( stream_.deviceBuffer, + stream_.userBuffer[OUTPUT], + stream_.convertInfo[OUTPUT] ); + + } + + // Convert callback buffer to stream sample rate + convertBufferWasapi( convBuffer, + stream_.deviceBuffer, + stream_.nDeviceChannels[OUTPUT], + stream_.sampleRate, + renderFormat->nSamplesPerSec, + stream_.bufferSize, + convBufferSize, + stream_.deviceFormat[OUTPUT] ); + + // Push callback buffer into outputBuffer + callbackPushed = renderBuffer.pushBuffer( convBuffer, + convBufferSize * stream_.nDeviceChannels[OUTPUT], + stream_.deviceFormat[OUTPUT] ); + } + else { + // if there is no render stream, set callbackPushed flag + callbackPushed = true; + } + + // Stream Capture + // ============== + // 1. Get capture buffer from stream + // 2. Push capture buffer into inputBuffer + // 3. If 2. was successful: Release capture buffer + + if ( captureAudioClient ) { + // if the callback input buffer was not pulled from captureBuffer, wait for next capture event + if ( !callbackPulled ) { + WaitForSingleObject( captureEvent, INFINITE ); + } + + // Get capture buffer from stream + hr = captureClient->GetBuffer( &streamBuffer, + &bufferFrameCount, + &captureFlags, NULL, NULL ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::wasapiThread: Unable to retrieve capture buffer."; + goto Exit; + } + + if ( bufferFrameCount != 0 ) { + // Push capture buffer into inputBuffer + if ( captureBuffer.pushBuffer( ( char* ) streamBuffer, + bufferFrameCount * stream_.nDeviceChannels[INPUT], + stream_.deviceFormat[INPUT] ) ) + { + // Release capture buffer + hr = captureClient->ReleaseBuffer( bufferFrameCount ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::wasapiThread: Unable to release capture buffer."; + goto Exit; + } + } + else + { + // Inform WASAPI that capture was unsuccessful + hr = captureClient->ReleaseBuffer( 0 ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::wasapiThread: Unable to release capture buffer."; + goto Exit; + } + } + } + else + { + // Inform WASAPI that capture was unsuccessful + hr = captureClient->ReleaseBuffer( 0 ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::wasapiThread: Unable to release capture buffer."; + goto Exit; + } + } + } + + // Stream Render + // ============= + // 1. Get render buffer from stream + // 2. Pull next buffer from outputBuffer + // 3. If 2. was successful: Fill render buffer with next buffer + // Release render buffer + + if ( renderAudioClient ) { + // if the callback output buffer was not pushed to renderBuffer, wait for next render event + if ( callbackPulled && !callbackPushed ) { + WaitForSingleObject( renderEvent, INFINITE ); + } + + // Get render buffer from stream + hr = renderAudioClient->GetBufferSize( &bufferFrameCount ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::wasapiThread: Unable to retrieve render buffer size."; + goto Exit; + } + + hr = renderAudioClient->GetCurrentPadding( &numFramesPadding ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::wasapiThread: Unable to retrieve render buffer padding."; + goto Exit; + } + + bufferFrameCount -= numFramesPadding; + + if ( bufferFrameCount != 0 ) { + hr = renderClient->GetBuffer( bufferFrameCount, &streamBuffer ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::wasapiThread: Unable to retrieve render buffer."; + goto Exit; + } + + // Pull next buffer from outputBuffer + // Fill render buffer with next buffer + if ( renderBuffer.pullBuffer( ( char* ) streamBuffer, + bufferFrameCount * stream_.nDeviceChannels[OUTPUT], + stream_.deviceFormat[OUTPUT] ) ) + { + // Release render buffer + hr = renderClient->ReleaseBuffer( bufferFrameCount, 0 ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::wasapiThread: Unable to release render buffer."; + goto Exit; + } + } + else + { + // Inform WASAPI that render was unsuccessful + hr = renderClient->ReleaseBuffer( 0, 0 ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::wasapiThread: Unable to release render buffer."; + goto Exit; + } + } + } + else + { + // Inform WASAPI that render was unsuccessful + hr = renderClient->ReleaseBuffer( 0, 0 ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::wasapiThread: Unable to release render buffer."; + goto Exit; + } + } + } + + // if the callback buffer was pushed renderBuffer reset callbackPulled flag + if ( callbackPushed ) { + callbackPulled = false; + } + + // tick stream time + RtApi::tickStreamTime(); + } + +Exit: + // clean up + CoTaskMemFree( captureFormat ); + CoTaskMemFree( renderFormat ); + + free ( convBuffer ); + + CoUninitialize(); + + // update stream state + stream_.state = STREAM_STOPPED; + + if ( errorText_.empty() ) + return; + else + error( errorType ); +} + +//******************** End of __WINDOWS_WASAPI__ *********************// +#endif + + +#if defined(__WINDOWS_DS__) // Windows DirectSound API + +// Modified by Robin Davies, October 2005 +// - Improvements to DirectX pointer chasing. +// - Bug fix for non-power-of-two Asio granularity used by Edirol PCR-A30. +// - Auto-call CoInitialize for DSOUND and ASIO platforms. +// Various revisions for RtAudio 4.0 by Gary Scavone, April 2007 +// Changed device query structure for RtAudio 4.0.7, January 2010 + +#include +#include +#include + +#if defined(__MINGW32__) + // missing from latest mingw winapi +#define WAVE_FORMAT_96M08 0x00010000 /* 96 kHz, Mono, 8-bit */ +#define WAVE_FORMAT_96S08 0x00020000 /* 96 kHz, Stereo, 8-bit */ +#define WAVE_FORMAT_96M16 0x00040000 /* 96 kHz, Mono, 16-bit */ +#define WAVE_FORMAT_96S16 0x00080000 /* 96 kHz, Stereo, 16-bit */ +#endif + +#define MINIMUM_DEVICE_BUFFER_SIZE 32768 + +#ifdef _MSC_VER // if Microsoft Visual C++ +#pragma comment( lib, "winmm.lib" ) // then, auto-link winmm.lib. Otherwise, it has to be added manually. +#endif + +static inline DWORD dsPointerBetween( DWORD pointer, DWORD laterPointer, DWORD earlierPointer, DWORD bufferSize ) +{ + if ( pointer > bufferSize ) pointer -= bufferSize; + if ( laterPointer < earlierPointer ) laterPointer += bufferSize; + if ( pointer < earlierPointer ) pointer += bufferSize; + return pointer >= earlierPointer && pointer < laterPointer; +} + +// A structure to hold various information related to the DirectSound +// API implementation. +struct DsHandle { + unsigned int drainCounter; // Tracks callback counts when draining + bool internalDrain; // Indicates if stop is initiated from callback or not. + void *id[2]; + void *buffer[2]; + bool xrun[2]; + UINT bufferPointer[2]; + DWORD dsBufferSize[2]; + DWORD dsPointerLeadTime[2]; // the number of bytes ahead of the safe pointer to lead by. + HANDLE condition; + + DsHandle() + :drainCounter(0), internalDrain(false) { id[0] = 0; id[1] = 0; buffer[0] = 0; buffer[1] = 0; xrun[0] = false; xrun[1] = false; bufferPointer[0] = 0; bufferPointer[1] = 0; } +}; + +// Declarations for utility functions, callbacks, and structures +// specific to the DirectSound implementation. +static BOOL CALLBACK deviceQueryCallback( LPGUID lpguid, + LPCTSTR description, + LPCTSTR module, + LPVOID lpContext ); + +static const char* getErrorString( int code ); + +static unsigned __stdcall callbackHandler( void *ptr ); + +struct DsDevice { + LPGUID id[2]; + bool validId[2]; + bool found; + std::string name; + + DsDevice() + : found(false) { validId[0] = false; validId[1] = false; } +}; + +struct DsProbeData { + bool isInput; + std::vector* dsDevices; +}; + +RtApiDs :: RtApiDs() +{ + // Dsound will run both-threaded. If CoInitialize fails, then just + // accept whatever the mainline chose for a threading model. + coInitialized_ = false; + HRESULT hr = CoInitialize( NULL ); + if ( !FAILED( hr ) ) coInitialized_ = true; +} + +RtApiDs :: ~RtApiDs() +{ + if ( coInitialized_ ) CoUninitialize(); // balanced call. + if ( stream_.state != STREAM_CLOSED ) closeStream(); +} + +// The DirectSound default output is always the first device. +unsigned int RtApiDs :: getDefaultOutputDevice( void ) +{ + return 0; +} + +// The DirectSound default input is always the first input device, +// which is the first capture device enumerated. +unsigned int RtApiDs :: getDefaultInputDevice( void ) +{ + return 0; +} + +unsigned int RtApiDs :: getDeviceCount( void ) +{ + // Set query flag for previously found devices to false, so that we + // can check for any devices that have disappeared. + for ( unsigned int i=0; i indices; + for ( unsigned int i=0; i(dsDevices.size()); +} + +RtAudio::DeviceInfo RtApiDs :: getDeviceInfo( unsigned int device ) +{ + RtAudio::DeviceInfo info; + info.probed = false; + + if ( dsDevices.size() == 0 ) { + // Force a query of all devices + getDeviceCount(); + if ( dsDevices.size() == 0 ) { + errorText_ = "RtApiDs::getDeviceInfo: no devices found!"; + error( RtAudioError::INVALID_USE ); + return info; + } + } + + if ( device >= dsDevices.size() ) { + errorText_ = "RtApiDs::getDeviceInfo: device ID is invalid!"; + error( RtAudioError::INVALID_USE ); + return info; + } + + HRESULT result; + if ( dsDevices[ device ].validId[0] == false ) goto probeInput; + + LPDIRECTSOUND output; + DSCAPS outCaps; + result = DirectSoundCreate( dsDevices[ device ].id[0], &output, NULL ); + if ( FAILED( result ) ) { + errorStream_ << "RtApiDs::getDeviceInfo: error (" << getErrorString( result ) << ") opening output device (" << dsDevices[ device ].name << ")!"; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + goto probeInput; + } + + outCaps.dwSize = sizeof( outCaps ); + result = output->GetCaps( &outCaps ); + if ( FAILED( result ) ) { + output->Release(); + errorStream_ << "RtApiDs::getDeviceInfo: error (" << getErrorString( result ) << ") getting capabilities!"; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + goto probeInput; + } + + // Get output channel information. + info.outputChannels = ( outCaps.dwFlags & DSCAPS_PRIMARYSTEREO ) ? 2 : 1; + + // Get sample rate information. + info.sampleRates.clear(); + for ( unsigned int k=0; k= (unsigned int) outCaps.dwMinSecondarySampleRate && + SAMPLE_RATES[k] <= (unsigned int) outCaps.dwMaxSecondarySampleRate ) + info.sampleRates.push_back( SAMPLE_RATES[k] ); + } + + // Get format information. + if ( outCaps.dwFlags & DSCAPS_PRIMARY16BIT ) info.nativeFormats |= RTAUDIO_SINT16; + if ( outCaps.dwFlags & DSCAPS_PRIMARY8BIT ) info.nativeFormats |= RTAUDIO_SINT8; + + output->Release(); + + if ( getDefaultOutputDevice() == device ) + info.isDefaultOutput = true; + + if ( dsDevices[ device ].validId[1] == false ) { + info.name = dsDevices[ device ].name; + info.probed = true; + return info; + } + + probeInput: + + LPDIRECTSOUNDCAPTURE input; + result = DirectSoundCaptureCreate( dsDevices[ device ].id[1], &input, NULL ); + if ( FAILED( result ) ) { + errorStream_ << "RtApiDs::getDeviceInfo: error (" << getErrorString( result ) << ") opening input device (" << dsDevices[ device ].name << ")!"; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + return info; + } + + DSCCAPS inCaps; + inCaps.dwSize = sizeof( inCaps ); + result = input->GetCaps( &inCaps ); + if ( FAILED( result ) ) { + input->Release(); + errorStream_ << "RtApiDs::getDeviceInfo: error (" << getErrorString( result ) << ") getting object capabilities (" << dsDevices[ device ].name << ")!"; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + return info; + } + + // Get input channel information. + info.inputChannels = inCaps.dwChannels; + + // Get sample rate and format information. + std::vector rates; + if ( inCaps.dwChannels >= 2 ) { + if ( inCaps.dwFormats & WAVE_FORMAT_1S16 ) info.nativeFormats |= RTAUDIO_SINT16; + if ( inCaps.dwFormats & WAVE_FORMAT_2S16 ) info.nativeFormats |= RTAUDIO_SINT16; + if ( inCaps.dwFormats & WAVE_FORMAT_4S16 ) info.nativeFormats |= RTAUDIO_SINT16; + if ( inCaps.dwFormats & WAVE_FORMAT_96S16 ) info.nativeFormats |= RTAUDIO_SINT16; + if ( inCaps.dwFormats & WAVE_FORMAT_1S08 ) info.nativeFormats |= RTAUDIO_SINT8; + if ( inCaps.dwFormats & WAVE_FORMAT_2S08 ) info.nativeFormats |= RTAUDIO_SINT8; + if ( inCaps.dwFormats & WAVE_FORMAT_4S08 ) info.nativeFormats |= RTAUDIO_SINT8; + if ( inCaps.dwFormats & WAVE_FORMAT_96S08 ) info.nativeFormats |= RTAUDIO_SINT8; + + if ( info.nativeFormats & RTAUDIO_SINT16 ) { + if ( inCaps.dwFormats & WAVE_FORMAT_1S16 ) rates.push_back( 11025 ); + if ( inCaps.dwFormats & WAVE_FORMAT_2S16 ) rates.push_back( 22050 ); + if ( inCaps.dwFormats & WAVE_FORMAT_4S16 ) rates.push_back( 44100 ); + if ( inCaps.dwFormats & WAVE_FORMAT_96S16 ) rates.push_back( 96000 ); + } + else if ( info.nativeFormats & RTAUDIO_SINT8 ) { + if ( inCaps.dwFormats & WAVE_FORMAT_1S08 ) rates.push_back( 11025 ); + if ( inCaps.dwFormats & WAVE_FORMAT_2S08 ) rates.push_back( 22050 ); + if ( inCaps.dwFormats & WAVE_FORMAT_4S08 ) rates.push_back( 44100 ); + if ( inCaps.dwFormats & WAVE_FORMAT_96S08 ) rates.push_back( 96000 ); + } + } + else if ( inCaps.dwChannels == 1 ) { + if ( inCaps.dwFormats & WAVE_FORMAT_1M16 ) info.nativeFormats |= RTAUDIO_SINT16; + if ( inCaps.dwFormats & WAVE_FORMAT_2M16 ) info.nativeFormats |= RTAUDIO_SINT16; + if ( inCaps.dwFormats & WAVE_FORMAT_4M16 ) info.nativeFormats |= RTAUDIO_SINT16; + if ( inCaps.dwFormats & WAVE_FORMAT_96M16 ) info.nativeFormats |= RTAUDIO_SINT16; + if ( inCaps.dwFormats & WAVE_FORMAT_1M08 ) info.nativeFormats |= RTAUDIO_SINT8; + if ( inCaps.dwFormats & WAVE_FORMAT_2M08 ) info.nativeFormats |= RTAUDIO_SINT8; + if ( inCaps.dwFormats & WAVE_FORMAT_4M08 ) info.nativeFormats |= RTAUDIO_SINT8; + if ( inCaps.dwFormats & WAVE_FORMAT_96M08 ) info.nativeFormats |= RTAUDIO_SINT8; + + if ( info.nativeFormats & RTAUDIO_SINT16 ) { + if ( inCaps.dwFormats & WAVE_FORMAT_1M16 ) rates.push_back( 11025 ); + if ( inCaps.dwFormats & WAVE_FORMAT_2M16 ) rates.push_back( 22050 ); + if ( inCaps.dwFormats & WAVE_FORMAT_4M16 ) rates.push_back( 44100 ); + if ( inCaps.dwFormats & WAVE_FORMAT_96M16 ) rates.push_back( 96000 ); + } + else if ( info.nativeFormats & RTAUDIO_SINT8 ) { + if ( inCaps.dwFormats & WAVE_FORMAT_1M08 ) rates.push_back( 11025 ); + if ( inCaps.dwFormats & WAVE_FORMAT_2M08 ) rates.push_back( 22050 ); + if ( inCaps.dwFormats & WAVE_FORMAT_4M08 ) rates.push_back( 44100 ); + if ( inCaps.dwFormats & WAVE_FORMAT_96M08 ) rates.push_back( 96000 ); + } + } + else info.inputChannels = 0; // technically, this would be an error + + input->Release(); + + if ( info.inputChannels == 0 ) return info; + + // Copy the supported rates to the info structure but avoid duplication. + bool found; + for ( unsigned int i=0; i 0 && info.inputChannels > 0 ) + info.duplexChannels = (info.outputChannels > info.inputChannels) ? info.inputChannels : info.outputChannels; + + if ( device == 0 ) info.isDefaultInput = true; + + // Copy name and return. + info.name = dsDevices[ device ].name; + info.probed = true; + return info; +} + +bool RtApiDs :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, + unsigned int firstChannel, unsigned int sampleRate, + RtAudioFormat format, unsigned int *bufferSize, + RtAudio::StreamOptions *options ) +{ + if ( channels + firstChannel > 2 ) { + errorText_ = "RtApiDs::probeDeviceOpen: DirectSound does not support more than 2 channels per device."; + return FAILURE; + } + + size_t nDevices = dsDevices.size(); + if ( nDevices == 0 ) { + // This should not happen because a check is made before this function is called. + errorText_ = "RtApiDs::probeDeviceOpen: no devices found!"; + return FAILURE; + } + + if ( device >= nDevices ) { + // This should not happen because a check is made before this function is called. + errorText_ = "RtApiDs::probeDeviceOpen: device ID is invalid!"; + return FAILURE; + } + + if ( mode == OUTPUT ) { + if ( dsDevices[ device ].validId[0] == false ) { + errorStream_ << "RtApiDs::probeDeviceOpen: device (" << device << ") does not support output!"; + errorText_ = errorStream_.str(); + return FAILURE; + } + } + else { // mode == INPUT + if ( dsDevices[ device ].validId[1] == false ) { + errorStream_ << "RtApiDs::probeDeviceOpen: device (" << device << ") does not support input!"; + errorText_ = errorStream_.str(); + return FAILURE; + } + } + + // According to a note in PortAudio, using GetDesktopWindow() + // instead of GetForegroundWindow() is supposed to avoid problems + // that occur when the application's window is not the foreground + // window. Also, if the application window closes before the + // DirectSound buffer, DirectSound can crash. In the past, I had + // problems when using GetDesktopWindow() but it seems fine now + // (January 2010). I'll leave it commented here. + // HWND hWnd = GetForegroundWindow(); + HWND hWnd = GetDesktopWindow(); + + // Check the numberOfBuffers parameter and limit the lowest value to + // two. This is a judgement call and a value of two is probably too + // low for capture, but it should work for playback. + int nBuffers = 0; + if ( options ) nBuffers = options->numberOfBuffers; + if ( options && options->flags & RTAUDIO_MINIMIZE_LATENCY ) nBuffers = 2; + if ( nBuffers < 2 ) nBuffers = 3; + + // Check the lower range of the user-specified buffer size and set + // (arbitrarily) to a lower bound of 32. + if ( *bufferSize < 32 ) *bufferSize = 32; + + // Create the wave format structure. The data format setting will + // be determined later. + WAVEFORMATEX waveFormat; + ZeroMemory( &waveFormat, sizeof(WAVEFORMATEX) ); + waveFormat.wFormatTag = WAVE_FORMAT_PCM; + waveFormat.nChannels = channels + firstChannel; + waveFormat.nSamplesPerSec = (unsigned long) sampleRate; + + // Determine the device buffer size. By default, we'll use the value + // defined above (32K), but we will grow it to make allowances for + // very large software buffer sizes. + DWORD dsBufferSize = MINIMUM_DEVICE_BUFFER_SIZE; + DWORD dsPointerLeadTime = 0; + + void *ohandle = 0, *bhandle = 0; + HRESULT result; + if ( mode == OUTPUT ) { + + LPDIRECTSOUND output; + result = DirectSoundCreate( dsDevices[ device ].id[0], &output, NULL ); + if ( FAILED( result ) ) { + errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") opening output device (" << dsDevices[ device ].name << ")!"; + errorText_ = errorStream_.str(); + return FAILURE; + } + + DSCAPS outCaps; + outCaps.dwSize = sizeof( outCaps ); + result = output->GetCaps( &outCaps ); + if ( FAILED( result ) ) { + output->Release(); + errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") getting capabilities (" << dsDevices[ device ].name << ")!"; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Check channel information. + if ( channels + firstChannel == 2 && !( outCaps.dwFlags & DSCAPS_PRIMARYSTEREO ) ) { + errorStream_ << "RtApiDs::getDeviceInfo: the output device (" << dsDevices[ device ].name << ") does not support stereo playback."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Check format information. Use 16-bit format unless not + // supported or user requests 8-bit. + if ( outCaps.dwFlags & DSCAPS_PRIMARY16BIT && + !( format == RTAUDIO_SINT8 && outCaps.dwFlags & DSCAPS_PRIMARY8BIT ) ) { + waveFormat.wBitsPerSample = 16; + stream_.deviceFormat[mode] = RTAUDIO_SINT16; + } + else { + waveFormat.wBitsPerSample = 8; + stream_.deviceFormat[mode] = RTAUDIO_SINT8; + } + stream_.userFormat = format; + + // Update wave format structure and buffer information. + waveFormat.nBlockAlign = waveFormat.nChannels * waveFormat.wBitsPerSample / 8; + waveFormat.nAvgBytesPerSec = waveFormat.nSamplesPerSec * waveFormat.nBlockAlign; + dsPointerLeadTime = nBuffers * (*bufferSize) * (waveFormat.wBitsPerSample / 8) * channels; + + // If the user wants an even bigger buffer, increase the device buffer size accordingly. + while ( dsPointerLeadTime * 2U > dsBufferSize ) + dsBufferSize *= 2; + + // Set cooperative level to DSSCL_EXCLUSIVE ... sound stops when window focus changes. + // result = output->SetCooperativeLevel( hWnd, DSSCL_EXCLUSIVE ); + // Set cooperative level to DSSCL_PRIORITY ... sound remains when window focus changes. + result = output->SetCooperativeLevel( hWnd, DSSCL_PRIORITY ); + if ( FAILED( result ) ) { + output->Release(); + errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") setting cooperative level (" << dsDevices[ device ].name << ")!"; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Even though we will write to the secondary buffer, we need to + // access the primary buffer to set the correct output format + // (since the default is 8-bit, 22 kHz!). Setup the DS primary + // buffer description. + DSBUFFERDESC bufferDescription; + ZeroMemory( &bufferDescription, sizeof( DSBUFFERDESC ) ); + bufferDescription.dwSize = sizeof( DSBUFFERDESC ); + bufferDescription.dwFlags = DSBCAPS_PRIMARYBUFFER; + + // Obtain the primary buffer + LPDIRECTSOUNDBUFFER buffer; + result = output->CreateSoundBuffer( &bufferDescription, &buffer, NULL ); + if ( FAILED( result ) ) { + output->Release(); + errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") accessing primary buffer (" << dsDevices[ device ].name << ")!"; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Set the primary DS buffer sound format. + result = buffer->SetFormat( &waveFormat ); + if ( FAILED( result ) ) { + output->Release(); + errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") setting primary buffer format (" << dsDevices[ device ].name << ")!"; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Setup the secondary DS buffer description. + ZeroMemory( &bufferDescription, sizeof( DSBUFFERDESC ) ); + bufferDescription.dwSize = sizeof( DSBUFFERDESC ); + bufferDescription.dwFlags = ( DSBCAPS_STICKYFOCUS | + DSBCAPS_GLOBALFOCUS | + DSBCAPS_GETCURRENTPOSITION2 | + DSBCAPS_LOCHARDWARE ); // Force hardware mixing + bufferDescription.dwBufferBytes = dsBufferSize; + bufferDescription.lpwfxFormat = &waveFormat; + + // Try to create the secondary DS buffer. If that doesn't work, + // try to use software mixing. Otherwise, there's a problem. + result = output->CreateSoundBuffer( &bufferDescription, &buffer, NULL ); + if ( FAILED( result ) ) { + bufferDescription.dwFlags = ( DSBCAPS_STICKYFOCUS | + DSBCAPS_GLOBALFOCUS | + DSBCAPS_GETCURRENTPOSITION2 | + DSBCAPS_LOCSOFTWARE ); // Force software mixing + result = output->CreateSoundBuffer( &bufferDescription, &buffer, NULL ); + if ( FAILED( result ) ) { + output->Release(); + errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") creating secondary buffer (" << dsDevices[ device ].name << ")!"; + errorText_ = errorStream_.str(); + return FAILURE; + } + } + + // Get the buffer size ... might be different from what we specified. + DSBCAPS dsbcaps; + dsbcaps.dwSize = sizeof( DSBCAPS ); + result = buffer->GetCaps( &dsbcaps ); + if ( FAILED( result ) ) { + output->Release(); + buffer->Release(); + errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") getting buffer settings (" << dsDevices[ device ].name << ")!"; + errorText_ = errorStream_.str(); + return FAILURE; + } + + dsBufferSize = dsbcaps.dwBufferBytes; + + // Lock the DS buffer + LPVOID audioPtr; + DWORD dataLen; + result = buffer->Lock( 0, dsBufferSize, &audioPtr, &dataLen, NULL, NULL, 0 ); + if ( FAILED( result ) ) { + output->Release(); + buffer->Release(); + errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") locking buffer (" << dsDevices[ device ].name << ")!"; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Zero the DS buffer + ZeroMemory( audioPtr, dataLen ); + + // Unlock the DS buffer + result = buffer->Unlock( audioPtr, dataLen, NULL, 0 ); + if ( FAILED( result ) ) { + output->Release(); + buffer->Release(); + errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") unlocking buffer (" << dsDevices[ device ].name << ")!"; + errorText_ = errorStream_.str(); + return FAILURE; + } + + ohandle = (void *) output; + bhandle = (void *) buffer; + } + + if ( mode == INPUT ) { + + LPDIRECTSOUNDCAPTURE input; + result = DirectSoundCaptureCreate( dsDevices[ device ].id[1], &input, NULL ); + if ( FAILED( result ) ) { + errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") opening input device (" << dsDevices[ device ].name << ")!"; + errorText_ = errorStream_.str(); + return FAILURE; + } + + DSCCAPS inCaps; + inCaps.dwSize = sizeof( inCaps ); + result = input->GetCaps( &inCaps ); + if ( FAILED( result ) ) { + input->Release(); + errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") getting input capabilities (" << dsDevices[ device ].name << ")!"; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Check channel information. + if ( inCaps.dwChannels < channels + firstChannel ) { + errorText_ = "RtApiDs::getDeviceInfo: the input device does not support requested input channels."; + return FAILURE; + } + + // Check format information. Use 16-bit format unless user + // requests 8-bit. + DWORD deviceFormats; + if ( channels + firstChannel == 2 ) { + deviceFormats = WAVE_FORMAT_1S08 | WAVE_FORMAT_2S08 | WAVE_FORMAT_4S08 | WAVE_FORMAT_96S08; + if ( format == RTAUDIO_SINT8 && inCaps.dwFormats & deviceFormats ) { + waveFormat.wBitsPerSample = 8; + stream_.deviceFormat[mode] = RTAUDIO_SINT8; + } + else { // assume 16-bit is supported + waveFormat.wBitsPerSample = 16; + stream_.deviceFormat[mode] = RTAUDIO_SINT16; + } + } + else { // channel == 1 + deviceFormats = WAVE_FORMAT_1M08 | WAVE_FORMAT_2M08 | WAVE_FORMAT_4M08 | WAVE_FORMAT_96M08; + if ( format == RTAUDIO_SINT8 && inCaps.dwFormats & deviceFormats ) { + waveFormat.wBitsPerSample = 8; + stream_.deviceFormat[mode] = RTAUDIO_SINT8; + } + else { // assume 16-bit is supported + waveFormat.wBitsPerSample = 16; + stream_.deviceFormat[mode] = RTAUDIO_SINT16; + } + } + stream_.userFormat = format; + + // Update wave format structure and buffer information. + waveFormat.nBlockAlign = waveFormat.nChannels * waveFormat.wBitsPerSample / 8; + waveFormat.nAvgBytesPerSec = waveFormat.nSamplesPerSec * waveFormat.nBlockAlign; + dsPointerLeadTime = nBuffers * (*bufferSize) * (waveFormat.wBitsPerSample / 8) * channels; + + // If the user wants an even bigger buffer, increase the device buffer size accordingly. + while ( dsPointerLeadTime * 2U > dsBufferSize ) + dsBufferSize *= 2; + + // Setup the secondary DS buffer description. + DSCBUFFERDESC bufferDescription; + ZeroMemory( &bufferDescription, sizeof( DSCBUFFERDESC ) ); + bufferDescription.dwSize = sizeof( DSCBUFFERDESC ); + bufferDescription.dwFlags = 0; + bufferDescription.dwReserved = 0; + bufferDescription.dwBufferBytes = dsBufferSize; + bufferDescription.lpwfxFormat = &waveFormat; + + // Create the capture buffer. + LPDIRECTSOUNDCAPTUREBUFFER buffer; + result = input->CreateCaptureBuffer( &bufferDescription, &buffer, NULL ); + if ( FAILED( result ) ) { + input->Release(); + errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") creating input buffer (" << dsDevices[ device ].name << ")!"; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Get the buffer size ... might be different from what we specified. + DSCBCAPS dscbcaps; + dscbcaps.dwSize = sizeof( DSCBCAPS ); + result = buffer->GetCaps( &dscbcaps ); + if ( FAILED( result ) ) { + input->Release(); + buffer->Release(); + errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") getting buffer settings (" << dsDevices[ device ].name << ")!"; + errorText_ = errorStream_.str(); + return FAILURE; + } + + dsBufferSize = dscbcaps.dwBufferBytes; + + // NOTE: We could have a problem here if this is a duplex stream + // and the play and capture hardware buffer sizes are different + // (I'm actually not sure if that is a problem or not). + // Currently, we are not verifying that. + + // Lock the capture buffer + LPVOID audioPtr; + DWORD dataLen; + result = buffer->Lock( 0, dsBufferSize, &audioPtr, &dataLen, NULL, NULL, 0 ); + if ( FAILED( result ) ) { + input->Release(); + buffer->Release(); + errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") locking input buffer (" << dsDevices[ device ].name << ")!"; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Zero the buffer + ZeroMemory( audioPtr, dataLen ); + + // Unlock the buffer + result = buffer->Unlock( audioPtr, dataLen, NULL, 0 ); + if ( FAILED( result ) ) { + input->Release(); + buffer->Release(); + errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") unlocking input buffer (" << dsDevices[ device ].name << ")!"; + errorText_ = errorStream_.str(); + return FAILURE; + } + + ohandle = (void *) input; + bhandle = (void *) buffer; + } + + // Set various stream parameters + DsHandle *handle = 0; + stream_.nDeviceChannels[mode] = channels + firstChannel; + stream_.nUserChannels[mode] = channels; + stream_.bufferSize = *bufferSize; + stream_.channelOffset[mode] = firstChannel; + stream_.deviceInterleaved[mode] = true; + if ( options && options->flags & RTAUDIO_NONINTERLEAVED ) stream_.userInterleaved = false; + else stream_.userInterleaved = true; + + // Set flag for buffer conversion + stream_.doConvertBuffer[mode] = false; + if (stream_.nUserChannels[mode] != stream_.nDeviceChannels[mode]) + stream_.doConvertBuffer[mode] = true; + if (stream_.userFormat != stream_.deviceFormat[mode]) + stream_.doConvertBuffer[mode] = true; + if ( stream_.userInterleaved != stream_.deviceInterleaved[mode] && + stream_.nUserChannels[mode] > 1 ) + stream_.doConvertBuffer[mode] = true; + + // Allocate necessary internal buffers + long bufferBytes = stream_.nUserChannels[mode] * *bufferSize * formatBytes( stream_.userFormat ); + stream_.userBuffer[mode] = (char *) calloc( bufferBytes, 1 ); + if ( stream_.userBuffer[mode] == NULL ) { + errorText_ = "RtApiDs::probeDeviceOpen: error allocating user buffer memory."; + goto error; + } + + if ( stream_.doConvertBuffer[mode] ) { + + bool makeBuffer = true; + bufferBytes = stream_.nDeviceChannels[mode] * formatBytes( stream_.deviceFormat[mode] ); + if ( mode == INPUT ) { + if ( stream_.mode == OUTPUT && stream_.deviceBuffer ) { + unsigned long bytesOut = stream_.nDeviceChannels[0] * formatBytes( stream_.deviceFormat[0] ); + if ( bufferBytes <= (long) bytesOut ) makeBuffer = false; + } + } + + if ( makeBuffer ) { + bufferBytes *= *bufferSize; + if ( stream_.deviceBuffer ) free( stream_.deviceBuffer ); + stream_.deviceBuffer = (char *) calloc( bufferBytes, 1 ); + if ( stream_.deviceBuffer == NULL ) { + errorText_ = "RtApiDs::probeDeviceOpen: error allocating device buffer memory."; + goto error; + } + } + } + + // Allocate our DsHandle structures for the stream. + if ( stream_.apiHandle == 0 ) { + try { + handle = new DsHandle; + } + catch ( std::bad_alloc& ) { + errorText_ = "RtApiDs::probeDeviceOpen: error allocating AsioHandle memory."; + goto error; + } + + // Create a manual-reset event. + handle->condition = CreateEvent( NULL, // no security + TRUE, // manual-reset + FALSE, // non-signaled initially + NULL ); // unnamed + stream_.apiHandle = (void *) handle; + } + else + handle = (DsHandle *) stream_.apiHandle; + handle->id[mode] = ohandle; + handle->buffer[mode] = bhandle; + handle->dsBufferSize[mode] = dsBufferSize; + handle->dsPointerLeadTime[mode] = dsPointerLeadTime; + + stream_.device[mode] = device; + stream_.state = STREAM_STOPPED; + if ( stream_.mode == OUTPUT && mode == INPUT ) + // We had already set up an output stream. + stream_.mode = DUPLEX; + else + stream_.mode = mode; + stream_.nBuffers = nBuffers; + stream_.sampleRate = sampleRate; + + // Setup the buffer conversion information structure. + if ( stream_.doConvertBuffer[mode] ) setConvertInfo( mode, firstChannel ); + + // Setup the callback thread. + if ( stream_.callbackInfo.isRunning == false ) { + unsigned threadId; + stream_.callbackInfo.isRunning = true; + stream_.callbackInfo.object = (void *) this; + stream_.callbackInfo.thread = _beginthreadex( NULL, 0, &callbackHandler, + &stream_.callbackInfo, 0, &threadId ); + if ( stream_.callbackInfo.thread == 0 ) { + errorText_ = "RtApiDs::probeDeviceOpen: error creating callback thread!"; + goto error; + } + + // Boost DS thread priority + SetThreadPriority( (HANDLE) stream_.callbackInfo.thread, THREAD_PRIORITY_HIGHEST ); + } + return SUCCESS; + + error: + if ( handle ) { + if ( handle->buffer[0] ) { // the object pointer can be NULL and valid + LPDIRECTSOUND object = (LPDIRECTSOUND) handle->id[0]; + LPDIRECTSOUNDBUFFER buffer = (LPDIRECTSOUNDBUFFER) handle->buffer[0]; + if ( buffer ) buffer->Release(); + object->Release(); + } + if ( handle->buffer[1] ) { + LPDIRECTSOUNDCAPTURE object = (LPDIRECTSOUNDCAPTURE) handle->id[1]; + LPDIRECTSOUNDCAPTUREBUFFER buffer = (LPDIRECTSOUNDCAPTUREBUFFER) handle->buffer[1]; + if ( buffer ) buffer->Release(); + object->Release(); + } + CloseHandle( handle->condition ); + delete handle; + stream_.apiHandle = 0; + } + + for ( int i=0; i<2; i++ ) { + if ( stream_.userBuffer[i] ) { + free( stream_.userBuffer[i] ); + stream_.userBuffer[i] = 0; + } + } + + if ( stream_.deviceBuffer ) { + free( stream_.deviceBuffer ); + stream_.deviceBuffer = 0; + } + + stream_.state = STREAM_CLOSED; + return FAILURE; +} + +void RtApiDs :: closeStream() +{ + if ( stream_.state == STREAM_CLOSED ) { + errorText_ = "RtApiDs::closeStream(): no open stream to close!"; + error( RtAudioError::WARNING ); + return; + } + + // Stop the callback thread. + stream_.callbackInfo.isRunning = false; + WaitForSingleObject( (HANDLE) stream_.callbackInfo.thread, INFINITE ); + CloseHandle( (HANDLE) stream_.callbackInfo.thread ); + + DsHandle *handle = (DsHandle *) stream_.apiHandle; + if ( handle ) { + if ( handle->buffer[0] ) { // the object pointer can be NULL and valid + LPDIRECTSOUND object = (LPDIRECTSOUND) handle->id[0]; + LPDIRECTSOUNDBUFFER buffer = (LPDIRECTSOUNDBUFFER) handle->buffer[0]; + if ( buffer ) { + buffer->Stop(); + buffer->Release(); + } + object->Release(); + } + if ( handle->buffer[1] ) { + LPDIRECTSOUNDCAPTURE object = (LPDIRECTSOUNDCAPTURE) handle->id[1]; + LPDIRECTSOUNDCAPTUREBUFFER buffer = (LPDIRECTSOUNDCAPTUREBUFFER) handle->buffer[1]; + if ( buffer ) { + buffer->Stop(); + buffer->Release(); + } + object->Release(); + } + CloseHandle( handle->condition ); + delete handle; + stream_.apiHandle = 0; + } + + for ( int i=0; i<2; i++ ) { + if ( stream_.userBuffer[i] ) { + free( stream_.userBuffer[i] ); + stream_.userBuffer[i] = 0; + } + } + + if ( stream_.deviceBuffer ) { + free( stream_.deviceBuffer ); + stream_.deviceBuffer = 0; + } + + stream_.mode = UNINITIALIZED; + stream_.state = STREAM_CLOSED; +} + +void RtApiDs :: startStream() +{ + verifyStream(); + if ( stream_.state == STREAM_RUNNING ) { + errorText_ = "RtApiDs::startStream(): the stream is already running!"; + error( RtAudioError::WARNING ); + return; + } + + DsHandle *handle = (DsHandle *) stream_.apiHandle; + + // Increase scheduler frequency on lesser windows (a side-effect of + // increasing timer accuracy). On greater windows (Win2K or later), + // this is already in effect. + timeBeginPeriod( 1 ); + + buffersRolling = false; + duplexPrerollBytes = 0; + + if ( stream_.mode == DUPLEX ) { + // 0.5 seconds of silence in DUPLEX mode while the devices spin up and synchronize. + duplexPrerollBytes = (int) ( 0.5 * stream_.sampleRate * formatBytes( stream_.deviceFormat[1] ) * stream_.nDeviceChannels[1] ); + } + + HRESULT result = 0; + if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { + + LPDIRECTSOUNDBUFFER buffer = (LPDIRECTSOUNDBUFFER) handle->buffer[0]; + result = buffer->Play( 0, 0, DSBPLAY_LOOPING ); + if ( FAILED( result ) ) { + errorStream_ << "RtApiDs::startStream: error (" << getErrorString( result ) << ") starting output buffer!"; + errorText_ = errorStream_.str(); + goto unlock; + } + } + + if ( stream_.mode == INPUT || stream_.mode == DUPLEX ) { + + LPDIRECTSOUNDCAPTUREBUFFER buffer = (LPDIRECTSOUNDCAPTUREBUFFER) handle->buffer[1]; + result = buffer->Start( DSCBSTART_LOOPING ); + if ( FAILED( result ) ) { + errorStream_ << "RtApiDs::startStream: error (" << getErrorString( result ) << ") starting input buffer!"; + errorText_ = errorStream_.str(); + goto unlock; + } + } + + handle->drainCounter = 0; + handle->internalDrain = false; + ResetEvent( handle->condition ); + stream_.state = STREAM_RUNNING; + + unlock: + if ( FAILED( result ) ) error( RtAudioError::SYSTEM_ERROR ); +} + +void RtApiDs :: stopStream() +{ + verifyStream(); + if ( stream_.state == STREAM_STOPPED ) { + errorText_ = "RtApiDs::stopStream(): the stream is already stopped!"; + error( RtAudioError::WARNING ); + return; + } + + HRESULT result = 0; + LPVOID audioPtr; + DWORD dataLen; + DsHandle *handle = (DsHandle *) stream_.apiHandle; + if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { + if ( handle->drainCounter == 0 ) { + handle->drainCounter = 2; + WaitForSingleObject( handle->condition, INFINITE ); // block until signaled + } + + stream_.state = STREAM_STOPPED; + + MUTEX_LOCK( &stream_.mutex ); + + // Stop the buffer and clear memory + LPDIRECTSOUNDBUFFER buffer = (LPDIRECTSOUNDBUFFER) handle->buffer[0]; + result = buffer->Stop(); + if ( FAILED( result ) ) { + errorStream_ << "RtApiDs::stopStream: error (" << getErrorString( result ) << ") stopping output buffer!"; + errorText_ = errorStream_.str(); + goto unlock; + } + + // Lock the buffer and clear it so that if we start to play again, + // we won't have old data playing. + result = buffer->Lock( 0, handle->dsBufferSize[0], &audioPtr, &dataLen, NULL, NULL, 0 ); + if ( FAILED( result ) ) { + errorStream_ << "RtApiDs::stopStream: error (" << getErrorString( result ) << ") locking output buffer!"; + errorText_ = errorStream_.str(); + goto unlock; + } + + // Zero the DS buffer + ZeroMemory( audioPtr, dataLen ); + + // Unlock the DS buffer + result = buffer->Unlock( audioPtr, dataLen, NULL, 0 ); + if ( FAILED( result ) ) { + errorStream_ << "RtApiDs::stopStream: error (" << getErrorString( result ) << ") unlocking output buffer!"; + errorText_ = errorStream_.str(); + goto unlock; + } + + // If we start playing again, we must begin at beginning of buffer. + handle->bufferPointer[0] = 0; + } + + if ( stream_.mode == INPUT || stream_.mode == DUPLEX ) { + LPDIRECTSOUNDCAPTUREBUFFER buffer = (LPDIRECTSOUNDCAPTUREBUFFER) handle->buffer[1]; + audioPtr = NULL; + dataLen = 0; + + stream_.state = STREAM_STOPPED; + + if ( stream_.mode != DUPLEX ) + MUTEX_LOCK( &stream_.mutex ); + + result = buffer->Stop(); + if ( FAILED( result ) ) { + errorStream_ << "RtApiDs::stopStream: error (" << getErrorString( result ) << ") stopping input buffer!"; + errorText_ = errorStream_.str(); + goto unlock; + } + + // Lock the buffer and clear it so that if we start to play again, + // we won't have old data playing. + result = buffer->Lock( 0, handle->dsBufferSize[1], &audioPtr, &dataLen, NULL, NULL, 0 ); + if ( FAILED( result ) ) { + errorStream_ << "RtApiDs::stopStream: error (" << getErrorString( result ) << ") locking input buffer!"; + errorText_ = errorStream_.str(); + goto unlock; + } + + // Zero the DS buffer + ZeroMemory( audioPtr, dataLen ); + + // Unlock the DS buffer + result = buffer->Unlock( audioPtr, dataLen, NULL, 0 ); + if ( FAILED( result ) ) { + errorStream_ << "RtApiDs::stopStream: error (" << getErrorString( result ) << ") unlocking input buffer!"; + errorText_ = errorStream_.str(); + goto unlock; + } + + // If we start recording again, we must begin at beginning of buffer. + handle->bufferPointer[1] = 0; + } + + unlock: + timeEndPeriod( 1 ); // revert to normal scheduler frequency on lesser windows. + MUTEX_UNLOCK( &stream_.mutex ); + + if ( FAILED( result ) ) error( RtAudioError::SYSTEM_ERROR ); +} + +void RtApiDs :: abortStream() +{ + verifyStream(); + if ( stream_.state == STREAM_STOPPED ) { + errorText_ = "RtApiDs::abortStream(): the stream is already stopped!"; + error( RtAudioError::WARNING ); + return; + } + + DsHandle *handle = (DsHandle *) stream_.apiHandle; + handle->drainCounter = 2; + + stopStream(); +} + +void RtApiDs :: callbackEvent() +{ + if ( stream_.state == STREAM_STOPPED || stream_.state == STREAM_STOPPING ) { + Sleep( 50 ); // sleep 50 milliseconds + return; + } + + if ( stream_.state == STREAM_CLOSED ) { + errorText_ = "RtApiDs::callbackEvent(): the stream is closed ... this shouldn't happen!"; + error( RtAudioError::WARNING ); + return; + } + + CallbackInfo *info = (CallbackInfo *) &stream_.callbackInfo; + DsHandle *handle = (DsHandle *) stream_.apiHandle; + + // Check if we were draining the stream and signal is finished. + if ( handle->drainCounter > stream_.nBuffers + 2 ) { + + stream_.state = STREAM_STOPPING; + if ( handle->internalDrain == false ) + SetEvent( handle->condition ); + else + stopStream(); + return; + } + + // Invoke user callback to get fresh output data UNLESS we are + // draining stream. + if ( handle->drainCounter == 0 ) { + RtAudioCallback callback = (RtAudioCallback) info->callback; + double streamTime = getStreamTime(); + RtAudioStreamStatus status = 0; + if ( stream_.mode != INPUT && handle->xrun[0] == true ) { + status |= RTAUDIO_OUTPUT_UNDERFLOW; + handle->xrun[0] = false; + } + if ( stream_.mode != OUTPUT && handle->xrun[1] == true ) { + status |= RTAUDIO_INPUT_OVERFLOW; + handle->xrun[1] = false; + } + int cbReturnValue = callback( stream_.userBuffer[0], stream_.userBuffer[1], + stream_.bufferSize, streamTime, status, info->userData ); + if ( cbReturnValue == 2 ) { + stream_.state = STREAM_STOPPING; + handle->drainCounter = 2; + abortStream(); + return; + } + else if ( cbReturnValue == 1 ) { + handle->drainCounter = 1; + handle->internalDrain = true; + } + } + + HRESULT result; + DWORD currentWritePointer, safeWritePointer; + DWORD currentReadPointer, safeReadPointer; + UINT nextWritePointer; + + LPVOID buffer1 = NULL; + LPVOID buffer2 = NULL; + DWORD bufferSize1 = 0; + DWORD bufferSize2 = 0; + + char *buffer; + long bufferBytes; + + MUTEX_LOCK( &stream_.mutex ); + if ( stream_.state == STREAM_STOPPED ) { + MUTEX_UNLOCK( &stream_.mutex ); + return; + } + + if ( buffersRolling == false ) { + if ( stream_.mode == DUPLEX ) { + //assert( handle->dsBufferSize[0] == handle->dsBufferSize[1] ); + + // It takes a while for the devices to get rolling. As a result, + // there's no guarantee that the capture and write device pointers + // will move in lockstep. Wait here for both devices to start + // rolling, and then set our buffer pointers accordingly. + // e.g. Crystal Drivers: the capture buffer starts up 5700 to 9600 + // bytes later than the write buffer. + + // Stub: a serious risk of having a pre-emptive scheduling round + // take place between the two GetCurrentPosition calls... but I'm + // really not sure how to solve the problem. Temporarily boost to + // Realtime priority, maybe; but I'm not sure what priority the + // DirectSound service threads run at. We *should* be roughly + // within a ms or so of correct. + + LPDIRECTSOUNDBUFFER dsWriteBuffer = (LPDIRECTSOUNDBUFFER) handle->buffer[0]; + LPDIRECTSOUNDCAPTUREBUFFER dsCaptureBuffer = (LPDIRECTSOUNDCAPTUREBUFFER) handle->buffer[1]; + + DWORD startSafeWritePointer, startSafeReadPointer; + + result = dsWriteBuffer->GetCurrentPosition( NULL, &startSafeWritePointer ); + if ( FAILED( result ) ) { + errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current write position!"; + errorText_ = errorStream_.str(); + error( RtAudioError::SYSTEM_ERROR ); + return; + } + result = dsCaptureBuffer->GetCurrentPosition( NULL, &startSafeReadPointer ); + if ( FAILED( result ) ) { + errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current read position!"; + errorText_ = errorStream_.str(); + error( RtAudioError::SYSTEM_ERROR ); + return; + } + while ( true ) { + result = dsWriteBuffer->GetCurrentPosition( NULL, &safeWritePointer ); + if ( FAILED( result ) ) { + errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current write position!"; + errorText_ = errorStream_.str(); + error( RtAudioError::SYSTEM_ERROR ); + return; + } + result = dsCaptureBuffer->GetCurrentPosition( NULL, &safeReadPointer ); + if ( FAILED( result ) ) { + errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current read position!"; + errorText_ = errorStream_.str(); + error( RtAudioError::SYSTEM_ERROR ); + return; + } + if ( safeWritePointer != startSafeWritePointer && safeReadPointer != startSafeReadPointer ) break; + Sleep( 1 ); + } + + //assert( handle->dsBufferSize[0] == handle->dsBufferSize[1] ); + + handle->bufferPointer[0] = safeWritePointer + handle->dsPointerLeadTime[0]; + if ( handle->bufferPointer[0] >= handle->dsBufferSize[0] ) handle->bufferPointer[0] -= handle->dsBufferSize[0]; + handle->bufferPointer[1] = safeReadPointer; + } + else if ( stream_.mode == OUTPUT ) { + + // Set the proper nextWritePosition after initial startup. + LPDIRECTSOUNDBUFFER dsWriteBuffer = (LPDIRECTSOUNDBUFFER) handle->buffer[0]; + result = dsWriteBuffer->GetCurrentPosition( ¤tWritePointer, &safeWritePointer ); + if ( FAILED( result ) ) { + errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current write position!"; + errorText_ = errorStream_.str(); + error( RtAudioError::SYSTEM_ERROR ); + return; + } + handle->bufferPointer[0] = safeWritePointer + handle->dsPointerLeadTime[0]; + if ( handle->bufferPointer[0] >= handle->dsBufferSize[0] ) handle->bufferPointer[0] -= handle->dsBufferSize[0]; + } + + buffersRolling = true; + } + + if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { + + LPDIRECTSOUNDBUFFER dsBuffer = (LPDIRECTSOUNDBUFFER) handle->buffer[0]; + + if ( handle->drainCounter > 1 ) { // write zeros to the output stream + bufferBytes = stream_.bufferSize * stream_.nUserChannels[0]; + bufferBytes *= formatBytes( stream_.userFormat ); + memset( stream_.userBuffer[0], 0, bufferBytes ); + } + + // Setup parameters and do buffer conversion if necessary. + if ( stream_.doConvertBuffer[0] ) { + buffer = stream_.deviceBuffer; + convertBuffer( buffer, stream_.userBuffer[0], stream_.convertInfo[0] ); + bufferBytes = stream_.bufferSize * stream_.nDeviceChannels[0]; + bufferBytes *= formatBytes( stream_.deviceFormat[0] ); + } + else { + buffer = stream_.userBuffer[0]; + bufferBytes = stream_.bufferSize * stream_.nUserChannels[0]; + bufferBytes *= formatBytes( stream_.userFormat ); + } + + // No byte swapping necessary in DirectSound implementation. + + // Ahhh ... windoze. 16-bit data is signed but 8-bit data is + // unsigned. So, we need to convert our signed 8-bit data here to + // unsigned. + if ( stream_.deviceFormat[0] == RTAUDIO_SINT8 ) + for ( int i=0; idsBufferSize[0]; + nextWritePointer = handle->bufferPointer[0]; + + DWORD endWrite, leadPointer; + while ( true ) { + // Find out where the read and "safe write" pointers are. + result = dsBuffer->GetCurrentPosition( ¤tWritePointer, &safeWritePointer ); + if ( FAILED( result ) ) { + errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current write position!"; + errorText_ = errorStream_.str(); + error( RtAudioError::SYSTEM_ERROR ); + return; + } + + // We will copy our output buffer into the region between + // safeWritePointer and leadPointer. If leadPointer is not + // beyond the next endWrite position, wait until it is. + leadPointer = safeWritePointer + handle->dsPointerLeadTime[0]; + //std::cout << "safeWritePointer = " << safeWritePointer << ", leadPointer = " << leadPointer << ", nextWritePointer = " << nextWritePointer << std::endl; + if ( leadPointer > dsBufferSize ) leadPointer -= dsBufferSize; + if ( leadPointer < nextWritePointer ) leadPointer += dsBufferSize; // unwrap offset + endWrite = nextWritePointer + bufferBytes; + + // Check whether the entire write region is behind the play pointer. + if ( leadPointer >= endWrite ) break; + + // If we are here, then we must wait until the leadPointer advances + // beyond the end of our next write region. We use the + // Sleep() function to suspend operation until that happens. + double millis = ( endWrite - leadPointer ) * 1000.0; + millis /= ( formatBytes( stream_.deviceFormat[0]) * stream_.nDeviceChannels[0] * stream_.sampleRate); + if ( millis < 1.0 ) millis = 1.0; + Sleep( (DWORD) millis ); + } + + if ( dsPointerBetween( nextWritePointer, safeWritePointer, currentWritePointer, dsBufferSize ) + || dsPointerBetween( endWrite, safeWritePointer, currentWritePointer, dsBufferSize ) ) { + // We've strayed into the forbidden zone ... resync the read pointer. + handle->xrun[0] = true; + nextWritePointer = safeWritePointer + handle->dsPointerLeadTime[0] - bufferBytes; + if ( nextWritePointer >= dsBufferSize ) nextWritePointer -= dsBufferSize; + handle->bufferPointer[0] = nextWritePointer; + endWrite = nextWritePointer + bufferBytes; + } + + // Lock free space in the buffer + result = dsBuffer->Lock( nextWritePointer, bufferBytes, &buffer1, + &bufferSize1, &buffer2, &bufferSize2, 0 ); + if ( FAILED( result ) ) { + errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") locking buffer during playback!"; + errorText_ = errorStream_.str(); + error( RtAudioError::SYSTEM_ERROR ); + return; + } + + // Copy our buffer into the DS buffer + CopyMemory( buffer1, buffer, bufferSize1 ); + if ( buffer2 != NULL ) CopyMemory( buffer2, buffer+bufferSize1, bufferSize2 ); + + // Update our buffer offset and unlock sound buffer + dsBuffer->Unlock( buffer1, bufferSize1, buffer2, bufferSize2 ); + if ( FAILED( result ) ) { + errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") unlocking buffer during playback!"; + errorText_ = errorStream_.str(); + error( RtAudioError::SYSTEM_ERROR ); + return; + } + nextWritePointer = ( nextWritePointer + bufferSize1 + bufferSize2 ) % dsBufferSize; + handle->bufferPointer[0] = nextWritePointer; + } + + // Don't bother draining input + if ( handle->drainCounter ) { + handle->drainCounter++; + goto unlock; + } + + if ( stream_.mode == INPUT || stream_.mode == DUPLEX ) { + + // Setup parameters. + if ( stream_.doConvertBuffer[1] ) { + buffer = stream_.deviceBuffer; + bufferBytes = stream_.bufferSize * stream_.nDeviceChannels[1]; + bufferBytes *= formatBytes( stream_.deviceFormat[1] ); + } + else { + buffer = stream_.userBuffer[1]; + bufferBytes = stream_.bufferSize * stream_.nUserChannels[1]; + bufferBytes *= formatBytes( stream_.userFormat ); + } + + LPDIRECTSOUNDCAPTUREBUFFER dsBuffer = (LPDIRECTSOUNDCAPTUREBUFFER) handle->buffer[1]; + long nextReadPointer = handle->bufferPointer[1]; + DWORD dsBufferSize = handle->dsBufferSize[1]; + + // Find out where the write and "safe read" pointers are. + result = dsBuffer->GetCurrentPosition( ¤tReadPointer, &safeReadPointer ); + if ( FAILED( result ) ) { + errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current read position!"; + errorText_ = errorStream_.str(); + error( RtAudioError::SYSTEM_ERROR ); + return; + } + + if ( safeReadPointer < (DWORD)nextReadPointer ) safeReadPointer += dsBufferSize; // unwrap offset + DWORD endRead = nextReadPointer + bufferBytes; + + // Handling depends on whether we are INPUT or DUPLEX. + // If we're in INPUT mode then waiting is a good thing. If we're in DUPLEX mode, + // then a wait here will drag the write pointers into the forbidden zone. + // + // In DUPLEX mode, rather than wait, we will back off the read pointer until + // it's in a safe position. This causes dropouts, but it seems to be the only + // practical way to sync up the read and write pointers reliably, given the + // the very complex relationship between phase and increment of the read and write + // pointers. + // + // In order to minimize audible dropouts in DUPLEX mode, we will + // provide a pre-roll period of 0.5 seconds in which we return + // zeros from the read buffer while the pointers sync up. + + if ( stream_.mode == DUPLEX ) { + if ( safeReadPointer < endRead ) { + if ( duplexPrerollBytes <= 0 ) { + // Pre-roll time over. Be more agressive. + int adjustment = endRead-safeReadPointer; + + handle->xrun[1] = true; + // Two cases: + // - large adjustments: we've probably run out of CPU cycles, so just resync exactly, + // and perform fine adjustments later. + // - small adjustments: back off by twice as much. + if ( adjustment >= 2*bufferBytes ) + nextReadPointer = safeReadPointer-2*bufferBytes; + else + nextReadPointer = safeReadPointer-bufferBytes-adjustment; + + if ( nextReadPointer < 0 ) nextReadPointer += dsBufferSize; + + } + else { + // In pre=roll time. Just do it. + nextReadPointer = safeReadPointer - bufferBytes; + while ( nextReadPointer < 0 ) nextReadPointer += dsBufferSize; + } + endRead = nextReadPointer + bufferBytes; + } + } + else { // mode == INPUT + while ( safeReadPointer < endRead && stream_.callbackInfo.isRunning ) { + // See comments for playback. + double millis = (endRead - safeReadPointer) * 1000.0; + millis /= ( formatBytes(stream_.deviceFormat[1]) * stream_.nDeviceChannels[1] * stream_.sampleRate); + if ( millis < 1.0 ) millis = 1.0; + Sleep( (DWORD) millis ); + + // Wake up and find out where we are now. + result = dsBuffer->GetCurrentPosition( ¤tReadPointer, &safeReadPointer ); + if ( FAILED( result ) ) { + errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current read position!"; + errorText_ = errorStream_.str(); + error( RtAudioError::SYSTEM_ERROR ); + return; + } + + if ( safeReadPointer < (DWORD)nextReadPointer ) safeReadPointer += dsBufferSize; // unwrap offset + } + } + + // Lock free space in the buffer + result = dsBuffer->Lock( nextReadPointer, bufferBytes, &buffer1, + &bufferSize1, &buffer2, &bufferSize2, 0 ); + if ( FAILED( result ) ) { + errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") locking capture buffer!"; + errorText_ = errorStream_.str(); + error( RtAudioError::SYSTEM_ERROR ); + return; + } + + if ( duplexPrerollBytes <= 0 ) { + // Copy our buffer into the DS buffer + CopyMemory( buffer, buffer1, bufferSize1 ); + if ( buffer2 != NULL ) CopyMemory( buffer+bufferSize1, buffer2, bufferSize2 ); + } + else { + memset( buffer, 0, bufferSize1 ); + if ( buffer2 != NULL ) memset( buffer + bufferSize1, 0, bufferSize2 ); + duplexPrerollBytes -= bufferSize1 + bufferSize2; + } + + // Update our buffer offset and unlock sound buffer + nextReadPointer = ( nextReadPointer + bufferSize1 + bufferSize2 ) % dsBufferSize; + dsBuffer->Unlock( buffer1, bufferSize1, buffer2, bufferSize2 ); + if ( FAILED( result ) ) { + errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") unlocking capture buffer!"; + errorText_ = errorStream_.str(); + error( RtAudioError::SYSTEM_ERROR ); + return; + } + handle->bufferPointer[1] = nextReadPointer; + + // No byte swapping necessary in DirectSound implementation. + + // If necessary, convert 8-bit data from unsigned to signed. + if ( stream_.deviceFormat[1] == RTAUDIO_SINT8 ) + for ( int j=0; jobject; + bool* isRunning = &info->isRunning; + + while ( *isRunning == true ) { + object->callbackEvent(); + } + + _endthreadex( 0 ); + return 0; +} + +#include "tchar.h" + +static std::string convertTChar( LPCTSTR name ) +{ +#if defined( UNICODE ) || defined( _UNICODE ) + int length = WideCharToMultiByte(CP_UTF8, 0, name, -1, NULL, 0, NULL, NULL); + std::string s( length-1, '\0' ); + WideCharToMultiByte(CP_UTF8, 0, name, -1, &s[0], length, NULL, NULL); +#else + std::string s( name ); +#endif + + return s; +} + +static BOOL CALLBACK deviceQueryCallback( LPGUID lpguid, + LPCTSTR description, + LPCTSTR /*module*/, + LPVOID lpContext ) +{ + struct DsProbeData& probeInfo = *(struct DsProbeData*) lpContext; + std::vector& dsDevices = *probeInfo.dsDevices; + + HRESULT hr; + bool validDevice = false; + if ( probeInfo.isInput == true ) { + DSCCAPS caps; + LPDIRECTSOUNDCAPTURE object; + + hr = DirectSoundCaptureCreate( lpguid, &object, NULL ); + if ( hr != DS_OK ) return TRUE; + + caps.dwSize = sizeof(caps); + hr = object->GetCaps( &caps ); + if ( hr == DS_OK ) { + if ( caps.dwChannels > 0 && caps.dwFormats > 0 ) + validDevice = true; + } + object->Release(); + } + else { + DSCAPS caps; + LPDIRECTSOUND object; + hr = DirectSoundCreate( lpguid, &object, NULL ); + if ( hr != DS_OK ) return TRUE; + + caps.dwSize = sizeof(caps); + hr = object->GetCaps( &caps ); + if ( hr == DS_OK ) { + if ( caps.dwFlags & DSCAPS_PRIMARYMONO || caps.dwFlags & DSCAPS_PRIMARYSTEREO ) + validDevice = true; + } + object->Release(); + } + + // If good device, then save its name and guid. + std::string name = convertTChar( description ); + //if ( name == "Primary Sound Driver" || name == "Primary Sound Capture Driver" ) + if ( lpguid == NULL ) + name = "Default Device"; + if ( validDevice ) { + for ( unsigned int i=0; i +#include + + // A structure to hold various information related to the ALSA API + // implementation. +struct AlsaHandle { + snd_pcm_t *handles[2]; + bool synchronized; + bool xrun[2]; + pthread_cond_t runnable_cv; + bool runnable; + + AlsaHandle() + :synchronized(false), runnable(false) { xrun[0] = false; xrun[1] = false; } +}; + +static void *alsaCallbackHandler( void * ptr ); + +RtApiAlsa :: RtApiAlsa() +{ + // Nothing to do here. +} + +RtApiAlsa :: ~RtApiAlsa() +{ + if ( stream_.state != STREAM_CLOSED ) closeStream(); +} + +unsigned int RtApiAlsa :: getDeviceCount( void ) +{ + unsigned nDevices = 0; + int result, subdevice, card; + char name[64]; + snd_ctl_t *handle; + + // Count cards and devices + card = -1; + snd_card_next( &card ); + while ( card >= 0 ) { + sprintf( name, "hw:%d", card ); + result = snd_ctl_open( &handle, name, 0 ); + if ( result < 0 ) { + errorStream_ << "RtApiAlsa::getDeviceCount: control open, card = " << card << ", " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + goto nextcard; + } + subdevice = -1; + while( 1 ) { + result = snd_ctl_pcm_next_device( handle, &subdevice ); + if ( result < 0 ) { + errorStream_ << "RtApiAlsa::getDeviceCount: control next device, card = " << card << ", " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + break; + } + if ( subdevice < 0 ) + break; + nDevices++; + } + nextcard: + snd_ctl_close( handle ); + snd_card_next( &card ); + } + + result = snd_ctl_open( &handle, "default", 0 ); + if (result == 0) { + nDevices++; + snd_ctl_close( handle ); + } + + return nDevices; +} + +RtAudio::DeviceInfo RtApiAlsa :: getDeviceInfo( unsigned int device ) +{ + RtAudio::DeviceInfo info; + info.probed = false; + + unsigned nDevices = 0; + int result, subdevice, card; + char name[64]; + snd_ctl_t *chandle; + + // Count cards and devices + card = -1; + snd_card_next( &card ); + while ( card >= 0 ) { + sprintf( name, "hw:%d", card ); + result = snd_ctl_open( &chandle, name, SND_CTL_NONBLOCK ); + if ( result < 0 ) { + errorStream_ << "RtApiAlsa::getDeviceInfo: control open, card = " << card << ", " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + goto nextcard; + } + subdevice = -1; + while( 1 ) { + result = snd_ctl_pcm_next_device( chandle, &subdevice ); + if ( result < 0 ) { + errorStream_ << "RtApiAlsa::getDeviceInfo: control next device, card = " << card << ", " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + break; + } + if ( subdevice < 0 ) break; + if ( nDevices == device ) { + sprintf( name, "hw:%d,%d", card, subdevice ); + goto foundDevice; + } + nDevices++; + } + nextcard: + snd_ctl_close( chandle ); + snd_card_next( &card ); + } + + result = snd_ctl_open( &chandle, "default", SND_CTL_NONBLOCK ); + if ( result == 0 ) { + if ( nDevices == device ) { + strcpy( name, "default" ); + goto foundDevice; + } + nDevices++; + } + + if ( nDevices == 0 ) { + errorText_ = "RtApiAlsa::getDeviceInfo: no devices found!"; + error( RtAudioError::INVALID_USE ); + return info; + } + + if ( device >= nDevices ) { + errorText_ = "RtApiAlsa::getDeviceInfo: device ID is invalid!"; + error( RtAudioError::INVALID_USE ); + return info; + } + + foundDevice: + + // If a stream is already open, we cannot probe the stream devices. + // Thus, use the saved results. + if ( stream_.state != STREAM_CLOSED && + ( stream_.device[0] == device || stream_.device[1] == device ) ) { + snd_ctl_close( chandle ); + if ( device >= devices_.size() ) { + errorText_ = "RtApiAlsa::getDeviceInfo: device ID was not present before stream was opened."; + error( RtAudioError::WARNING ); + return info; + } + return devices_[ device ]; + } + + int openMode = SND_PCM_ASYNC; + snd_pcm_stream_t stream; + snd_pcm_info_t *pcminfo; + snd_pcm_info_alloca( &pcminfo ); + snd_pcm_t *phandle; + snd_pcm_hw_params_t *params; + snd_pcm_hw_params_alloca( ¶ms ); + + // First try for playback unless default device (which has subdev -1) + stream = SND_PCM_STREAM_PLAYBACK; + snd_pcm_info_set_stream( pcminfo, stream ); + if ( subdevice != -1 ) { + snd_pcm_info_set_device( pcminfo, subdevice ); + snd_pcm_info_set_subdevice( pcminfo, 0 ); + + result = snd_ctl_pcm_info( chandle, pcminfo ); + if ( result < 0 ) { + // Device probably doesn't support playback. + goto captureProbe; + } + } + + result = snd_pcm_open( &phandle, name, stream, openMode | SND_PCM_NONBLOCK ); + if ( result < 0 ) { + errorStream_ << "RtApiAlsa::getDeviceInfo: snd_pcm_open error for device (" << name << "), " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + goto captureProbe; + } + + // The device is open ... fill the parameter structure. + result = snd_pcm_hw_params_any( phandle, params ); + if ( result < 0 ) { + snd_pcm_close( phandle ); + errorStream_ << "RtApiAlsa::getDeviceInfo: snd_pcm_hw_params error for device (" << name << "), " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + goto captureProbe; + } + + // Get output channel information. + unsigned int value; + result = snd_pcm_hw_params_get_channels_max( params, &value ); + if ( result < 0 ) { + snd_pcm_close( phandle ); + errorStream_ << "RtApiAlsa::getDeviceInfo: error getting device (" << name << ") output channels, " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + goto captureProbe; + } + info.outputChannels = value; + snd_pcm_close( phandle ); + + captureProbe: + stream = SND_PCM_STREAM_CAPTURE; + snd_pcm_info_set_stream( pcminfo, stream ); + + // Now try for capture unless default device (with subdev = -1) + if ( subdevice != -1 ) { + result = snd_ctl_pcm_info( chandle, pcminfo ); + snd_ctl_close( chandle ); + if ( result < 0 ) { + // Device probably doesn't support capture. + if ( info.outputChannels == 0 ) return info; + goto probeParameters; + } + } + else + snd_ctl_close( chandle ); + + result = snd_pcm_open( &phandle, name, stream, openMode | SND_PCM_NONBLOCK); + if ( result < 0 ) { + errorStream_ << "RtApiAlsa::getDeviceInfo: snd_pcm_open error for device (" << name << "), " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + if ( info.outputChannels == 0 ) return info; + goto probeParameters; + } + + // The device is open ... fill the parameter structure. + result = snd_pcm_hw_params_any( phandle, params ); + if ( result < 0 ) { + snd_pcm_close( phandle ); + errorStream_ << "RtApiAlsa::getDeviceInfo: snd_pcm_hw_params error for device (" << name << "), " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + if ( info.outputChannels == 0 ) return info; + goto probeParameters; + } + + result = snd_pcm_hw_params_get_channels_max( params, &value ); + if ( result < 0 ) { + snd_pcm_close( phandle ); + errorStream_ << "RtApiAlsa::getDeviceInfo: error getting device (" << name << ") input channels, " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + if ( info.outputChannels == 0 ) return info; + goto probeParameters; + } + info.inputChannels = value; + snd_pcm_close( phandle ); + + // If device opens for both playback and capture, we determine the channels. + if ( info.outputChannels > 0 && info.inputChannels > 0 ) + info.duplexChannels = (info.outputChannels > info.inputChannels) ? info.inputChannels : info.outputChannels; + + // ALSA doesn't provide default devices so we'll use the first available one. + if ( device == 0 && info.outputChannels > 0 ) + info.isDefaultOutput = true; + if ( device == 0 && info.inputChannels > 0 ) + info.isDefaultInput = true; + + probeParameters: + // At this point, we just need to figure out the supported data + // formats and sample rates. We'll proceed by opening the device in + // the direction with the maximum number of channels, or playback if + // they are equal. This might limit our sample rate options, but so + // be it. + + if ( info.outputChannels >= info.inputChannels ) + stream = SND_PCM_STREAM_PLAYBACK; + else + stream = SND_PCM_STREAM_CAPTURE; + snd_pcm_info_set_stream( pcminfo, stream ); + + result = snd_pcm_open( &phandle, name, stream, openMode | SND_PCM_NONBLOCK); + if ( result < 0 ) { + errorStream_ << "RtApiAlsa::getDeviceInfo: snd_pcm_open error for device (" << name << "), " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + return info; + } + + // The device is open ... fill the parameter structure. + result = snd_pcm_hw_params_any( phandle, params ); + if ( result < 0 ) { + snd_pcm_close( phandle ); + errorStream_ << "RtApiAlsa::getDeviceInfo: snd_pcm_hw_params error for device (" << name << "), " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + return info; + } + + // Test our discrete set of sample rate values. + info.sampleRates.clear(); + for ( unsigned int i=0; i= 0 ) { + sprintf( name, "hw:%s,%d", cardname, subdevice ); + free( cardname ); + } + info.name = name; + + // That's all ... close the device and return + snd_pcm_close( phandle ); + info.probed = true; + return info; +} + +void RtApiAlsa :: saveDeviceInfo( void ) +{ + devices_.clear(); + + unsigned int nDevices = getDeviceCount(); + devices_.resize( nDevices ); + for ( unsigned int i=0; iflags & RTAUDIO_ALSA_USE_DEFAULT ) + snprintf(name, sizeof(name), "%s", "default"); + else { + // Count cards and devices + card = -1; + snd_card_next( &card ); + while ( card >= 0 ) { + sprintf( name, "hw:%d", card ); + result = snd_ctl_open( &chandle, name, SND_CTL_NONBLOCK ); + if ( result < 0 ) { + errorStream_ << "RtApiAlsa::probeDeviceOpen: control open, card = " << card << ", " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + return FAILURE; + } + subdevice = -1; + while( 1 ) { + result = snd_ctl_pcm_next_device( chandle, &subdevice ); + if ( result < 0 ) break; + if ( subdevice < 0 ) break; + if ( nDevices == device ) { + sprintf( name, "hw:%d,%d", card, subdevice ); + snd_ctl_close( chandle ); + goto foundDevice; + } + nDevices++; + } + snd_ctl_close( chandle ); + snd_card_next( &card ); + } + + result = snd_ctl_open( &chandle, "default", SND_CTL_NONBLOCK ); + if ( result == 0 ) { + if ( nDevices == device ) { + strcpy( name, "default" ); + goto foundDevice; + } + nDevices++; + } + + if ( nDevices == 0 ) { + // This should not happen because a check is made before this function is called. + errorText_ = "RtApiAlsa::probeDeviceOpen: no devices found!"; + return FAILURE; + } + + if ( device >= nDevices ) { + // This should not happen because a check is made before this function is called. + errorText_ = "RtApiAlsa::probeDeviceOpen: device ID is invalid!"; + return FAILURE; + } + } + + foundDevice: + + // The getDeviceInfo() function will not work for a device that is + // already open. Thus, we'll probe the system before opening a + // stream and save the results for use by getDeviceInfo(). + if ( mode == OUTPUT || ( mode == INPUT && stream_.mode != OUTPUT ) ) // only do once + this->saveDeviceInfo(); + + snd_pcm_stream_t stream; + if ( mode == OUTPUT ) + stream = SND_PCM_STREAM_PLAYBACK; + else + stream = SND_PCM_STREAM_CAPTURE; + + snd_pcm_t *phandle; + int openMode = SND_PCM_ASYNC; + result = snd_pcm_open( &phandle, name, stream, openMode ); + if ( result < 0 ) { + if ( mode == OUTPUT ) + errorStream_ << "RtApiAlsa::probeDeviceOpen: pcm device (" << name << ") won't open for output."; + else + errorStream_ << "RtApiAlsa::probeDeviceOpen: pcm device (" << name << ") won't open for input."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Fill the parameter structure. + snd_pcm_hw_params_t *hw_params; + snd_pcm_hw_params_alloca( &hw_params ); + result = snd_pcm_hw_params_any( phandle, hw_params ); + if ( result < 0 ) { + snd_pcm_close( phandle ); + errorStream_ << "RtApiAlsa::probeDeviceOpen: error getting pcm device (" << name << ") parameters, " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + return FAILURE; + } + +#if defined(__RTAUDIO_DEBUG__) + fprintf( stderr, "\nRtApiAlsa: dump hardware params just after device open:\n\n" ); + snd_pcm_hw_params_dump( hw_params, out ); +#endif + + // Set access ... check user preference. + if ( options && options->flags & RTAUDIO_NONINTERLEAVED ) { + stream_.userInterleaved = false; + result = snd_pcm_hw_params_set_access( phandle, hw_params, SND_PCM_ACCESS_RW_NONINTERLEAVED ); + if ( result < 0 ) { + result = snd_pcm_hw_params_set_access( phandle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED ); + stream_.deviceInterleaved[mode] = true; + } + else + stream_.deviceInterleaved[mode] = false; + } + else { + stream_.userInterleaved = true; + result = snd_pcm_hw_params_set_access( phandle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED ); + if ( result < 0 ) { + result = snd_pcm_hw_params_set_access( phandle, hw_params, SND_PCM_ACCESS_RW_NONINTERLEAVED ); + stream_.deviceInterleaved[mode] = false; + } + else + stream_.deviceInterleaved[mode] = true; + } + + if ( result < 0 ) { + snd_pcm_close( phandle ); + errorStream_ << "RtApiAlsa::probeDeviceOpen: error setting pcm device (" << name << ") access, " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Determine how to set the device format. + stream_.userFormat = format; + snd_pcm_format_t deviceFormat = SND_PCM_FORMAT_UNKNOWN; + + if ( format == RTAUDIO_SINT8 ) + deviceFormat = SND_PCM_FORMAT_S8; + else if ( format == RTAUDIO_SINT16 ) + deviceFormat = SND_PCM_FORMAT_S16; + else if ( format == RTAUDIO_SINT24 ) + deviceFormat = SND_PCM_FORMAT_S24; + else if ( format == RTAUDIO_SINT32 ) + deviceFormat = SND_PCM_FORMAT_S32; + else if ( format == RTAUDIO_FLOAT32 ) + deviceFormat = SND_PCM_FORMAT_FLOAT; + else if ( format == RTAUDIO_FLOAT64 ) + deviceFormat = SND_PCM_FORMAT_FLOAT64; + + if ( snd_pcm_hw_params_test_format(phandle, hw_params, deviceFormat) == 0) { + stream_.deviceFormat[mode] = format; + goto setFormat; + } + + // The user requested format is not natively supported by the device. + deviceFormat = SND_PCM_FORMAT_FLOAT64; + if ( snd_pcm_hw_params_test_format( phandle, hw_params, deviceFormat ) == 0 ) { + stream_.deviceFormat[mode] = RTAUDIO_FLOAT64; + goto setFormat; + } + + deviceFormat = SND_PCM_FORMAT_FLOAT; + if ( snd_pcm_hw_params_test_format(phandle, hw_params, deviceFormat ) == 0 ) { + stream_.deviceFormat[mode] = RTAUDIO_FLOAT32; + goto setFormat; + } + + deviceFormat = SND_PCM_FORMAT_S32; + if ( snd_pcm_hw_params_test_format(phandle, hw_params, deviceFormat ) == 0 ) { + stream_.deviceFormat[mode] = RTAUDIO_SINT32; + goto setFormat; + } + + deviceFormat = SND_PCM_FORMAT_S24; + if ( snd_pcm_hw_params_test_format(phandle, hw_params, deviceFormat ) == 0 ) { + stream_.deviceFormat[mode] = RTAUDIO_SINT24; + goto setFormat; + } + + deviceFormat = SND_PCM_FORMAT_S16; + if ( snd_pcm_hw_params_test_format(phandle, hw_params, deviceFormat ) == 0 ) { + stream_.deviceFormat[mode] = RTAUDIO_SINT16; + goto setFormat; + } + + deviceFormat = SND_PCM_FORMAT_S8; + if ( snd_pcm_hw_params_test_format(phandle, hw_params, deviceFormat ) == 0 ) { + stream_.deviceFormat[mode] = RTAUDIO_SINT8; + goto setFormat; + } + + // If we get here, no supported format was found. + snd_pcm_close( phandle ); + errorStream_ << "RtApiAlsa::probeDeviceOpen: pcm device " << device << " data format not supported by RtAudio."; + errorText_ = errorStream_.str(); + return FAILURE; + + setFormat: + result = snd_pcm_hw_params_set_format( phandle, hw_params, deviceFormat ); + if ( result < 0 ) { + snd_pcm_close( phandle ); + errorStream_ << "RtApiAlsa::probeDeviceOpen: error setting pcm device (" << name << ") data format, " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Determine whether byte-swaping is necessary. + stream_.doByteSwap[mode] = false; + if ( deviceFormat != SND_PCM_FORMAT_S8 ) { + result = snd_pcm_format_cpu_endian( deviceFormat ); + if ( result == 0 ) + stream_.doByteSwap[mode] = true; + else if (result < 0) { + snd_pcm_close( phandle ); + errorStream_ << "RtApiAlsa::probeDeviceOpen: error getting pcm device (" << name << ") endian-ness, " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + return FAILURE; + } + } + + // Set the sample rate. + result = snd_pcm_hw_params_set_rate_near( phandle, hw_params, (unsigned int*) &sampleRate, 0 ); + if ( result < 0 ) { + snd_pcm_close( phandle ); + errorStream_ << "RtApiAlsa::probeDeviceOpen: error setting sample rate on device (" << name << "), " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Determine the number of channels for this device. We support a possible + // minimum device channel number > than the value requested by the user. + stream_.nUserChannels[mode] = channels; + unsigned int value; + result = snd_pcm_hw_params_get_channels_max( hw_params, &value ); + unsigned int deviceChannels = value; + if ( result < 0 || deviceChannels < channels + firstChannel ) { + snd_pcm_close( phandle ); + errorStream_ << "RtApiAlsa::probeDeviceOpen: requested channel parameters not supported by device (" << name << "), " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + result = snd_pcm_hw_params_get_channels_min( hw_params, &value ); + if ( result < 0 ) { + snd_pcm_close( phandle ); + errorStream_ << "RtApiAlsa::probeDeviceOpen: error getting minimum channels for device (" << name << "), " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + return FAILURE; + } + deviceChannels = value; + if ( deviceChannels < channels + firstChannel ) deviceChannels = channels + firstChannel; + stream_.nDeviceChannels[mode] = deviceChannels; + + // Set the device channels. + result = snd_pcm_hw_params_set_channels( phandle, hw_params, deviceChannels ); + if ( result < 0 ) { + snd_pcm_close( phandle ); + errorStream_ << "RtApiAlsa::probeDeviceOpen: error setting channels for device (" << name << "), " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Set the buffer (or period) size. + int dir = 0; + snd_pcm_uframes_t periodSize = *bufferSize; + result = snd_pcm_hw_params_set_period_size_near( phandle, hw_params, &periodSize, &dir ); + if ( result < 0 ) { + snd_pcm_close( phandle ); + errorStream_ << "RtApiAlsa::probeDeviceOpen: error setting period size for device (" << name << "), " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + return FAILURE; + } + *bufferSize = periodSize; + + // Set the buffer number, which in ALSA is referred to as the "period". + unsigned int periods = 0; + if ( options && options->flags & RTAUDIO_MINIMIZE_LATENCY ) periods = 2; + if ( options && options->numberOfBuffers > 0 ) periods = options->numberOfBuffers; + if ( periods < 2 ) periods = 4; // a fairly safe default value + result = snd_pcm_hw_params_set_periods_near( phandle, hw_params, &periods, &dir ); + if ( result < 0 ) { + snd_pcm_close( phandle ); + errorStream_ << "RtApiAlsa::probeDeviceOpen: error setting periods for device (" << name << "), " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // If attempting to setup a duplex stream, the bufferSize parameter + // MUST be the same in both directions! + if ( stream_.mode == OUTPUT && mode == INPUT && *bufferSize != stream_.bufferSize ) { + snd_pcm_close( phandle ); + errorStream_ << "RtApiAlsa::probeDeviceOpen: system error setting buffer size for duplex stream on device (" << name << ")."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + stream_.bufferSize = *bufferSize; + + // Install the hardware configuration + result = snd_pcm_hw_params( phandle, hw_params ); + if ( result < 0 ) { + snd_pcm_close( phandle ); + errorStream_ << "RtApiAlsa::probeDeviceOpen: error installing hardware configuration on device (" << name << "), " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + return FAILURE; + } + +#if defined(__RTAUDIO_DEBUG__) + fprintf(stderr, "\nRtApiAlsa: dump hardware params after installation:\n\n"); + snd_pcm_hw_params_dump( hw_params, out ); +#endif + + // Set the software configuration to fill buffers with zeros and prevent device stopping on xruns. + snd_pcm_sw_params_t *sw_params = NULL; + snd_pcm_sw_params_alloca( &sw_params ); + snd_pcm_sw_params_current( phandle, sw_params ); + snd_pcm_sw_params_set_start_threshold( phandle, sw_params, *bufferSize ); + snd_pcm_sw_params_set_stop_threshold( phandle, sw_params, ULONG_MAX ); + snd_pcm_sw_params_set_silence_threshold( phandle, sw_params, 0 ); + + // The following two settings were suggested by Theo Veenker + //snd_pcm_sw_params_set_avail_min( phandle, sw_params, *bufferSize ); + //snd_pcm_sw_params_set_xfer_align( phandle, sw_params, 1 ); + + // here are two options for a fix + //snd_pcm_sw_params_set_silence_size( phandle, sw_params, ULONG_MAX ); + snd_pcm_uframes_t val; + snd_pcm_sw_params_get_boundary( sw_params, &val ); + snd_pcm_sw_params_set_silence_size( phandle, sw_params, val ); + + result = snd_pcm_sw_params( phandle, sw_params ); + if ( result < 0 ) { + snd_pcm_close( phandle ); + errorStream_ << "RtApiAlsa::probeDeviceOpen: error installing software configuration on device (" << name << "), " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + return FAILURE; + } + +#if defined(__RTAUDIO_DEBUG__) + fprintf(stderr, "\nRtApiAlsa: dump software params after installation:\n\n"); + snd_pcm_sw_params_dump( sw_params, out ); +#endif + + // Set flags for buffer conversion + stream_.doConvertBuffer[mode] = false; + if ( stream_.userFormat != stream_.deviceFormat[mode] ) + stream_.doConvertBuffer[mode] = true; + if ( stream_.nUserChannels[mode] < stream_.nDeviceChannels[mode] ) + stream_.doConvertBuffer[mode] = true; + if ( stream_.userInterleaved != stream_.deviceInterleaved[mode] && + stream_.nUserChannels[mode] > 1 ) + stream_.doConvertBuffer[mode] = true; + + // Allocate the ApiHandle if necessary and then save. + AlsaHandle *apiInfo = 0; + if ( stream_.apiHandle == 0 ) { + try { + apiInfo = (AlsaHandle *) new AlsaHandle; + } + catch ( std::bad_alloc& ) { + errorText_ = "RtApiAlsa::probeDeviceOpen: error allocating AlsaHandle memory."; + goto error; + } + + if ( pthread_cond_init( &apiInfo->runnable_cv, NULL ) ) { + errorText_ = "RtApiAlsa::probeDeviceOpen: error initializing pthread condition variable."; + goto error; + } + + stream_.apiHandle = (void *) apiInfo; + apiInfo->handles[0] = 0; + apiInfo->handles[1] = 0; + } + else { + apiInfo = (AlsaHandle *) stream_.apiHandle; + } + apiInfo->handles[mode] = phandle; + phandle = 0; + + // Allocate necessary internal buffers. + unsigned long bufferBytes; + bufferBytes = stream_.nUserChannels[mode] * *bufferSize * formatBytes( stream_.userFormat ); + stream_.userBuffer[mode] = (char *) calloc( bufferBytes, 1 ); + if ( stream_.userBuffer[mode] == NULL ) { + errorText_ = "RtApiAlsa::probeDeviceOpen: error allocating user buffer memory."; + goto error; + } + + if ( stream_.doConvertBuffer[mode] ) { + + bool makeBuffer = true; + bufferBytes = stream_.nDeviceChannels[mode] * formatBytes( stream_.deviceFormat[mode] ); + if ( mode == INPUT ) { + if ( stream_.mode == OUTPUT && stream_.deviceBuffer ) { + unsigned long bytesOut = stream_.nDeviceChannels[0] * formatBytes( stream_.deviceFormat[0] ); + if ( bufferBytes <= bytesOut ) makeBuffer = false; + } + } + + if ( makeBuffer ) { + bufferBytes *= *bufferSize; + if ( stream_.deviceBuffer ) free( stream_.deviceBuffer ); + stream_.deviceBuffer = (char *) calloc( bufferBytes, 1 ); + if ( stream_.deviceBuffer == NULL ) { + errorText_ = "RtApiAlsa::probeDeviceOpen: error allocating device buffer memory."; + goto error; + } + } + } + + stream_.sampleRate = sampleRate; + stream_.nBuffers = periods; + stream_.device[mode] = device; + stream_.state = STREAM_STOPPED; + + // Setup the buffer conversion information structure. + if ( stream_.doConvertBuffer[mode] ) setConvertInfo( mode, firstChannel ); + + // Setup thread if necessary. + if ( stream_.mode == OUTPUT && mode == INPUT ) { + // We had already set up an output stream. + stream_.mode = DUPLEX; + // Link the streams if possible. + apiInfo->synchronized = false; + if ( snd_pcm_link( apiInfo->handles[0], apiInfo->handles[1] ) == 0 ) + apiInfo->synchronized = true; + else { + errorText_ = "RtApiAlsa::probeDeviceOpen: unable to synchronize input and output devices."; + error( RtAudioError::WARNING ); + } + } + else { + stream_.mode = mode; + + // Setup callback thread. + stream_.callbackInfo.object = (void *) this; + + // Set the thread attributes for joinable and realtime scheduling + // priority (optional). The higher priority will only take affect + // if the program is run as root or suid. Note, under Linux + // processes with CAP_SYS_NICE privilege, a user can change + // scheduling policy and priority (thus need not be root). See + // POSIX "capabilities". + pthread_attr_t attr; + pthread_attr_init( &attr ); + pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_JOINABLE ); + +#ifdef SCHED_RR // Undefined with some OSes (eg: NetBSD 1.6.x with GNU Pthread) + if ( options && options->flags & RTAUDIO_SCHEDULE_REALTIME ) { + // We previously attempted to increase the audio callback priority + // to SCHED_RR here via the attributes. However, while no errors + // were reported in doing so, it did not work. So, now this is + // done in the alsaCallbackHandler function. + stream_.callbackInfo.doRealtime = true; + int priority = options->priority; + int min = sched_get_priority_min( SCHED_RR ); + int max = sched_get_priority_max( SCHED_RR ); + if ( priority < min ) priority = min; + else if ( priority > max ) priority = max; + stream_.callbackInfo.priority = priority; + } +#endif + + stream_.callbackInfo.isRunning = true; + result = pthread_create( &stream_.callbackInfo.thread, &attr, alsaCallbackHandler, &stream_.callbackInfo ); + pthread_attr_destroy( &attr ); + if ( result ) { + stream_.callbackInfo.isRunning = false; + errorText_ = "RtApiAlsa::error creating callback thread!"; + goto error; + } + } + + return SUCCESS; + + error: + if ( apiInfo ) { + pthread_cond_destroy( &apiInfo->runnable_cv ); + if ( apiInfo->handles[0] ) snd_pcm_close( apiInfo->handles[0] ); + if ( apiInfo->handles[1] ) snd_pcm_close( apiInfo->handles[1] ); + delete apiInfo; + stream_.apiHandle = 0; + } + + if ( phandle) snd_pcm_close( phandle ); + + for ( int i=0; i<2; i++ ) { + if ( stream_.userBuffer[i] ) { + free( stream_.userBuffer[i] ); + stream_.userBuffer[i] = 0; + } + } + + if ( stream_.deviceBuffer ) { + free( stream_.deviceBuffer ); + stream_.deviceBuffer = 0; + } + + stream_.state = STREAM_CLOSED; + return FAILURE; +} + +void RtApiAlsa :: closeStream() +{ + if ( stream_.state == STREAM_CLOSED ) { + errorText_ = "RtApiAlsa::closeStream(): no open stream to close!"; + error( RtAudioError::WARNING ); + return; + } + + AlsaHandle *apiInfo = (AlsaHandle *) stream_.apiHandle; + stream_.callbackInfo.isRunning = false; + MUTEX_LOCK( &stream_.mutex ); + if ( stream_.state == STREAM_STOPPED ) { + apiInfo->runnable = true; + pthread_cond_signal( &apiInfo->runnable_cv ); + } + MUTEX_UNLOCK( &stream_.mutex ); + pthread_join( stream_.callbackInfo.thread, NULL ); + + if ( stream_.state == STREAM_RUNNING ) { + stream_.state = STREAM_STOPPED; + if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) + snd_pcm_drop( apiInfo->handles[0] ); + if ( stream_.mode == INPUT || stream_.mode == DUPLEX ) + snd_pcm_drop( apiInfo->handles[1] ); + } + + if ( apiInfo ) { + pthread_cond_destroy( &apiInfo->runnable_cv ); + if ( apiInfo->handles[0] ) snd_pcm_close( apiInfo->handles[0] ); + if ( apiInfo->handles[1] ) snd_pcm_close( apiInfo->handles[1] ); + delete apiInfo; + stream_.apiHandle = 0; + } + + for ( int i=0; i<2; i++ ) { + if ( stream_.userBuffer[i] ) { + free( stream_.userBuffer[i] ); + stream_.userBuffer[i] = 0; + } + } + + if ( stream_.deviceBuffer ) { + free( stream_.deviceBuffer ); + stream_.deviceBuffer = 0; + } + + stream_.mode = UNINITIALIZED; + stream_.state = STREAM_CLOSED; +} + +void RtApiAlsa :: startStream() +{ + // This method calls snd_pcm_prepare if the device isn't already in that state. + + verifyStream(); + if ( stream_.state == STREAM_RUNNING ) { + errorText_ = "RtApiAlsa::startStream(): the stream is already running!"; + error( RtAudioError::WARNING ); + return; + } + + MUTEX_LOCK( &stream_.mutex ); + + int result = 0; + snd_pcm_state_t state; + AlsaHandle *apiInfo = (AlsaHandle *) stream_.apiHandle; + snd_pcm_t **handle = (snd_pcm_t **) apiInfo->handles; + if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { + state = snd_pcm_state( handle[0] ); + if ( state != SND_PCM_STATE_PREPARED ) { + result = snd_pcm_prepare( handle[0] ); + if ( result < 0 ) { + errorStream_ << "RtApiAlsa::startStream: error preparing output pcm device, " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + goto unlock; + } + } + } + + if ( ( stream_.mode == INPUT || stream_.mode == DUPLEX ) && !apiInfo->synchronized ) { + result = snd_pcm_drop(handle[1]); // fix to remove stale data received since device has been open + state = snd_pcm_state( handle[1] ); + if ( state != SND_PCM_STATE_PREPARED ) { + result = snd_pcm_prepare( handle[1] ); + if ( result < 0 ) { + errorStream_ << "RtApiAlsa::startStream: error preparing input pcm device, " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + goto unlock; + } + } + } + + stream_.state = STREAM_RUNNING; + + unlock: + apiInfo->runnable = true; + pthread_cond_signal( &apiInfo->runnable_cv ); + MUTEX_UNLOCK( &stream_.mutex ); + + if ( result >= 0 ) return; + error( RtAudioError::SYSTEM_ERROR ); +} + +void RtApiAlsa :: stopStream() +{ + verifyStream(); + if ( stream_.state == STREAM_STOPPED ) { + errorText_ = "RtApiAlsa::stopStream(): the stream is already stopped!"; + error( RtAudioError::WARNING ); + return; + } + + stream_.state = STREAM_STOPPED; + MUTEX_LOCK( &stream_.mutex ); + + int result = 0; + AlsaHandle *apiInfo = (AlsaHandle *) stream_.apiHandle; + snd_pcm_t **handle = (snd_pcm_t **) apiInfo->handles; + if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { + if ( apiInfo->synchronized ) + result = snd_pcm_drop( handle[0] ); + else + result = snd_pcm_drain( handle[0] ); + if ( result < 0 ) { + errorStream_ << "RtApiAlsa::stopStream: error draining output pcm device, " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + goto unlock; + } + } + + if ( ( stream_.mode == INPUT || stream_.mode == DUPLEX ) && !apiInfo->synchronized ) { + result = snd_pcm_drop( handle[1] ); + if ( result < 0 ) { + errorStream_ << "RtApiAlsa::stopStream: error stopping input pcm device, " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + goto unlock; + } + } + + unlock: + apiInfo->runnable = false; // fixes high CPU usage when stopped + MUTEX_UNLOCK( &stream_.mutex ); + + if ( result >= 0 ) return; + error( RtAudioError::SYSTEM_ERROR ); +} + +void RtApiAlsa :: abortStream() +{ + verifyStream(); + if ( stream_.state == STREAM_STOPPED ) { + errorText_ = "RtApiAlsa::abortStream(): the stream is already stopped!"; + error( RtAudioError::WARNING ); + return; + } + + stream_.state = STREAM_STOPPED; + MUTEX_LOCK( &stream_.mutex ); + + int result = 0; + AlsaHandle *apiInfo = (AlsaHandle *) stream_.apiHandle; + snd_pcm_t **handle = (snd_pcm_t **) apiInfo->handles; + if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { + result = snd_pcm_drop( handle[0] ); + if ( result < 0 ) { + errorStream_ << "RtApiAlsa::abortStream: error aborting output pcm device, " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + goto unlock; + } + } + + if ( ( stream_.mode == INPUT || stream_.mode == DUPLEX ) && !apiInfo->synchronized ) { + result = snd_pcm_drop( handle[1] ); + if ( result < 0 ) { + errorStream_ << "RtApiAlsa::abortStream: error aborting input pcm device, " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + goto unlock; + } + } + + unlock: + apiInfo->runnable = false; // fixes high CPU usage when stopped + MUTEX_UNLOCK( &stream_.mutex ); + + if ( result >= 0 ) return; + error( RtAudioError::SYSTEM_ERROR ); +} + +void RtApiAlsa :: callbackEvent() +{ + AlsaHandle *apiInfo = (AlsaHandle *) stream_.apiHandle; + if ( stream_.state == STREAM_STOPPED ) { + MUTEX_LOCK( &stream_.mutex ); + while ( !apiInfo->runnable ) + pthread_cond_wait( &apiInfo->runnable_cv, &stream_.mutex ); + + if ( stream_.state != STREAM_RUNNING ) { + MUTEX_UNLOCK( &stream_.mutex ); + return; + } + MUTEX_UNLOCK( &stream_.mutex ); + } + + if ( stream_.state == STREAM_CLOSED ) { + errorText_ = "RtApiAlsa::callbackEvent(): the stream is closed ... this shouldn't happen!"; + error( RtAudioError::WARNING ); + return; + } + + int doStopStream = 0; + RtAudioCallback callback = (RtAudioCallback) stream_.callbackInfo.callback; + double streamTime = getStreamTime(); + RtAudioStreamStatus status = 0; + if ( stream_.mode != INPUT && apiInfo->xrun[0] == true ) { + status |= RTAUDIO_OUTPUT_UNDERFLOW; + apiInfo->xrun[0] = false; + } + if ( stream_.mode != OUTPUT && apiInfo->xrun[1] == true ) { + status |= RTAUDIO_INPUT_OVERFLOW; + apiInfo->xrun[1] = false; + } + doStopStream = callback( stream_.userBuffer[0], stream_.userBuffer[1], + stream_.bufferSize, streamTime, status, stream_.callbackInfo.userData ); + + if ( doStopStream == 2 ) { + abortStream(); + return; + } + + MUTEX_LOCK( &stream_.mutex ); + + // The state might change while waiting on a mutex. + if ( stream_.state == STREAM_STOPPED ) goto unlock; + + int result; + char *buffer; + int channels; + snd_pcm_t **handle; + snd_pcm_sframes_t frames; + RtAudioFormat format; + handle = (snd_pcm_t **) apiInfo->handles; + + if ( stream_.mode == INPUT || stream_.mode == DUPLEX ) { + + // Setup parameters. + if ( stream_.doConvertBuffer[1] ) { + buffer = stream_.deviceBuffer; + channels = stream_.nDeviceChannels[1]; + format = stream_.deviceFormat[1]; + } + else { + buffer = stream_.userBuffer[1]; + channels = stream_.nUserChannels[1]; + format = stream_.userFormat; + } + + // Read samples from device in interleaved/non-interleaved format. + if ( stream_.deviceInterleaved[1] ) + result = snd_pcm_readi( handle[1], buffer, stream_.bufferSize ); + else { + void *bufs[channels]; + size_t offset = stream_.bufferSize * formatBytes( format ); + for ( int i=0; ixrun[1] = true; + result = snd_pcm_prepare( handle[1] ); + if ( result < 0 ) { + errorStream_ << "RtApiAlsa::callbackEvent: error preparing device after overrun, " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + } + } + else { + errorStream_ << "RtApiAlsa::callbackEvent: error, current state is " << snd_pcm_state_name( state ) << ", " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + } + } + else { + errorStream_ << "RtApiAlsa::callbackEvent: audio read error, " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + } + error( RtAudioError::WARNING ); + goto tryOutput; + } + + // Do byte swapping if necessary. + if ( stream_.doByteSwap[1] ) + byteSwapBuffer( buffer, stream_.bufferSize * channels, format ); + + // Do buffer conversion if necessary. + if ( stream_.doConvertBuffer[1] ) + convertBuffer( stream_.userBuffer[1], stream_.deviceBuffer, stream_.convertInfo[1] ); + + // Check stream latency + result = snd_pcm_delay( handle[1], &frames ); + if ( result == 0 && frames > 0 ) stream_.latency[1] = frames; + } + + tryOutput: + + if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { + + // Setup parameters and do buffer conversion if necessary. + if ( stream_.doConvertBuffer[0] ) { + buffer = stream_.deviceBuffer; + convertBuffer( buffer, stream_.userBuffer[0], stream_.convertInfo[0] ); + channels = stream_.nDeviceChannels[0]; + format = stream_.deviceFormat[0]; + } + else { + buffer = stream_.userBuffer[0]; + channels = stream_.nUserChannels[0]; + format = stream_.userFormat; + } + + // Do byte swapping if necessary. + if ( stream_.doByteSwap[0] ) + byteSwapBuffer(buffer, stream_.bufferSize * channels, format); + + // Write samples to device in interleaved/non-interleaved format. + if ( stream_.deviceInterleaved[0] ) + result = snd_pcm_writei( handle[0], buffer, stream_.bufferSize ); + else { + void *bufs[channels]; + size_t offset = stream_.bufferSize * formatBytes( format ); + for ( int i=0; ixrun[0] = true; + result = snd_pcm_prepare( handle[0] ); + if ( result < 0 ) { + errorStream_ << "RtApiAlsa::callbackEvent: error preparing device after underrun, " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + } + } + else { + errorStream_ << "RtApiAlsa::callbackEvent: error, current state is " << snd_pcm_state_name( state ) << ", " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + } + } + else { + errorStream_ << "RtApiAlsa::callbackEvent: audio write error, " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + } + error( RtAudioError::WARNING ); + goto unlock; + } + + // Check stream latency + result = snd_pcm_delay( handle[0], &frames ); + if ( result == 0 && frames > 0 ) stream_.latency[0] = frames; + } + + unlock: + MUTEX_UNLOCK( &stream_.mutex ); + + RtApi::tickStreamTime(); + if ( doStopStream == 1 ) this->stopStream(); +} + +static void *alsaCallbackHandler( void *ptr ) +{ + CallbackInfo *info = (CallbackInfo *) ptr; + RtApiAlsa *object = (RtApiAlsa *) info->object; + bool *isRunning = &info->isRunning; + +#ifdef SCHED_RR // Undefined with some OSes (eg: NetBSD 1.6.x with GNU Pthread) + if ( &info->doRealtime ) { + pthread_t tID = pthread_self(); // ID of this thread + sched_param prio = { info->priority }; // scheduling priority of thread + pthread_setschedparam( tID, SCHED_RR, &prio ); + } +#endif + + while ( *isRunning == true ) { + pthread_testcancel(); + object->callbackEvent(); + } + + pthread_exit( NULL ); +} + +//******************** End of __LINUX_ALSA__ *********************// +#endif + +#if defined(__LINUX_PULSE__) + +// Code written by Peter Meerwald, pmeerw@pmeerw.net +// and Tristan Matthews. + +#include +#include +#include + +static const unsigned int SUPPORTED_SAMPLERATES[] = { 8000, 16000, 22050, 32000, + 44100, 48000, 96000, 0}; + +struct rtaudio_pa_format_mapping_t { + RtAudioFormat rtaudio_format; + pa_sample_format_t pa_format; +}; + +static const rtaudio_pa_format_mapping_t supported_sampleformats[] = { + {RTAUDIO_SINT16, PA_SAMPLE_S16LE}, + {RTAUDIO_SINT32, PA_SAMPLE_S32LE}, + {RTAUDIO_FLOAT32, PA_SAMPLE_FLOAT32LE}, + {0, PA_SAMPLE_INVALID}}; + +struct PulseAudioHandle { + pa_simple *s_play; + pa_simple *s_rec; + pthread_t thread; + pthread_cond_t runnable_cv; + bool runnable; + PulseAudioHandle() : s_play(0), s_rec(0), runnable(false) { } +}; + +RtApiPulse::~RtApiPulse() +{ + if ( stream_.state != STREAM_CLOSED ) + closeStream(); +} + +unsigned int RtApiPulse::getDeviceCount( void ) +{ + return 1; +} + +RtAudio::DeviceInfo RtApiPulse::getDeviceInfo( unsigned int /*device*/ ) +{ + RtAudio::DeviceInfo info; + info.probed = true; + info.name = "PulseAudio"; + info.outputChannels = 2; + info.inputChannels = 2; + info.duplexChannels = 2; + info.isDefaultOutput = true; + info.isDefaultInput = true; + + for ( const unsigned int *sr = SUPPORTED_SAMPLERATES; *sr; ++sr ) + info.sampleRates.push_back( *sr ); + + info.nativeFormats = RTAUDIO_SINT16 | RTAUDIO_SINT32 | RTAUDIO_FLOAT32; + + return info; +} + +static void *pulseaudio_callback( void * user ) +{ + CallbackInfo *cbi = static_cast( user ); + RtApiPulse *context = static_cast( cbi->object ); + volatile bool *isRunning = &cbi->isRunning; + + while ( *isRunning ) { + pthread_testcancel(); + context->callbackEvent(); + } + + pthread_exit( NULL ); +} + +void RtApiPulse::closeStream( void ) +{ + PulseAudioHandle *pah = static_cast( stream_.apiHandle ); + + stream_.callbackInfo.isRunning = false; + if ( pah ) { + MUTEX_LOCK( &stream_.mutex ); + if ( stream_.state == STREAM_STOPPED ) { + pah->runnable = true; + pthread_cond_signal( &pah->runnable_cv ); + } + MUTEX_UNLOCK( &stream_.mutex ); + + pthread_join( pah->thread, 0 ); + if ( pah->s_play ) { + pa_simple_flush( pah->s_play, NULL ); + pa_simple_free( pah->s_play ); + } + if ( pah->s_rec ) + pa_simple_free( pah->s_rec ); + + pthread_cond_destroy( &pah->runnable_cv ); + delete pah; + stream_.apiHandle = 0; + } + + if ( stream_.userBuffer[0] ) { + free( stream_.userBuffer[0] ); + stream_.userBuffer[0] = 0; + } + if ( stream_.userBuffer[1] ) { + free( stream_.userBuffer[1] ); + stream_.userBuffer[1] = 0; + } + + stream_.state = STREAM_CLOSED; + stream_.mode = UNINITIALIZED; +} + +void RtApiPulse::callbackEvent( void ) +{ + PulseAudioHandle *pah = static_cast( stream_.apiHandle ); + + if ( stream_.state == STREAM_STOPPED ) { + MUTEX_LOCK( &stream_.mutex ); + while ( !pah->runnable ) + pthread_cond_wait( &pah->runnable_cv, &stream_.mutex ); + + if ( stream_.state != STREAM_RUNNING ) { + MUTEX_UNLOCK( &stream_.mutex ); + return; + } + MUTEX_UNLOCK( &stream_.mutex ); + } + + if ( stream_.state == STREAM_CLOSED ) { + errorText_ = "RtApiPulse::callbackEvent(): the stream is closed ... " + "this shouldn't happen!"; + error( RtAudioError::WARNING ); + return; + } + + RtAudioCallback callback = (RtAudioCallback) stream_.callbackInfo.callback; + double streamTime = getStreamTime(); + RtAudioStreamStatus status = 0; + int doStopStream = callback( stream_.userBuffer[OUTPUT], stream_.userBuffer[INPUT], + stream_.bufferSize, streamTime, status, + stream_.callbackInfo.userData ); + + if ( doStopStream == 2 ) { + abortStream(); + return; + } + + MUTEX_LOCK( &stream_.mutex ); + void *pulse_in = stream_.doConvertBuffer[INPUT] ? stream_.deviceBuffer : stream_.userBuffer[INPUT]; + void *pulse_out = stream_.doConvertBuffer[OUTPUT] ? stream_.deviceBuffer : stream_.userBuffer[OUTPUT]; + + if ( stream_.state != STREAM_RUNNING ) + goto unlock; + + int pa_error; + size_t bytes; + if (stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { + if ( stream_.doConvertBuffer[OUTPUT] ) { + convertBuffer( stream_.deviceBuffer, + stream_.userBuffer[OUTPUT], + stream_.convertInfo[OUTPUT] ); + bytes = stream_.nDeviceChannels[OUTPUT] * stream_.bufferSize * + formatBytes( stream_.deviceFormat[OUTPUT] ); + } else + bytes = stream_.nUserChannels[OUTPUT] * stream_.bufferSize * + formatBytes( stream_.userFormat ); + + if ( pa_simple_write( pah->s_play, pulse_out, bytes, &pa_error ) < 0 ) { + errorStream_ << "RtApiPulse::callbackEvent: audio write error, " << + pa_strerror( pa_error ) << "."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + } + } + + if ( stream_.mode == INPUT || stream_.mode == DUPLEX) { + if ( stream_.doConvertBuffer[INPUT] ) + bytes = stream_.nDeviceChannels[INPUT] * stream_.bufferSize * + formatBytes( stream_.deviceFormat[INPUT] ); + else + bytes = stream_.nUserChannels[INPUT] * stream_.bufferSize * + formatBytes( stream_.userFormat ); + + if ( pa_simple_read( pah->s_rec, pulse_in, bytes, &pa_error ) < 0 ) { + errorStream_ << "RtApiPulse::callbackEvent: audio read error, " << + pa_strerror( pa_error ) << "."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + } + if ( stream_.doConvertBuffer[INPUT] ) { + convertBuffer( stream_.userBuffer[INPUT], + stream_.deviceBuffer, + stream_.convertInfo[INPUT] ); + } + } + + unlock: + MUTEX_UNLOCK( &stream_.mutex ); + RtApi::tickStreamTime(); + + if ( doStopStream == 1 ) + stopStream(); +} + +void RtApiPulse::startStream( void ) +{ + PulseAudioHandle *pah = static_cast( stream_.apiHandle ); + + if ( stream_.state == STREAM_CLOSED ) { + errorText_ = "RtApiPulse::startStream(): the stream is not open!"; + error( RtAudioError::INVALID_USE ); + return; + } + if ( stream_.state == STREAM_RUNNING ) { + errorText_ = "RtApiPulse::startStream(): the stream is already running!"; + error( RtAudioError::WARNING ); + return; + } + + MUTEX_LOCK( &stream_.mutex ); + + stream_.state = STREAM_RUNNING; + + pah->runnable = true; + pthread_cond_signal( &pah->runnable_cv ); + MUTEX_UNLOCK( &stream_.mutex ); +} + +void RtApiPulse::stopStream( void ) +{ + PulseAudioHandle *pah = static_cast( stream_.apiHandle ); + + if ( stream_.state == STREAM_CLOSED ) { + errorText_ = "RtApiPulse::stopStream(): the stream is not open!"; + error( RtAudioError::INVALID_USE ); + return; + } + if ( stream_.state == STREAM_STOPPED ) { + errorText_ = "RtApiPulse::stopStream(): the stream is already stopped!"; + error( RtAudioError::WARNING ); + return; + } + + stream_.state = STREAM_STOPPED; + MUTEX_LOCK( &stream_.mutex ); + + if ( pah && pah->s_play ) { + int pa_error; + if ( pa_simple_drain( pah->s_play, &pa_error ) < 0 ) { + errorStream_ << "RtApiPulse::stopStream: error draining output device, " << + pa_strerror( pa_error ) << "."; + errorText_ = errorStream_.str(); + MUTEX_UNLOCK( &stream_.mutex ); + error( RtAudioError::SYSTEM_ERROR ); + return; + } + } + + stream_.state = STREAM_STOPPED; + MUTEX_UNLOCK( &stream_.mutex ); +} + +void RtApiPulse::abortStream( void ) +{ + PulseAudioHandle *pah = static_cast( stream_.apiHandle ); + + if ( stream_.state == STREAM_CLOSED ) { + errorText_ = "RtApiPulse::abortStream(): the stream is not open!"; + error( RtAudioError::INVALID_USE ); + return; + } + if ( stream_.state == STREAM_STOPPED ) { + errorText_ = "RtApiPulse::abortStream(): the stream is already stopped!"; + error( RtAudioError::WARNING ); + return; + } + + stream_.state = STREAM_STOPPED; + MUTEX_LOCK( &stream_.mutex ); + + if ( pah && pah->s_play ) { + int pa_error; + if ( pa_simple_flush( pah->s_play, &pa_error ) < 0 ) { + errorStream_ << "RtApiPulse::abortStream: error flushing output device, " << + pa_strerror( pa_error ) << "."; + errorText_ = errorStream_.str(); + MUTEX_UNLOCK( &stream_.mutex ); + error( RtAudioError::SYSTEM_ERROR ); + return; + } + } + + stream_.state = STREAM_STOPPED; + MUTEX_UNLOCK( &stream_.mutex ); +} + +bool RtApiPulse::probeDeviceOpen( unsigned int device, StreamMode mode, + unsigned int channels, unsigned int firstChannel, + unsigned int sampleRate, RtAudioFormat format, + unsigned int *bufferSize, RtAudio::StreamOptions *options ) +{ + PulseAudioHandle *pah = 0; + unsigned long bufferBytes = 0; + pa_sample_spec ss; + + if ( device != 0 ) return false; + if ( mode != INPUT && mode != OUTPUT ) return false; + if ( channels != 1 && channels != 2 ) { + errorText_ = "RtApiPulse::probeDeviceOpen: unsupported number of channels."; + return false; + } + ss.channels = channels; + + if ( firstChannel != 0 ) return false; + + bool sr_found = false; + for ( const unsigned int *sr = SUPPORTED_SAMPLERATES; *sr; ++sr ) { + if ( sampleRate == *sr ) { + sr_found = true; + stream_.sampleRate = sampleRate; + ss.rate = sampleRate; + break; + } + } + if ( !sr_found ) { + errorText_ = "RtApiPulse::probeDeviceOpen: unsupported sample rate."; + return false; + } + + bool sf_found = 0; + for ( const rtaudio_pa_format_mapping_t *sf = supported_sampleformats; + sf->rtaudio_format && sf->pa_format != PA_SAMPLE_INVALID; ++sf ) { + if ( format == sf->rtaudio_format ) { + sf_found = true; + stream_.userFormat = sf->rtaudio_format; + stream_.deviceFormat[mode] = stream_.userFormat; + ss.format = sf->pa_format; + break; + } + } + if ( !sf_found ) { // Use internal data format conversion. + stream_.userFormat = format; + stream_.deviceFormat[mode] = RTAUDIO_FLOAT32; + ss.format = PA_SAMPLE_FLOAT32LE; + } + + // Set other stream parameters. + if ( options && options->flags & RTAUDIO_NONINTERLEAVED ) stream_.userInterleaved = false; + else stream_.userInterleaved = true; + stream_.deviceInterleaved[mode] = true; + stream_.nBuffers = 1; + stream_.doByteSwap[mode] = false; + stream_.nUserChannels[mode] = channels; + stream_.nDeviceChannels[mode] = channels + firstChannel; + stream_.channelOffset[mode] = 0; + std::string streamName = "RtAudio"; + + // Set flags for buffer conversion. + stream_.doConvertBuffer[mode] = false; + if ( stream_.userFormat != stream_.deviceFormat[mode] ) + stream_.doConvertBuffer[mode] = true; + if ( stream_.nUserChannels[mode] < stream_.nDeviceChannels[mode] ) + stream_.doConvertBuffer[mode] = true; + + // Allocate necessary internal buffers. + bufferBytes = stream_.nUserChannels[mode] * *bufferSize * formatBytes( stream_.userFormat ); + stream_.userBuffer[mode] = (char *) calloc( bufferBytes, 1 ); + if ( stream_.userBuffer[mode] == NULL ) { + errorText_ = "RtApiPulse::probeDeviceOpen: error allocating user buffer memory."; + goto error; + } + stream_.bufferSize = *bufferSize; + + if ( stream_.doConvertBuffer[mode] ) { + + bool makeBuffer = true; + bufferBytes = stream_.nDeviceChannels[mode] * formatBytes( stream_.deviceFormat[mode] ); + if ( mode == INPUT ) { + if ( stream_.mode == OUTPUT && stream_.deviceBuffer ) { + unsigned long bytesOut = stream_.nDeviceChannels[0] * formatBytes( stream_.deviceFormat[0] ); + if ( bufferBytes <= bytesOut ) makeBuffer = false; + } + } + + if ( makeBuffer ) { + bufferBytes *= *bufferSize; + if ( stream_.deviceBuffer ) free( stream_.deviceBuffer ); + stream_.deviceBuffer = (char *) calloc( bufferBytes, 1 ); + if ( stream_.deviceBuffer == NULL ) { + errorText_ = "RtApiPulse::probeDeviceOpen: error allocating device buffer memory."; + goto error; + } + } + } + + stream_.device[mode] = device; + + // Setup the buffer conversion information structure. + if ( stream_.doConvertBuffer[mode] ) setConvertInfo( mode, firstChannel ); + + if ( !stream_.apiHandle ) { + PulseAudioHandle *pah = new PulseAudioHandle; + if ( !pah ) { + errorText_ = "RtApiPulse::probeDeviceOpen: error allocating memory for handle."; + goto error; + } + + stream_.apiHandle = pah; + if ( pthread_cond_init( &pah->runnable_cv, NULL ) != 0 ) { + errorText_ = "RtApiPulse::probeDeviceOpen: error creating condition variable."; + goto error; + } + } + pah = static_cast( stream_.apiHandle ); + + int error; + if ( !options->streamName.empty() ) streamName = options->streamName; + switch ( mode ) { + case INPUT: + pa_buffer_attr buffer_attr; + buffer_attr.fragsize = bufferBytes; + buffer_attr.maxlength = -1; + + pah->s_rec = pa_simple_new( NULL, streamName.c_str(), PA_STREAM_RECORD, NULL, "Record", &ss, NULL, &buffer_attr, &error ); + if ( !pah->s_rec ) { + errorText_ = "RtApiPulse::probeDeviceOpen: error connecting input to PulseAudio server."; + goto error; + } + break; + case OUTPUT: + pah->s_play = pa_simple_new( NULL, "RtAudio", PA_STREAM_PLAYBACK, NULL, "Playback", &ss, NULL, NULL, &error ); + if ( !pah->s_play ) { + errorText_ = "RtApiPulse::probeDeviceOpen: error connecting output to PulseAudio server."; + goto error; + } + break; + default: + goto error; + } + + if ( stream_.mode == UNINITIALIZED ) + stream_.mode = mode; + else if ( stream_.mode == mode ) + goto error; + else + stream_.mode = DUPLEX; + + if ( !stream_.callbackInfo.isRunning ) { + stream_.callbackInfo.object = this; + stream_.callbackInfo.isRunning = true; + if ( pthread_create( &pah->thread, NULL, pulseaudio_callback, (void *)&stream_.callbackInfo) != 0 ) { + errorText_ = "RtApiPulse::probeDeviceOpen: error creating thread."; + goto error; + } + } + + stream_.state = STREAM_STOPPED; + return true; + + error: + if ( pah && stream_.callbackInfo.isRunning ) { + pthread_cond_destroy( &pah->runnable_cv ); + delete pah; + stream_.apiHandle = 0; + } + + for ( int i=0; i<2; i++ ) { + if ( stream_.userBuffer[i] ) { + free( stream_.userBuffer[i] ); + stream_.userBuffer[i] = 0; + } + } + + if ( stream_.deviceBuffer ) { + free( stream_.deviceBuffer ); + stream_.deviceBuffer = 0; + } + + return FAILURE; +} + +//******************** End of __LINUX_PULSE__ *********************// +#endif + +#if defined(__LINUX_OSS__) + +#include +#include +#include +#include +#include +#include +#include + +static void *ossCallbackHandler(void * ptr); + +// A structure to hold various information related to the OSS API +// implementation. +struct OssHandle { + int id[2]; // device ids + bool xrun[2]; + bool triggered; + pthread_cond_t runnable; + + OssHandle() + :triggered(false) { id[0] = 0; id[1] = 0; xrun[0] = false; xrun[1] = false; } +}; + +RtApiOss :: RtApiOss() +{ + // Nothing to do here. +} + +RtApiOss :: ~RtApiOss() +{ + if ( stream_.state != STREAM_CLOSED ) closeStream(); +} + +unsigned int RtApiOss :: getDeviceCount( void ) +{ + int mixerfd = open( "/dev/mixer", O_RDWR, 0 ); + if ( mixerfd == -1 ) { + errorText_ = "RtApiOss::getDeviceCount: error opening '/dev/mixer'."; + error( RtAudioError::WARNING ); + return 0; + } + + oss_sysinfo sysinfo; + if ( ioctl( mixerfd, SNDCTL_SYSINFO, &sysinfo ) == -1 ) { + close( mixerfd ); + errorText_ = "RtApiOss::getDeviceCount: error getting sysinfo, OSS version >= 4.0 is required."; + error( RtAudioError::WARNING ); + return 0; + } + + close( mixerfd ); + return sysinfo.numaudios; +} + +RtAudio::DeviceInfo RtApiOss :: getDeviceInfo( unsigned int device ) +{ + RtAudio::DeviceInfo info; + info.probed = false; + + int mixerfd = open( "/dev/mixer", O_RDWR, 0 ); + if ( mixerfd == -1 ) { + errorText_ = "RtApiOss::getDeviceInfo: error opening '/dev/mixer'."; + error( RtAudioError::WARNING ); + return info; + } + + oss_sysinfo sysinfo; + int result = ioctl( mixerfd, SNDCTL_SYSINFO, &sysinfo ); + if ( result == -1 ) { + close( mixerfd ); + errorText_ = "RtApiOss::getDeviceInfo: error getting sysinfo, OSS version >= 4.0 is required."; + error( RtAudioError::WARNING ); + return info; + } + + unsigned nDevices = sysinfo.numaudios; + if ( nDevices == 0 ) { + close( mixerfd ); + errorText_ = "RtApiOss::getDeviceInfo: no devices found!"; + error( RtAudioError::INVALID_USE ); + return info; + } + + if ( device >= nDevices ) { + close( mixerfd ); + errorText_ = "RtApiOss::getDeviceInfo: device ID is invalid!"; + error( RtAudioError::INVALID_USE ); + return info; + } + + oss_audioinfo ainfo; + ainfo.dev = device; + result = ioctl( mixerfd, SNDCTL_AUDIOINFO, &ainfo ); + close( mixerfd ); + if ( result == -1 ) { + errorStream_ << "RtApiOss::getDeviceInfo: error getting device (" << ainfo.name << ") info."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + return info; + } + + // Probe channels + if ( ainfo.caps & PCM_CAP_OUTPUT ) info.outputChannels = ainfo.max_channels; + if ( ainfo.caps & PCM_CAP_INPUT ) info.inputChannels = ainfo.max_channels; + if ( ainfo.caps & PCM_CAP_DUPLEX ) { + if ( info.outputChannels > 0 && info.inputChannels > 0 && ainfo.caps & PCM_CAP_DUPLEX ) + info.duplexChannels = (info.outputChannels > info.inputChannels) ? info.inputChannels : info.outputChannels; + } + + // Probe data formats ... do for input + unsigned long mask = ainfo.iformats; + if ( mask & AFMT_S16_LE || mask & AFMT_S16_BE ) + info.nativeFormats |= RTAUDIO_SINT16; + if ( mask & AFMT_S8 ) + info.nativeFormats |= RTAUDIO_SINT8; + if ( mask & AFMT_S32_LE || mask & AFMT_S32_BE ) + info.nativeFormats |= RTAUDIO_SINT32; + if ( mask & AFMT_FLOAT ) + info.nativeFormats |= RTAUDIO_FLOAT32; + if ( mask & AFMT_S24_LE || mask & AFMT_S24_BE ) + info.nativeFormats |= RTAUDIO_SINT24; + + // Check that we have at least one supported format + if ( info.nativeFormats == 0 ) { + errorStream_ << "RtApiOss::getDeviceInfo: device (" << ainfo.name << ") data format not supported by RtAudio."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + return info; + } + + // Probe the supported sample rates. + info.sampleRates.clear(); + if ( ainfo.nrates ) { + for ( unsigned int i=0; i= (int) SAMPLE_RATES[k] ) + info.sampleRates.push_back( SAMPLE_RATES[k] ); + } + } + + if ( info.sampleRates.size() == 0 ) { + errorStream_ << "RtApiOss::getDeviceInfo: no supported sample rates found for device (" << ainfo.name << ")."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + } + else { + info.probed = true; + info.name = ainfo.name; + } + + return info; +} + + +bool RtApiOss :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, + unsigned int firstChannel, unsigned int sampleRate, + RtAudioFormat format, unsigned int *bufferSize, + RtAudio::StreamOptions *options ) +{ + int mixerfd = open( "/dev/mixer", O_RDWR, 0 ); + if ( mixerfd == -1 ) { + errorText_ = "RtApiOss::probeDeviceOpen: error opening '/dev/mixer'."; + return FAILURE; + } + + oss_sysinfo sysinfo; + int result = ioctl( mixerfd, SNDCTL_SYSINFO, &sysinfo ); + if ( result == -1 ) { + close( mixerfd ); + errorText_ = "RtApiOss::probeDeviceOpen: error getting sysinfo, OSS version >= 4.0 is required."; + return FAILURE; + } + + unsigned nDevices = sysinfo.numaudios; + if ( nDevices == 0 ) { + // This should not happen because a check is made before this function is called. + close( mixerfd ); + errorText_ = "RtApiOss::probeDeviceOpen: no devices found!"; + return FAILURE; + } + + if ( device >= nDevices ) { + // This should not happen because a check is made before this function is called. + close( mixerfd ); + errorText_ = "RtApiOss::probeDeviceOpen: device ID is invalid!"; + return FAILURE; + } + + oss_audioinfo ainfo; + ainfo.dev = device; + result = ioctl( mixerfd, SNDCTL_AUDIOINFO, &ainfo ); + close( mixerfd ); + if ( result == -1 ) { + errorStream_ << "RtApiOss::getDeviceInfo: error getting device (" << ainfo.name << ") info."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Check if device supports input or output + if ( ( mode == OUTPUT && !( ainfo.caps & PCM_CAP_OUTPUT ) ) || + ( mode == INPUT && !( ainfo.caps & PCM_CAP_INPUT ) ) ) { + if ( mode == OUTPUT ) + errorStream_ << "RtApiOss::probeDeviceOpen: device (" << ainfo.name << ") does not support output."; + else + errorStream_ << "RtApiOss::probeDeviceOpen: device (" << ainfo.name << ") does not support input."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + int flags = 0; + OssHandle *handle = (OssHandle *) stream_.apiHandle; + if ( mode == OUTPUT ) + flags |= O_WRONLY; + else { // mode == INPUT + if (stream_.mode == OUTPUT && stream_.device[0] == device) { + // We just set the same device for playback ... close and reopen for duplex (OSS only). + close( handle->id[0] ); + handle->id[0] = 0; + if ( !( ainfo.caps & PCM_CAP_DUPLEX ) ) { + errorStream_ << "RtApiOss::probeDeviceOpen: device (" << ainfo.name << ") does not support duplex mode."; + errorText_ = errorStream_.str(); + return FAILURE; + } + // Check that the number previously set channels is the same. + if ( stream_.nUserChannels[0] != channels ) { + errorStream_ << "RtApiOss::probeDeviceOpen: input/output channels must be equal for OSS duplex device (" << ainfo.name << ")."; + errorText_ = errorStream_.str(); + return FAILURE; + } + flags |= O_RDWR; + } + else + flags |= O_RDONLY; + } + + // Set exclusive access if specified. + if ( options && options->flags & RTAUDIO_HOG_DEVICE ) flags |= O_EXCL; + + // Try to open the device. + int fd; + fd = open( ainfo.devnode, flags, 0 ); + if ( fd == -1 ) { + if ( errno == EBUSY ) + errorStream_ << "RtApiOss::probeDeviceOpen: device (" << ainfo.name << ") is busy."; + else + errorStream_ << "RtApiOss::probeDeviceOpen: error opening device (" << ainfo.name << ")."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // For duplex operation, specifically set this mode (this doesn't seem to work). + /* + if ( flags | O_RDWR ) { + result = ioctl( fd, SNDCTL_DSP_SETDUPLEX, NULL ); + if ( result == -1) { + errorStream_ << "RtApiOss::probeDeviceOpen: error setting duplex mode for device (" << ainfo.name << ")."; + errorText_ = errorStream_.str(); + return FAILURE; + } + } + */ + + // Check the device channel support. + stream_.nUserChannels[mode] = channels; + if ( ainfo.max_channels < (int)(channels + firstChannel) ) { + close( fd ); + errorStream_ << "RtApiOss::probeDeviceOpen: the device (" << ainfo.name << ") does not support requested channel parameters."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Set the number of channels. + int deviceChannels = channels + firstChannel; + result = ioctl( fd, SNDCTL_DSP_CHANNELS, &deviceChannels ); + if ( result == -1 || deviceChannels < (int)(channels + firstChannel) ) { + close( fd ); + errorStream_ << "RtApiOss::probeDeviceOpen: error setting channel parameters on device (" << ainfo.name << ")."; + errorText_ = errorStream_.str(); + return FAILURE; + } + stream_.nDeviceChannels[mode] = deviceChannels; + + // Get the data format mask + int mask; + result = ioctl( fd, SNDCTL_DSP_GETFMTS, &mask ); + if ( result == -1 ) { + close( fd ); + errorStream_ << "RtApiOss::probeDeviceOpen: error getting device (" << ainfo.name << ") data formats."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Determine how to set the device format. + stream_.userFormat = format; + int deviceFormat = -1; + stream_.doByteSwap[mode] = false; + if ( format == RTAUDIO_SINT8 ) { + if ( mask & AFMT_S8 ) { + deviceFormat = AFMT_S8; + stream_.deviceFormat[mode] = RTAUDIO_SINT8; + } + } + else if ( format == RTAUDIO_SINT16 ) { + if ( mask & AFMT_S16_NE ) { + deviceFormat = AFMT_S16_NE; + stream_.deviceFormat[mode] = RTAUDIO_SINT16; + } + else if ( mask & AFMT_S16_OE ) { + deviceFormat = AFMT_S16_OE; + stream_.deviceFormat[mode] = RTAUDIO_SINT16; + stream_.doByteSwap[mode] = true; + } + } + else if ( format == RTAUDIO_SINT24 ) { + if ( mask & AFMT_S24_NE ) { + deviceFormat = AFMT_S24_NE; + stream_.deviceFormat[mode] = RTAUDIO_SINT24; + } + else if ( mask & AFMT_S24_OE ) { + deviceFormat = AFMT_S24_OE; + stream_.deviceFormat[mode] = RTAUDIO_SINT24; + stream_.doByteSwap[mode] = true; + } + } + else if ( format == RTAUDIO_SINT32 ) { + if ( mask & AFMT_S32_NE ) { + deviceFormat = AFMT_S32_NE; + stream_.deviceFormat[mode] = RTAUDIO_SINT32; + } + else if ( mask & AFMT_S32_OE ) { + deviceFormat = AFMT_S32_OE; + stream_.deviceFormat[mode] = RTAUDIO_SINT32; + stream_.doByteSwap[mode] = true; + } + } + + if ( deviceFormat == -1 ) { + // The user requested format is not natively supported by the device. + if ( mask & AFMT_S16_NE ) { + deviceFormat = AFMT_S16_NE; + stream_.deviceFormat[mode] = RTAUDIO_SINT16; + } + else if ( mask & AFMT_S32_NE ) { + deviceFormat = AFMT_S32_NE; + stream_.deviceFormat[mode] = RTAUDIO_SINT32; + } + else if ( mask & AFMT_S24_NE ) { + deviceFormat = AFMT_S24_NE; + stream_.deviceFormat[mode] = RTAUDIO_SINT24; + } + else if ( mask & AFMT_S16_OE ) { + deviceFormat = AFMT_S16_OE; + stream_.deviceFormat[mode] = RTAUDIO_SINT16; + stream_.doByteSwap[mode] = true; + } + else if ( mask & AFMT_S32_OE ) { + deviceFormat = AFMT_S32_OE; + stream_.deviceFormat[mode] = RTAUDIO_SINT32; + stream_.doByteSwap[mode] = true; + } + else if ( mask & AFMT_S24_OE ) { + deviceFormat = AFMT_S24_OE; + stream_.deviceFormat[mode] = RTAUDIO_SINT24; + stream_.doByteSwap[mode] = true; + } + else if ( mask & AFMT_S8) { + deviceFormat = AFMT_S8; + stream_.deviceFormat[mode] = RTAUDIO_SINT8; + } + } + + if ( stream_.deviceFormat[mode] == 0 ) { + // This really shouldn't happen ... + close( fd ); + errorStream_ << "RtApiOss::probeDeviceOpen: device (" << ainfo.name << ") data format not supported by RtAudio."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Set the data format. + int temp = deviceFormat; + result = ioctl( fd, SNDCTL_DSP_SETFMT, &deviceFormat ); + if ( result == -1 || deviceFormat != temp ) { + close( fd ); + errorStream_ << "RtApiOss::probeDeviceOpen: error setting data format on device (" << ainfo.name << ")."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Attempt to set the buffer size. According to OSS, the minimum + // number of buffers is two. The supposed minimum buffer size is 16 + // bytes, so that will be our lower bound. The argument to this + // call is in the form 0xMMMMSSSS (hex), where the buffer size (in + // bytes) is given as 2^SSSS and the number of buffers as 2^MMMM. + // We'll check the actual value used near the end of the setup + // procedure. + int ossBufferBytes = *bufferSize * formatBytes( stream_.deviceFormat[mode] ) * deviceChannels; + if ( ossBufferBytes < 16 ) ossBufferBytes = 16; + int buffers = 0; + if ( options ) buffers = options->numberOfBuffers; + if ( options && options->flags & RTAUDIO_MINIMIZE_LATENCY ) buffers = 2; + if ( buffers < 2 ) buffers = 3; + temp = ((int) buffers << 16) + (int)( log10( (double)ossBufferBytes ) / log10( 2.0 ) ); + result = ioctl( fd, SNDCTL_DSP_SETFRAGMENT, &temp ); + if ( result == -1 ) { + close( fd ); + errorStream_ << "RtApiOss::probeDeviceOpen: error setting buffer size on device (" << ainfo.name << ")."; + errorText_ = errorStream_.str(); + return FAILURE; + } + stream_.nBuffers = buffers; + + // Save buffer size (in sample frames). + *bufferSize = ossBufferBytes / ( formatBytes(stream_.deviceFormat[mode]) * deviceChannels ); + stream_.bufferSize = *bufferSize; + + // Set the sample rate. + int srate = sampleRate; + result = ioctl( fd, SNDCTL_DSP_SPEED, &srate ); + if ( result == -1 ) { + close( fd ); + errorStream_ << "RtApiOss::probeDeviceOpen: error setting sample rate (" << sampleRate << ") on device (" << ainfo.name << ")."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Verify the sample rate setup worked. + if ( abs( srate - sampleRate ) > 100 ) { + close( fd ); + errorStream_ << "RtApiOss::probeDeviceOpen: device (" << ainfo.name << ") does not support sample rate (" << sampleRate << ")."; + errorText_ = errorStream_.str(); + return FAILURE; + } + stream_.sampleRate = sampleRate; + + if ( mode == INPUT && stream_.mode == OUTPUT && stream_.device[0] == device) { + // We're doing duplex setup here. + stream_.deviceFormat[0] = stream_.deviceFormat[1]; + stream_.nDeviceChannels[0] = deviceChannels; + } + + // Set interleaving parameters. + stream_.userInterleaved = true; + stream_.deviceInterleaved[mode] = true; + if ( options && options->flags & RTAUDIO_NONINTERLEAVED ) + stream_.userInterleaved = false; + + // Set flags for buffer conversion + stream_.doConvertBuffer[mode] = false; + if ( stream_.userFormat != stream_.deviceFormat[mode] ) + stream_.doConvertBuffer[mode] = true; + if ( stream_.nUserChannels[mode] < stream_.nDeviceChannels[mode] ) + stream_.doConvertBuffer[mode] = true; + if ( stream_.userInterleaved != stream_.deviceInterleaved[mode] && + stream_.nUserChannels[mode] > 1 ) + stream_.doConvertBuffer[mode] = true; + + // Allocate the stream handles if necessary and then save. + if ( stream_.apiHandle == 0 ) { + try { + handle = new OssHandle; + } + catch ( std::bad_alloc& ) { + errorText_ = "RtApiOss::probeDeviceOpen: error allocating OssHandle memory."; + goto error; + } + + if ( pthread_cond_init( &handle->runnable, NULL ) ) { + errorText_ = "RtApiOss::probeDeviceOpen: error initializing pthread condition variable."; + goto error; + } + + stream_.apiHandle = (void *) handle; + } + else { + handle = (OssHandle *) stream_.apiHandle; + } + handle->id[mode] = fd; + + // Allocate necessary internal buffers. + unsigned long bufferBytes; + bufferBytes = stream_.nUserChannels[mode] * *bufferSize * formatBytes( stream_.userFormat ); + stream_.userBuffer[mode] = (char *) calloc( bufferBytes, 1 ); + if ( stream_.userBuffer[mode] == NULL ) { + errorText_ = "RtApiOss::probeDeviceOpen: error allocating user buffer memory."; + goto error; + } + + if ( stream_.doConvertBuffer[mode] ) { + + bool makeBuffer = true; + bufferBytes = stream_.nDeviceChannels[mode] * formatBytes( stream_.deviceFormat[mode] ); + if ( mode == INPUT ) { + if ( stream_.mode == OUTPUT && stream_.deviceBuffer ) { + unsigned long bytesOut = stream_.nDeviceChannels[0] * formatBytes( stream_.deviceFormat[0] ); + if ( bufferBytes <= bytesOut ) makeBuffer = false; + } + } + + if ( makeBuffer ) { + bufferBytes *= *bufferSize; + if ( stream_.deviceBuffer ) free( stream_.deviceBuffer ); + stream_.deviceBuffer = (char *) calloc( bufferBytes, 1 ); + if ( stream_.deviceBuffer == NULL ) { + errorText_ = "RtApiOss::probeDeviceOpen: error allocating device buffer memory."; + goto error; + } + } + } + + stream_.device[mode] = device; + stream_.state = STREAM_STOPPED; + + // Setup the buffer conversion information structure. + if ( stream_.doConvertBuffer[mode] ) setConvertInfo( mode, firstChannel ); + + // Setup thread if necessary. + if ( stream_.mode == OUTPUT && mode == INPUT ) { + // We had already set up an output stream. + stream_.mode = DUPLEX; + if ( stream_.device[0] == device ) handle->id[0] = fd; + } + else { + stream_.mode = mode; + + // Setup callback thread. + stream_.callbackInfo.object = (void *) this; + + // Set the thread attributes for joinable and realtime scheduling + // priority. The higher priority will only take affect if the + // program is run as root or suid. + pthread_attr_t attr; + pthread_attr_init( &attr ); + pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_JOINABLE ); +#ifdef SCHED_RR // Undefined with some OSes (eg: NetBSD 1.6.x with GNU Pthread) + if ( options && options->flags & RTAUDIO_SCHEDULE_REALTIME ) { + struct sched_param param; + int priority = options->priority; + int min = sched_get_priority_min( SCHED_RR ); + int max = sched_get_priority_max( SCHED_RR ); + if ( priority < min ) priority = min; + else if ( priority > max ) priority = max; + param.sched_priority = priority; + pthread_attr_setschedparam( &attr, ¶m ); + pthread_attr_setschedpolicy( &attr, SCHED_RR ); + } + else + pthread_attr_setschedpolicy( &attr, SCHED_OTHER ); +#else + pthread_attr_setschedpolicy( &attr, SCHED_OTHER ); +#endif + + stream_.callbackInfo.isRunning = true; + result = pthread_create( &stream_.callbackInfo.thread, &attr, ossCallbackHandler, &stream_.callbackInfo ); + pthread_attr_destroy( &attr ); + if ( result ) { + stream_.callbackInfo.isRunning = false; + errorText_ = "RtApiOss::error creating callback thread!"; + goto error; + } + } + + return SUCCESS; + + error: + if ( handle ) { + pthread_cond_destroy( &handle->runnable ); + if ( handle->id[0] ) close( handle->id[0] ); + if ( handle->id[1] ) close( handle->id[1] ); + delete handle; + stream_.apiHandle = 0; + } + + for ( int i=0; i<2; i++ ) { + if ( stream_.userBuffer[i] ) { + free( stream_.userBuffer[i] ); + stream_.userBuffer[i] = 0; + } + } + + if ( stream_.deviceBuffer ) { + free( stream_.deviceBuffer ); + stream_.deviceBuffer = 0; + } + + return FAILURE; +} + +void RtApiOss :: closeStream() +{ + if ( stream_.state == STREAM_CLOSED ) { + errorText_ = "RtApiOss::closeStream(): no open stream to close!"; + error( RtAudioError::WARNING ); + return; + } + + OssHandle *handle = (OssHandle *) stream_.apiHandle; + stream_.callbackInfo.isRunning = false; + MUTEX_LOCK( &stream_.mutex ); + if ( stream_.state == STREAM_STOPPED ) + pthread_cond_signal( &handle->runnable ); + MUTEX_UNLOCK( &stream_.mutex ); + pthread_join( stream_.callbackInfo.thread, NULL ); + + if ( stream_.state == STREAM_RUNNING ) { + if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) + ioctl( handle->id[0], SNDCTL_DSP_HALT, 0 ); + else + ioctl( handle->id[1], SNDCTL_DSP_HALT, 0 ); + stream_.state = STREAM_STOPPED; + } + + if ( handle ) { + pthread_cond_destroy( &handle->runnable ); + if ( handle->id[0] ) close( handle->id[0] ); + if ( handle->id[1] ) close( handle->id[1] ); + delete handle; + stream_.apiHandle = 0; + } + + for ( int i=0; i<2; i++ ) { + if ( stream_.userBuffer[i] ) { + free( stream_.userBuffer[i] ); + stream_.userBuffer[i] = 0; + } + } + + if ( stream_.deviceBuffer ) { + free( stream_.deviceBuffer ); + stream_.deviceBuffer = 0; + } + + stream_.mode = UNINITIALIZED; + stream_.state = STREAM_CLOSED; +} + +void RtApiOss :: startStream() +{ + verifyStream(); + if ( stream_.state == STREAM_RUNNING ) { + errorText_ = "RtApiOss::startStream(): the stream is already running!"; + error( RtAudioError::WARNING ); + return; + } + + MUTEX_LOCK( &stream_.mutex ); + + stream_.state = STREAM_RUNNING; + + // No need to do anything else here ... OSS automatically starts + // when fed samples. + + MUTEX_UNLOCK( &stream_.mutex ); + + OssHandle *handle = (OssHandle *) stream_.apiHandle; + pthread_cond_signal( &handle->runnable ); +} + +void RtApiOss :: stopStream() +{ + verifyStream(); + if ( stream_.state == STREAM_STOPPED ) { + errorText_ = "RtApiOss::stopStream(): the stream is already stopped!"; + error( RtAudioError::WARNING ); + return; + } + + MUTEX_LOCK( &stream_.mutex ); + + // The state might change while waiting on a mutex. + if ( stream_.state == STREAM_STOPPED ) { + MUTEX_UNLOCK( &stream_.mutex ); + return; + } + + int result = 0; + OssHandle *handle = (OssHandle *) stream_.apiHandle; + if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { + + // Flush the output with zeros a few times. + char *buffer; + int samples; + RtAudioFormat format; + + if ( stream_.doConvertBuffer[0] ) { + buffer = stream_.deviceBuffer; + samples = stream_.bufferSize * stream_.nDeviceChannels[0]; + format = stream_.deviceFormat[0]; + } + else { + buffer = stream_.userBuffer[0]; + samples = stream_.bufferSize * stream_.nUserChannels[0]; + format = stream_.userFormat; + } + + memset( buffer, 0, samples * formatBytes(format) ); + for ( unsigned int i=0; iid[0], buffer, samples * formatBytes(format) ); + if ( result == -1 ) { + errorText_ = "RtApiOss::stopStream: audio write error."; + error( RtAudioError::WARNING ); + } + } + + result = ioctl( handle->id[0], SNDCTL_DSP_HALT, 0 ); + if ( result == -1 ) { + errorStream_ << "RtApiOss::stopStream: system error stopping callback procedure on device (" << stream_.device[0] << ")."; + errorText_ = errorStream_.str(); + goto unlock; + } + handle->triggered = false; + } + + if ( stream_.mode == INPUT || ( stream_.mode == DUPLEX && handle->id[0] != handle->id[1] ) ) { + result = ioctl( handle->id[1], SNDCTL_DSP_HALT, 0 ); + if ( result == -1 ) { + errorStream_ << "RtApiOss::stopStream: system error stopping input callback procedure on device (" << stream_.device[0] << ")."; + errorText_ = errorStream_.str(); + goto unlock; + } + } + + unlock: + stream_.state = STREAM_STOPPED; + MUTEX_UNLOCK( &stream_.mutex ); + + if ( result != -1 ) return; + error( RtAudioError::SYSTEM_ERROR ); +} + +void RtApiOss :: abortStream() +{ + verifyStream(); + if ( stream_.state == STREAM_STOPPED ) { + errorText_ = "RtApiOss::abortStream(): the stream is already stopped!"; + error( RtAudioError::WARNING ); + return; + } + + MUTEX_LOCK( &stream_.mutex ); + + // The state might change while waiting on a mutex. + if ( stream_.state == STREAM_STOPPED ) { + MUTEX_UNLOCK( &stream_.mutex ); + return; + } + + int result = 0; + OssHandle *handle = (OssHandle *) stream_.apiHandle; + if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { + result = ioctl( handle->id[0], SNDCTL_DSP_HALT, 0 ); + if ( result == -1 ) { + errorStream_ << "RtApiOss::abortStream: system error stopping callback procedure on device (" << stream_.device[0] << ")."; + errorText_ = errorStream_.str(); + goto unlock; + } + handle->triggered = false; + } + + if ( stream_.mode == INPUT || ( stream_.mode == DUPLEX && handle->id[0] != handle->id[1] ) ) { + result = ioctl( handle->id[1], SNDCTL_DSP_HALT, 0 ); + if ( result == -1 ) { + errorStream_ << "RtApiOss::abortStream: system error stopping input callback procedure on device (" << stream_.device[0] << ")."; + errorText_ = errorStream_.str(); + goto unlock; + } + } + + unlock: + stream_.state = STREAM_STOPPED; + MUTEX_UNLOCK( &stream_.mutex ); + + if ( result != -1 ) return; + error( RtAudioError::SYSTEM_ERROR ); +} + +void RtApiOss :: callbackEvent() +{ + OssHandle *handle = (OssHandle *) stream_.apiHandle; + if ( stream_.state == STREAM_STOPPED ) { + MUTEX_LOCK( &stream_.mutex ); + pthread_cond_wait( &handle->runnable, &stream_.mutex ); + if ( stream_.state != STREAM_RUNNING ) { + MUTEX_UNLOCK( &stream_.mutex ); + return; + } + MUTEX_UNLOCK( &stream_.mutex ); + } + + if ( stream_.state == STREAM_CLOSED ) { + errorText_ = "RtApiOss::callbackEvent(): the stream is closed ... this shouldn't happen!"; + error( RtAudioError::WARNING ); + return; + } + + // Invoke user callback to get fresh output data. + int doStopStream = 0; + RtAudioCallback callback = (RtAudioCallback) stream_.callbackInfo.callback; + double streamTime = getStreamTime(); + RtAudioStreamStatus status = 0; + if ( stream_.mode != INPUT && handle->xrun[0] == true ) { + status |= RTAUDIO_OUTPUT_UNDERFLOW; + handle->xrun[0] = false; + } + if ( stream_.mode != OUTPUT && handle->xrun[1] == true ) { + status |= RTAUDIO_INPUT_OVERFLOW; + handle->xrun[1] = false; + } + doStopStream = callback( stream_.userBuffer[0], stream_.userBuffer[1], + stream_.bufferSize, streamTime, status, stream_.callbackInfo.userData ); + if ( doStopStream == 2 ) { + this->abortStream(); + return; + } + + MUTEX_LOCK( &stream_.mutex ); + + // The state might change while waiting on a mutex. + if ( stream_.state == STREAM_STOPPED ) goto unlock; + + int result; + char *buffer; + int samples; + RtAudioFormat format; + + if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { + + // Setup parameters and do buffer conversion if necessary. + if ( stream_.doConvertBuffer[0] ) { + buffer = stream_.deviceBuffer; + convertBuffer( buffer, stream_.userBuffer[0], stream_.convertInfo[0] ); + samples = stream_.bufferSize * stream_.nDeviceChannels[0]; + format = stream_.deviceFormat[0]; + } + else { + buffer = stream_.userBuffer[0]; + samples = stream_.bufferSize * stream_.nUserChannels[0]; + format = stream_.userFormat; + } + + // Do byte swapping if necessary. + if ( stream_.doByteSwap[0] ) + byteSwapBuffer( buffer, samples, format ); + + if ( stream_.mode == DUPLEX && handle->triggered == false ) { + int trig = 0; + ioctl( handle->id[0], SNDCTL_DSP_SETTRIGGER, &trig ); + result = write( handle->id[0], buffer, samples * formatBytes(format) ); + trig = PCM_ENABLE_INPUT|PCM_ENABLE_OUTPUT; + ioctl( handle->id[0], SNDCTL_DSP_SETTRIGGER, &trig ); + handle->triggered = true; + } + else + // Write samples to device. + result = write( handle->id[0], buffer, samples * formatBytes(format) ); + + if ( result == -1 ) { + // We'll assume this is an underrun, though there isn't a + // specific means for determining that. + handle->xrun[0] = true; + errorText_ = "RtApiOss::callbackEvent: audio write error."; + error( RtAudioError::WARNING ); + // Continue on to input section. + } + } + + if ( stream_.mode == INPUT || stream_.mode == DUPLEX ) { + + // Setup parameters. + if ( stream_.doConvertBuffer[1] ) { + buffer = stream_.deviceBuffer; + samples = stream_.bufferSize * stream_.nDeviceChannels[1]; + format = stream_.deviceFormat[1]; + } + else { + buffer = stream_.userBuffer[1]; + samples = stream_.bufferSize * stream_.nUserChannels[1]; + format = stream_.userFormat; + } + + // Read samples from device. + result = read( handle->id[1], buffer, samples * formatBytes(format) ); + + if ( result == -1 ) { + // We'll assume this is an overrun, though there isn't a + // specific means for determining that. + handle->xrun[1] = true; + errorText_ = "RtApiOss::callbackEvent: audio read error."; + error( RtAudioError::WARNING ); + goto unlock; + } + + // Do byte swapping if necessary. + if ( stream_.doByteSwap[1] ) + byteSwapBuffer( buffer, samples, format ); + + // Do buffer conversion if necessary. + if ( stream_.doConvertBuffer[1] ) + convertBuffer( stream_.userBuffer[1], stream_.deviceBuffer, stream_.convertInfo[1] ); + } + + unlock: + MUTEX_UNLOCK( &stream_.mutex ); + + RtApi::tickStreamTime(); + if ( doStopStream == 1 ) this->stopStream(); +} + +static void *ossCallbackHandler( void *ptr ) +{ + CallbackInfo *info = (CallbackInfo *) ptr; + RtApiOss *object = (RtApiOss *) info->object; + bool *isRunning = &info->isRunning; + + while ( *isRunning == true ) { + pthread_testcancel(); + object->callbackEvent(); + } + + pthread_exit( NULL ); +} + +//******************** End of __LINUX_OSS__ *********************// +#endif + + +// *************************************************** // +// +// Protected common (OS-independent) RtAudio methods. +// +// *************************************************** // + +// This method can be modified to control the behavior of error +// message printing. +void RtApi :: error( RtAudioError::Type type ) +{ + errorStream_.str(""); // clear the ostringstream + + RtAudioErrorCallback errorCallback = (RtAudioErrorCallback) stream_.callbackInfo.errorCallback; + if ( errorCallback ) { + // abortStream() can generate new error messages. Ignore them. Just keep original one. + + if ( firstErrorOccurred_ ) + return; + + firstErrorOccurred_ = true; + const std::string errorMessage = errorText_; + + if ( type != RtAudioError::WARNING && stream_.state != STREAM_STOPPED) { + stream_.callbackInfo.isRunning = false; // exit from the thread + abortStream(); + } + + errorCallback( type, errorMessage ); + firstErrorOccurred_ = false; + return; + } + + if ( type == RtAudioError::WARNING && showWarnings_ == true ) + std::cerr << '\n' << errorText_ << "\n\n"; + else if ( type != RtAudioError::WARNING ) + throw( RtAudioError( errorText_, type ) ); +} + +void RtApi :: verifyStream() +{ + if ( stream_.state == STREAM_CLOSED ) { + errorText_ = "RtApi:: a stream is not open!"; + error( RtAudioError::INVALID_USE ); + } +} + +void RtApi :: clearStreamInfo() +{ + stream_.mode = UNINITIALIZED; + stream_.state = STREAM_CLOSED; + stream_.sampleRate = 0; + stream_.bufferSize = 0; + stream_.nBuffers = 0; + stream_.userFormat = 0; + stream_.userInterleaved = true; + stream_.streamTime = 0.0; + stream_.apiHandle = 0; + stream_.deviceBuffer = 0; + stream_.callbackInfo.callback = 0; + stream_.callbackInfo.userData = 0; + stream_.callbackInfo.isRunning = false; + stream_.callbackInfo.errorCallback = 0; + for ( int i=0; i<2; i++ ) { + stream_.device[i] = 11111; + stream_.doConvertBuffer[i] = false; + stream_.deviceInterleaved[i] = true; + stream_.doByteSwap[i] = false; + stream_.nUserChannels[i] = 0; + stream_.nDeviceChannels[i] = 0; + stream_.channelOffset[i] = 0; + stream_.deviceFormat[i] = 0; + stream_.latency[i] = 0; + stream_.userBuffer[i] = 0; + stream_.convertInfo[i].channels = 0; + stream_.convertInfo[i].inJump = 0; + stream_.convertInfo[i].outJump = 0; + stream_.convertInfo[i].inFormat = 0; + stream_.convertInfo[i].outFormat = 0; + stream_.convertInfo[i].inOffset.clear(); + stream_.convertInfo[i].outOffset.clear(); + } +} + +unsigned int RtApi :: formatBytes( RtAudioFormat format ) +{ + if ( format == RTAUDIO_SINT16 ) + return 2; + else if ( format == RTAUDIO_SINT32 || format == RTAUDIO_FLOAT32 ) + return 4; + else if ( format == RTAUDIO_FLOAT64 ) + return 8; + else if ( format == RTAUDIO_SINT24 ) + return 3; + else if ( format == RTAUDIO_SINT8 ) + return 1; + + errorText_ = "RtApi::formatBytes: undefined format."; + error( RtAudioError::WARNING ); + + return 0; +} + +void RtApi :: setConvertInfo( StreamMode mode, unsigned int firstChannel ) +{ + if ( mode == INPUT ) { // convert device to user buffer + stream_.convertInfo[mode].inJump = stream_.nDeviceChannels[1]; + stream_.convertInfo[mode].outJump = stream_.nUserChannels[1]; + stream_.convertInfo[mode].inFormat = stream_.deviceFormat[1]; + stream_.convertInfo[mode].outFormat = stream_.userFormat; + } + else { // convert user to device buffer + stream_.convertInfo[mode].inJump = stream_.nUserChannels[0]; + stream_.convertInfo[mode].outJump = stream_.nDeviceChannels[0]; + stream_.convertInfo[mode].inFormat = stream_.userFormat; + stream_.convertInfo[mode].outFormat = stream_.deviceFormat[0]; + } + + if ( stream_.convertInfo[mode].inJump < stream_.convertInfo[mode].outJump ) + stream_.convertInfo[mode].channels = stream_.convertInfo[mode].inJump; + else + stream_.convertInfo[mode].channels = stream_.convertInfo[mode].outJump; + + // Set up the interleave/deinterleave offsets. + if ( stream_.deviceInterleaved[mode] != stream_.userInterleaved ) { + if ( ( mode == OUTPUT && stream_.deviceInterleaved[mode] ) || + ( mode == INPUT && stream_.userInterleaved ) ) { + for ( int k=0; k 0 ) { + if ( stream_.deviceInterleaved[mode] ) { + if ( mode == OUTPUT ) { + for ( int k=0; k> 8); + //out[info.outOffset[j]] >>= 8; + } + in += info.inJump; + out += info.outJump; + } + } + else if (info.inFormat == RTAUDIO_FLOAT32) { + Float32 *in = (Float32 *)inBuffer; + for (unsigned int i=0; i> 8); + } + in += info.inJump; + out += info.outJump; + } + } + else if (info.inFormat == RTAUDIO_SINT32) { + Int32 *in = (Int32 *)inBuffer; + for (unsigned int i=0; i> 16) & 0x0000ffff); + } + in += info.inJump; + out += info.outJump; + } + } + else if (info.inFormat == RTAUDIO_FLOAT32) { + Float32 *in = (Float32 *)inBuffer; + for (unsigned int i=0; i> 8) & 0x00ff); + } + in += info.inJump; + out += info.outJump; + } + } + else if (info.inFormat == RTAUDIO_SINT24) { + Int24 *in = (Int24 *)inBuffer; + for (unsigned int i=0; i> 16); + } + in += info.inJump; + out += info.outJump; + } + } + else if (info.inFormat == RTAUDIO_SINT32) { + Int32 *in = (Int32 *)inBuffer; + for (unsigned int i=0; i> 24) & 0x000000ff); + } + in += info.inJump; + out += info.outJump; + } + } + else if (info.inFormat == RTAUDIO_FLOAT32) { + Float32 *in = (Float32 *)inBuffer; + for (unsigned int i=0; i>8) | (x<<8); } +//static inline uint32_t bswap_32(uint32_t x) { return (bswap_16(x&0xffff)<<16) | (bswap_16(x>>16)); } +//static inline uint64_t bswap_64(uint64_t x) { return (((unsigned long long)bswap_32(x&0xffffffffull))<<32) | (bswap_32(x>>32)); } + +void RtApi :: byteSwapBuffer( char *buffer, unsigned int samples, RtAudioFormat format ) +{ + register char val; + register char *ptr; + + ptr = buffer; + if ( format == RTAUDIO_SINT16 ) { + for ( unsigned int i=0; i +#include + +/* --- Monocasual hack ---------------------------------------------- */ +#if defined(__linux__) +#include +#endif +/* ------------------------------------------------------------------ */ + +#include +#include + +/*! \typedef typedef unsigned long RtAudioFormat; + \brief RtAudio data format type. + + Support for signed integers and floats. Audio data fed to/from an + RtAudio stream is assumed to ALWAYS be in host byte order. The + internal routines will automatically take care of any necessary + byte-swapping between the host format and the soundcard. Thus, + endian-ness is not a concern in the following format definitions. + + - \e RTAUDIO_SINT8: 8-bit signed integer. + - \e RTAUDIO_SINT16: 16-bit signed integer. + - \e RTAUDIO_SINT24: 24-bit signed integer. + - \e RTAUDIO_SINT32: 32-bit signed integer. + - \e RTAUDIO_FLOAT32: Normalized between plus/minus 1.0. + - \e RTAUDIO_FLOAT64: Normalized between plus/minus 1.0. +*/ +typedef unsigned long RtAudioFormat; +static const RtAudioFormat RTAUDIO_SINT8 = 0x1; // 8-bit signed integer. +static const RtAudioFormat RTAUDIO_SINT16 = 0x2; // 16-bit signed integer. +static const RtAudioFormat RTAUDIO_SINT24 = 0x4; // 24-bit signed integer. +static const RtAudioFormat RTAUDIO_SINT32 = 0x8; // 32-bit signed integer. +static const RtAudioFormat RTAUDIO_FLOAT32 = 0x10; // Normalized between plus/minus 1.0. +static const RtAudioFormat RTAUDIO_FLOAT64 = 0x20; // Normalized between plus/minus 1.0. + +/*! \typedef typedef unsigned long RtAudioStreamFlags; + \brief RtAudio stream option flags. + + The following flags can be OR'ed together to allow a client to + make changes to the default stream behavior: + + - \e RTAUDIO_NONINTERLEAVED: Use non-interleaved buffers (default = interleaved). + - \e RTAUDIO_MINIMIZE_LATENCY: Attempt to set stream parameters for lowest possible latency. + - \e RTAUDIO_HOG_DEVICE: Attempt grab device for exclusive use. + - \e RTAUDIO_ALSA_USE_DEFAULT: Use the "default" PCM device (ALSA only). + + By default, RtAudio streams pass and receive audio data from the + client in an interleaved format. By passing the + RTAUDIO_NONINTERLEAVED flag to the openStream() function, audio + data will instead be presented in non-interleaved buffers. In + this case, each buffer argument in the RtAudioCallback function + will point to a single array of data, with \c nFrames samples for + each channel concatenated back-to-back. For example, the first + sample of data for the second channel would be located at index \c + nFrames (assuming the \c buffer pointer was recast to the correct + data type for the stream). + + Certain audio APIs offer a number of parameters that influence the + I/O latency of a stream. By default, RtAudio will attempt to set + these parameters internally for robust (glitch-free) performance + (though some APIs, like Windows Direct Sound, make this difficult). + By passing the RTAUDIO_MINIMIZE_LATENCY flag to the openStream() + function, internal stream settings will be influenced in an attempt + to minimize stream latency, though possibly at the expense of stream + performance. + + If the RTAUDIO_HOG_DEVICE flag is set, RtAudio will attempt to + open the input and/or output stream device(s) for exclusive use. + Note that this is not possible with all supported audio APIs. + + If the RTAUDIO_SCHEDULE_REALTIME flag is set, RtAudio will attempt + to select realtime scheduling (round-robin) for the callback thread. + + If the RTAUDIO_ALSA_USE_DEFAULT flag is set, RtAudio will attempt to + open the "default" PCM device when using the ALSA API. Note that this + will override any specified input or output device id. +*/ +typedef unsigned int RtAudioStreamFlags; +static const RtAudioStreamFlags RTAUDIO_NONINTERLEAVED = 0x1; // Use non-interleaved buffers (default = interleaved). +static const RtAudioStreamFlags RTAUDIO_MINIMIZE_LATENCY = 0x2; // Attempt to set stream parameters for lowest possible latency. +static const RtAudioStreamFlags RTAUDIO_HOG_DEVICE = 0x4; // Attempt grab device and prevent use by others. +static const RtAudioStreamFlags RTAUDIO_SCHEDULE_REALTIME = 0x8; // Try to select realtime scheduling for callback thread. +static const RtAudioStreamFlags RTAUDIO_ALSA_USE_DEFAULT = 0x10; // Use the "default" PCM device (ALSA only). + +/*! \typedef typedef unsigned long RtAudioStreamStatus; + \brief RtAudio stream status (over- or underflow) flags. + + Notification of a stream over- or underflow is indicated by a + non-zero stream \c status argument in the RtAudioCallback function. + The stream status can be one of the following two options, + depending on whether the stream is open for output and/or input: + + - \e RTAUDIO_INPUT_OVERFLOW: Input data was discarded because of an overflow condition at the driver. + - \e RTAUDIO_OUTPUT_UNDERFLOW: The output buffer ran low, likely producing a break in the output sound. +*/ +typedef unsigned int RtAudioStreamStatus; +static const RtAudioStreamStatus RTAUDIO_INPUT_OVERFLOW = 0x1; // Input data was discarded because of an overflow condition at the driver. +static const RtAudioStreamStatus RTAUDIO_OUTPUT_UNDERFLOW = 0x2; // The output buffer ran low, likely causing a gap in the output sound. + +//! RtAudio callback function prototype. +/*! + All RtAudio clients must create a function of type RtAudioCallback + to read and/or write data from/to the audio stream. When the + underlying audio system is ready for new input or output data, this + function will be invoked. + + \param outputBuffer For output (or duplex) streams, the client + should write \c nFrames of audio sample frames into this + buffer. This argument should be recast to the datatype + specified when the stream was opened. For input-only + streams, this argument will be NULL. + + \param inputBuffer For input (or duplex) streams, this buffer will + hold \c nFrames of input audio sample frames. This + argument should be recast to the datatype specified when the + stream was opened. For output-only streams, this argument + will be NULL. + + \param nFrames The number of sample frames of input or output + data in the buffers. The actual buffer size in bytes is + dependent on the data type and number of channels in use. + + \param streamTime The number of seconds that have elapsed since the + stream was started. + + \param status If non-zero, this argument indicates a data overflow + or underflow condition for the stream. The particular + condition can be determined by comparison with the + RtAudioStreamStatus flags. + + \param userData A pointer to optional data provided by the client + when opening the stream (default = NULL). + + To continue normal stream operation, the RtAudioCallback function + should return a value of zero. To stop the stream and drain the + output buffer, the function should return a value of one. To abort + the stream immediately, the client should return a value of two. + */ +typedef int (*RtAudioCallback)( void *outputBuffer, void *inputBuffer, + unsigned int nFrames, + double streamTime, + RtAudioStreamStatus status, + void *userData ); + +/************************************************************************/ +/*! \class RtAudioError + \brief Exception handling class for RtAudio. + + The RtAudioError class is quite simple but it does allow errors to be + "caught" by RtAudioError::Type. See the RtAudio documentation to know + which methods can throw an RtAudioError. +*/ +/************************************************************************/ + +class RtAudioError : public std::exception +{ + public: + //! Defined RtAudioError types. + enum Type { + WARNING, /*!< A non-critical error. */ + DEBUG_WARNING, /*!< A non-critical error which might be useful for debugging. */ + UNSPECIFIED, /*!< The default, unspecified error type. */ + NO_DEVICES_FOUND, /*!< No devices found on system. */ + INVALID_DEVICE, /*!< An invalid device ID was specified. */ + MEMORY_ERROR, /*!< An error occured during memory allocation. */ + INVALID_PARAMETER, /*!< An invalid parameter was specified to a function. */ + INVALID_USE, /*!< The function was called incorrectly. */ + DRIVER_ERROR, /*!< A system driver error occured. */ + SYSTEM_ERROR, /*!< A system error occured. */ + THREAD_ERROR /*!< A thread error occured. */ + }; + + //! The constructor. + RtAudioError( const std::string& message, Type type = RtAudioError::UNSPECIFIED ) throw() : message_(message), type_(type) {} + + //! The destructor. + virtual ~RtAudioError( void ) throw() {} + + //! Prints thrown error message to stderr. + virtual void printMessage( void ) const throw() { std::cerr << '\n' << message_ << "\n\n"; } + + //! Returns the thrown error message type. + virtual const Type& getType(void) const throw() { return type_; } + + //! Returns the thrown error message string. + virtual const std::string& getMessage(void) const throw() { return message_; } + + //! Returns the thrown error message as a c-style string. + virtual const char* what( void ) const throw() { return message_.c_str(); } + + protected: + std::string message_; + Type type_; +}; + +//! RtAudio error callback function prototype. +/*! + \param type Type of error. + \param errorText Error description. + */ +typedef void (*RtAudioErrorCallback)( RtAudioError::Type type, const std::string &errorText ); + +// **************************************************************** // +// +// RtAudio class declaration. +// +// RtAudio is a "controller" used to select an available audio i/o +// interface. It presents a common API for the user to call but all +// functionality is implemented by the class RtApi and its +// subclasses. RtAudio creates an instance of an RtApi subclass +// based on the user's API choice. If no choice is made, RtAudio +// attempts to make a "logical" API selection. +// +// **************************************************************** // + +class RtApi; + +class RtAudio +{ + public: + + //! Audio API specifier arguments. + enum Api { + UNSPECIFIED, /*!< Search for a working compiled API. */ + LINUX_ALSA, /*!< The Advanced Linux Sound Architecture API. */ + LINUX_PULSE, /*!< The Linux PulseAudio API. */ + LINUX_OSS, /*!< The Linux Open Sound System API. */ + UNIX_JACK, /*!< The Jack Low-Latency Audio Server API. */ + MACOSX_CORE, /*!< Macintosh OS-X Core Audio API. */ + WINDOWS_WASAPI, /*!< The Microsoft WASAPI API. */ + WINDOWS_ASIO, /*!< The Steinberg Audio Stream I/O API. */ + WINDOWS_DS, /*!< The Microsoft Direct Sound API. */ + RTAUDIO_DUMMY /*!< A compilable but non-functional API. */ + }; + + //! The public device information structure for returning queried values. + struct DeviceInfo { + bool probed; /*!< true if the device capabilities were successfully probed. */ + std::string name; /*!< Character string device identifier. */ + unsigned int outputChannels; /*!< Maximum output channels supported by device. */ + unsigned int inputChannels; /*!< Maximum input channels supported by device. */ + unsigned int duplexChannels; /*!< Maximum simultaneous input/output channels supported by device. */ + bool isDefaultOutput; /*!< true if this is the default output device. */ + bool isDefaultInput; /*!< true if this is the default input device. */ + std::vector sampleRates; /*!< Supported sample rates (queried from list of standard rates). */ + RtAudioFormat nativeFormats; /*!< Bit mask of supported data formats. */ + + // Default constructor. + DeviceInfo() + :probed(false), outputChannels(0), inputChannels(0), duplexChannels(0), + isDefaultOutput(false), isDefaultInput(false), nativeFormats(0) {} + }; + + //! The structure for specifying input or ouput stream parameters. + struct StreamParameters { + unsigned int deviceId; /*!< Device index (0 to getDeviceCount() - 1). */ + unsigned int nChannels; /*!< Number of channels. */ + unsigned int firstChannel; /*!< First channel index on device (default = 0). */ + + // Default constructor. + StreamParameters() + : deviceId(0), nChannels(0), firstChannel(0) {} + }; + + //! The structure for specifying stream options. + /*! + The following flags can be OR'ed together to allow a client to + make changes to the default stream behavior: + + - \e RTAUDIO_NONINTERLEAVED: Use non-interleaved buffers (default = interleaved). + - \e RTAUDIO_MINIMIZE_LATENCY: Attempt to set stream parameters for lowest possible latency. + - \e RTAUDIO_HOG_DEVICE: Attempt grab device for exclusive use. + - \e RTAUDIO_SCHEDULE_REALTIME: Attempt to select realtime scheduling for callback thread. + - \e RTAUDIO_ALSA_USE_DEFAULT: Use the "default" PCM device (ALSA only). + + By default, RtAudio streams pass and receive audio data from the + client in an interleaved format. By passing the + RTAUDIO_NONINTERLEAVED flag to the openStream() function, audio + data will instead be presented in non-interleaved buffers. In + this case, each buffer argument in the RtAudioCallback function + will point to a single array of data, with \c nFrames samples for + each channel concatenated back-to-back. For example, the first + sample of data for the second channel would be located at index \c + nFrames (assuming the \c buffer pointer was recast to the correct + data type for the stream). + + Certain audio APIs offer a number of parameters that influence the + I/O latency of a stream. By default, RtAudio will attempt to set + these parameters internally for robust (glitch-free) performance + (though some APIs, like Windows Direct Sound, make this difficult). + By passing the RTAUDIO_MINIMIZE_LATENCY flag to the openStream() + function, internal stream settings will be influenced in an attempt + to minimize stream latency, though possibly at the expense of stream + performance. + + If the RTAUDIO_HOG_DEVICE flag is set, RtAudio will attempt to + open the input and/or output stream device(s) for exclusive use. + Note that this is not possible with all supported audio APIs. + + If the RTAUDIO_SCHEDULE_REALTIME flag is set, RtAudio will attempt + to select realtime scheduling (round-robin) for the callback thread. + The \c priority parameter will only be used if the RTAUDIO_SCHEDULE_REALTIME + flag is set. It defines the thread's realtime priority. + + If the RTAUDIO_ALSA_USE_DEFAULT flag is set, RtAudio will attempt to + open the "default" PCM device when using the ALSA API. Note that this + will override any specified input or output device id. + + The \c numberOfBuffers parameter can be used to control stream + latency in the Windows DirectSound, Linux OSS, and Linux Alsa APIs + only. A value of two is usually the smallest allowed. Larger + numbers can potentially result in more robust stream performance, + though likely at the cost of stream latency. The value set by the + user is replaced during execution of the RtAudio::openStream() + function by the value actually used by the system. + + The \c streamName parameter can be used to set the client name + when using the Jack API. By default, the client name is set to + RtApiJack. However, if you wish to create multiple instances of + RtAudio with Jack, each instance must have a unique client name. + */ + struct StreamOptions { + RtAudioStreamFlags flags; /*!< A bit-mask of stream flags (RTAUDIO_NONINTERLEAVED, RTAUDIO_MINIMIZE_LATENCY, RTAUDIO_HOG_DEVICE, RTAUDIO_ALSA_USE_DEFAULT). */ + unsigned int numberOfBuffers; /*!< Number of stream buffers. */ + std::string streamName; /*!< A stream name (currently used only in Jack). */ + int priority; /*!< Scheduling priority of callback thread (only used with flag RTAUDIO_SCHEDULE_REALTIME). */ + + // Default constructor. + StreamOptions() + : flags(0), numberOfBuffers(0), priority(0) {} + }; + + //! A static function to determine the current RtAudio version. + static std::string getVersion( void ) throw(); + + //! A static function to determine the available compiled audio APIs. + /*! + The values returned in the std::vector can be compared against + the enumerated list values. Note that there can be more than one + API compiled for certain operating systems. + */ + static void getCompiledApi( std::vector &apis ) throw(); + + //! The class constructor. + /*! + The constructor performs minor initialization tasks. An exception + can be thrown if no API support is compiled. + + If no API argument is specified and multiple API support has been + compiled, the default order of use is JACK, ALSA, OSS (Linux + systems) and ASIO, DS (Windows systems). + */ + RtAudio( RtAudio::Api api=UNSPECIFIED ); + + //! The destructor. + /*! + If a stream is running or open, it will be stopped and closed + automatically. + */ + ~RtAudio() throw(); + + //! Returns the audio API specifier for the current instance of RtAudio. + RtAudio::Api getCurrentApi( void ) throw(); + + //! A public function that queries for the number of audio devices available. + /*! + This function performs a system query of available devices each time it + is called, thus supporting devices connected \e after instantiation. If + a system error occurs during processing, a warning will be issued. + */ + unsigned int getDeviceCount( void ) throw(); + + //! Return an RtAudio::DeviceInfo structure for a specified device number. + /*! + + Any device integer between 0 and getDeviceCount() - 1 is valid. + If an invalid argument is provided, an RtAudioError (type = INVALID_USE) + will be thrown. If a device is busy or otherwise unavailable, the + structure member "probed" will have a value of "false" and all + other members are undefined. If the specified device is the + current default input or output device, the corresponding + "isDefault" member will have a value of "true". + */ + RtAudio::DeviceInfo getDeviceInfo( unsigned int device ); + + //! A function that returns the index of the default output device. + /*! + If the underlying audio API does not provide a "default + device", or if no devices are available, the return value will be + 0. Note that this is a valid device identifier and it is the + client's responsibility to verify that a device is available + before attempting to open a stream. + */ + unsigned int getDefaultOutputDevice( void ) throw(); + + //! A function that returns the index of the default input device. + /*! + If the underlying audio API does not provide a "default + device", or if no devices are available, the return value will be + 0. Note that this is a valid device identifier and it is the + client's responsibility to verify that a device is available + before attempting to open a stream. + */ + unsigned int getDefaultInputDevice( void ) throw(); + + //! A public function for opening a stream with the specified parameters. + /*! + An RtAudioError (type = SYSTEM_ERROR) is thrown if a stream cannot be + opened with the specified parameters or an error occurs during + processing. An RtAudioError (type = INVALID_USE) is thrown if any + invalid device ID or channel number parameters are specified. + + \param outputParameters Specifies output stream parameters to use + when opening a stream, including a device ID, number of channels, + and starting channel number. For input-only streams, this + argument should be NULL. The device ID is an index value between + 0 and getDeviceCount() - 1. + \param inputParameters Specifies input stream parameters to use + when opening a stream, including a device ID, number of channels, + and starting channel number. For output-only streams, this + argument should be NULL. The device ID is an index value between + 0 and getDeviceCount() - 1. + \param format An RtAudioFormat specifying the desired sample data format. + \param sampleRate The desired sample rate (sample frames per second). + \param *bufferFrames A pointer to a value indicating the desired + internal buffer size in sample frames. The actual value + used by the device is returned via the same pointer. A + value of zero can be specified, in which case the lowest + allowable value is determined. + \param callback A client-defined function that will be invoked + when input data is available and/or output data is needed. + \param userData An optional pointer to data that can be accessed + from within the callback function. + \param options An optional pointer to a structure containing various + global stream options, including a list of OR'ed RtAudioStreamFlags + and a suggested number of stream buffers that can be used to + control stream latency. More buffers typically result in more + robust performance, though at a cost of greater latency. If a + value of zero is specified, a system-specific median value is + chosen. If the RTAUDIO_MINIMIZE_LATENCY flag bit is set, the + lowest allowable value is used. The actual value used is + returned via the structure argument. The parameter is API dependent. + \param errorCallback A client-defined function that will be invoked + when an error has occured. + */ + void openStream( RtAudio::StreamParameters *outputParameters, + RtAudio::StreamParameters *inputParameters, + RtAudioFormat format, unsigned int sampleRate, + unsigned int *bufferFrames, RtAudioCallback callback, + void *userData = NULL, RtAudio::StreamOptions *options = NULL, RtAudioErrorCallback errorCallback = NULL ); + + //! A function that closes a stream and frees any associated stream memory. + /*! + If a stream is not open, this function issues a warning and + returns (no exception is thrown). + */ + void closeStream( void ) throw(); + + //! A function that starts a stream. + /*! + An RtAudioError (type = SYSTEM_ERROR) is thrown if an error occurs + during processing. An RtAudioError (type = INVALID_USE) is thrown if a + stream is not open. A warning is issued if the stream is already + running. + */ + void startStream( void ); + + //! Stop a stream, allowing any samples remaining in the output queue to be played. + /*! + An RtAudioError (type = SYSTEM_ERROR) is thrown if an error occurs + during processing. An RtAudioError (type = INVALID_USE) is thrown if a + stream is not open. A warning is issued if the stream is already + stopped. + */ + void stopStream( void ); + + //! Stop a stream, discarding any samples remaining in the input/output queue. + /*! + An RtAudioError (type = SYSTEM_ERROR) is thrown if an error occurs + during processing. An RtAudioError (type = INVALID_USE) is thrown if a + stream is not open. A warning is issued if the stream is already + stopped. + */ + void abortStream( void ); + + //! Returns true if a stream is open and false if not. + bool isStreamOpen( void ) const throw(); + + //! Returns true if the stream is running and false if it is stopped or not open. + bool isStreamRunning( void ) const throw(); + + //! Returns the number of elapsed seconds since the stream was started. + /*! + If a stream is not open, an RtAudioError (type = INVALID_USE) will be thrown. + */ + double getStreamTime( void ); + + //! Set the stream time to a time in seconds greater than or equal to 0.0. + /*! + If a stream is not open, an RtAudioError (type = INVALID_USE) will be thrown. + */ + void setStreamTime( double time ); + + //! Returns the internal stream latency in sample frames. + /*! + The stream latency refers to delay in audio input and/or output + caused by internal buffering by the audio system and/or hardware. + For duplex streams, the returned value will represent the sum of + the input and output latencies. If a stream is not open, an + RtAudioError (type = INVALID_USE) will be thrown. If the API does not + report latency, the return value will be zero. + */ + long getStreamLatency( void ); + + //! Returns actual sample rate in use by the stream. + /*! + On some systems, the sample rate used may be slightly different + than that specified in the stream parameters. If a stream is not + open, an RtAudioError (type = INVALID_USE) will be thrown. + */ + unsigned int getStreamSampleRate( void ); + + //! Specify whether warning messages should be printed to stderr. + void showWarnings( bool value = true ) throw(); + +/* --- Monocasual hack ---------------------------------------------- */ + //protected: +/* ------------------------------------------------------------------ */ + + void openRtApi( RtAudio::Api api ); + RtApi *rtapi_; +}; + +// Operating system dependent thread functionality. +#if defined(__WINDOWS_DS__) || defined(__WINDOWS_ASIO__) || defined(__WINDOWS_WASAPI__) + + #ifndef NOMINMAX + #define NOMINMAX + #endif + #include + #include + + typedef uintptr_t ThreadHandle; + typedef CRITICAL_SECTION StreamMutex; + +#elif defined(__LINUX_ALSA__) || defined(__LINUX_PULSE__) || defined(__UNIX_JACK__) || defined(__LINUX_OSS__) || defined(__MACOSX_CORE__) + // Using pthread library for various flavors of unix. + #include + + typedef pthread_t ThreadHandle; + typedef pthread_mutex_t StreamMutex; + +#else // Setup for "dummy" behavior + + #define __RTAUDIO_DUMMY__ + typedef int ThreadHandle; + typedef int StreamMutex; + +#endif + +// This global structure type is used to pass callback information +// between the private RtAudio stream structure and global callback +// handling functions. +struct CallbackInfo { + void *object; // Used as a "this" pointer. + ThreadHandle thread; + void *callback; + void *userData; + void *errorCallback; + void *apiInfo; // void pointer for API specific callback information + bool isRunning; + bool doRealtime; + int priority; + + // Default constructor. + CallbackInfo() + :object(0), callback(0), userData(0), errorCallback(0), apiInfo(0), isRunning(false), doRealtime(false) {} +}; + +// **************************************************************** // +// +// RtApi class declaration. +// +// Subclasses of RtApi contain all API- and OS-specific code necessary +// to fully implement the RtAudio API. +// +// Note that RtApi is an abstract base class and cannot be +// explicitly instantiated. The class RtAudio will create an +// instance of an RtApi subclass (RtApiOss, RtApiAlsa, +// RtApiJack, RtApiCore, RtApiDs, or RtApiAsio). +// +// **************************************************************** // + +#pragma pack(push, 1) +class S24 { + + protected: + unsigned char c3[3]; + + public: + S24() {} + + S24& operator = ( const int& i ) { + c3[0] = (i & 0x000000ff); + c3[1] = (i & 0x0000ff00) >> 8; + c3[2] = (i & 0x00ff0000) >> 16; + return *this; + } + + S24( const S24& v ) { *this = v; } + S24( const double& d ) { *this = (int) d; } + S24( const float& f ) { *this = (int) f; } + S24( const signed short& s ) { *this = (int) s; } + S24( const char& c ) { *this = (int) c; } + + int asInt() { + int i = c3[0] | (c3[1] << 8) | (c3[2] << 16); + if (i & 0x800000) i |= ~0xffffff; + return i; + } +}; +#pragma pack(pop) + +#if defined( HAVE_GETTIMEOFDAY ) + #include +#endif + +#include + +class RtApi +{ +public: + +/* --- Monocasual hack ---------------------------------------------- */ +#ifdef __linux__ + void *__HACK__getJackClient(); +#endif +/* ------------------------------------------------------------------ */ + + RtApi(); + virtual ~RtApi(); + virtual RtAudio::Api getCurrentApi( void ) = 0; + virtual unsigned int getDeviceCount( void ) = 0; + virtual RtAudio::DeviceInfo getDeviceInfo( unsigned int device ) = 0; + virtual unsigned int getDefaultInputDevice( void ); + virtual unsigned int getDefaultOutputDevice( void ); + void openStream( RtAudio::StreamParameters *outputParameters, + RtAudio::StreamParameters *inputParameters, + RtAudioFormat format, unsigned int sampleRate, + unsigned int *bufferFrames, RtAudioCallback callback, + void *userData, RtAudio::StreamOptions *options, + RtAudioErrorCallback errorCallback ); + virtual void closeStream( void ); + virtual void startStream( void ) = 0; + virtual void stopStream( void ) = 0; + virtual void abortStream( void ) = 0; + long getStreamLatency( void ); + unsigned int getStreamSampleRate( void ); + virtual double getStreamTime( void ); + virtual void setStreamTime( double time ); + bool isStreamOpen( void ) const { return stream_.state != STREAM_CLOSED; } + bool isStreamRunning( void ) const { return stream_.state == STREAM_RUNNING; } + void showWarnings( bool value ) { showWarnings_ = value; } + + +protected: + + static const unsigned int MAX_SAMPLE_RATES; + static const unsigned int SAMPLE_RATES[]; + + enum { FAILURE, SUCCESS }; + + enum StreamState { + STREAM_STOPPED, + STREAM_STOPPING, + STREAM_RUNNING, + STREAM_CLOSED = -50 + }; + + enum StreamMode { + OUTPUT, + INPUT, + DUPLEX, + UNINITIALIZED = -75 + }; + + // A protected structure used for buffer conversion. + struct ConvertInfo { + int channels; + int inJump, outJump; + RtAudioFormat inFormat, outFormat; + std::vector inOffset; + std::vector outOffset; + }; + + // A protected structure for audio streams. + struct RtApiStream { + unsigned int device[2]; // Playback and record, respectively. + void *apiHandle; // void pointer for API specific stream handle information + StreamMode mode; // OUTPUT, INPUT, or DUPLEX. + StreamState state; // STOPPED, RUNNING, or CLOSED + char *userBuffer[2]; // Playback and record, respectively. + char *deviceBuffer; + bool doConvertBuffer[2]; // Playback and record, respectively. + bool userInterleaved; + bool deviceInterleaved[2]; // Playback and record, respectively. + bool doByteSwap[2]; // Playback and record, respectively. + unsigned int sampleRate; + unsigned int bufferSize; + unsigned int nBuffers; + unsigned int nUserChannels[2]; // Playback and record, respectively. + unsigned int nDeviceChannels[2]; // Playback and record channels, respectively. + unsigned int channelOffset[2]; // Playback and record, respectively. + unsigned long latency[2]; // Playback and record, respectively. + RtAudioFormat userFormat; + RtAudioFormat deviceFormat[2]; // Playback and record, respectively. + StreamMutex mutex; + CallbackInfo callbackInfo; + ConvertInfo convertInfo[2]; + double streamTime; // Number of elapsed seconds since the stream started. + +#if defined(HAVE_GETTIMEOFDAY) + struct timeval lastTickTimestamp; +#endif + + RtApiStream() + :apiHandle(0), deviceBuffer(0) { device[0] = 11111; device[1] = 11111; } + }; + + typedef S24 Int24; + typedef signed short Int16; + typedef signed int Int32; + typedef float Float32; + typedef double Float64; + + std::ostringstream errorStream_; + std::string errorText_; + bool showWarnings_; + RtApiStream stream_; + bool firstErrorOccurred_; + + /*! + Protected, api-specific method that attempts to open a device + with the given parameters. This function MUST be implemented by + all subclasses. If an error is encountered during the probe, a + "warning" message is reported and FAILURE is returned. A + successful probe is indicated by a return value of SUCCESS. + */ + virtual bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, + unsigned int firstChannel, unsigned int sampleRate, + RtAudioFormat format, unsigned int *bufferSize, + RtAudio::StreamOptions *options ); + + //! A protected function used to increment the stream time. + void tickStreamTime( void ); + + //! Protected common method to clear an RtApiStream structure. + void clearStreamInfo(); + + /*! + Protected common method that throws an RtAudioError (type = + INVALID_USE) if a stream is not open. + */ + void verifyStream( void ); + + //! Protected common error method to allow global control over error handling. + void error( RtAudioError::Type type ); + + /*! + Protected method used to perform format, channel number, and/or interleaving + conversions between the user and device buffers. + */ + void convertBuffer( char *outBuffer, char *inBuffer, ConvertInfo &info ); + + //! Protected common method used to perform byte-swapping on buffers. + void byteSwapBuffer( char *buffer, unsigned int samples, RtAudioFormat format ); + + //! Protected common method that returns the number of bytes for a given format. + unsigned int formatBytes( RtAudioFormat format ); + + //! Protected common method that sets up the parameters for buffer conversion. + void setConvertInfo( StreamMode mode, unsigned int firstChannel ); +}; + +// **************************************************************** // +// +// Inline RtAudio definitions. +// +// **************************************************************** // + +inline RtAudio::Api RtAudio :: getCurrentApi( void ) throw() { return rtapi_->getCurrentApi(); } +inline unsigned int RtAudio :: getDeviceCount( void ) throw() { return rtapi_->getDeviceCount(); } +inline RtAudio::DeviceInfo RtAudio :: getDeviceInfo( unsigned int device ) { return rtapi_->getDeviceInfo( device ); } +inline unsigned int RtAudio :: getDefaultInputDevice( void ) throw() { return rtapi_->getDefaultInputDevice(); } +inline unsigned int RtAudio :: getDefaultOutputDevice( void ) throw() { return rtapi_->getDefaultOutputDevice(); } +inline void RtAudio :: closeStream( void ) throw() { return rtapi_->closeStream(); } +inline void RtAudio :: startStream( void ) { return rtapi_->startStream(); } +inline void RtAudio :: stopStream( void ) { return rtapi_->stopStream(); } +inline void RtAudio :: abortStream( void ) { return rtapi_->abortStream(); } +inline bool RtAudio :: isStreamOpen( void ) const throw() { return rtapi_->isStreamOpen(); } +inline bool RtAudio :: isStreamRunning( void ) const throw() { return rtapi_->isStreamRunning(); } +inline long RtAudio :: getStreamLatency( void ) { return rtapi_->getStreamLatency(); } +inline unsigned int RtAudio :: getStreamSampleRate( void ) { return rtapi_->getStreamSampleRate(); } +inline double RtAudio :: getStreamTime( void ) { return rtapi_->getStreamTime(); } +inline void RtAudio :: setStreamTime( double time ) { return rtapi_->setStreamTime( time ); } +inline void RtAudio :: showWarnings( bool value ) throw() { rtapi_->showWarnings( value ); } + +// RtApi Subclass prototypes. + +#if defined(__MACOSX_CORE__) + +#include + +class RtApiCore: public RtApi +{ +public: + + RtApiCore(); + ~RtApiCore(); + RtAudio::Api getCurrentApi( void ) { return RtAudio::MACOSX_CORE; } + unsigned int getDeviceCount( void ); + RtAudio::DeviceInfo getDeviceInfo( unsigned int device ); + unsigned int getDefaultOutputDevice( void ); + unsigned int getDefaultInputDevice( void ); + void closeStream( void ); + void startStream( void ); + void stopStream( void ); + void abortStream( void ); + long getStreamLatency( void ); + + // This function is intended for internal use only. It must be + // public because it is called by the internal callback handler, + // which is not a member of RtAudio. External use of this function + // will most likely produce highly undesireable results! + bool callbackEvent( AudioDeviceID deviceId, + const AudioBufferList *inBufferList, + const AudioBufferList *outBufferList ); + + private: + + bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, + unsigned int firstChannel, unsigned int sampleRate, + RtAudioFormat format, unsigned int *bufferSize, + RtAudio::StreamOptions *options ); + static const char* getErrorCode( OSStatus code ); +}; + +#endif + +#if defined(__UNIX_JACK__) + +class RtApiJack: public RtApi +{ +public: + + RtApiJack(); + ~RtApiJack(); + RtAudio::Api getCurrentApi( void ) { return RtAudio::UNIX_JACK; } + unsigned int getDeviceCount( void ); + RtAudio::DeviceInfo getDeviceInfo( unsigned int device ); + void closeStream( void ); + void startStream( void ); + void stopStream( void ); + void abortStream( void ); + long getStreamLatency( void ); + + // This function is intended for internal use only. It must be + // public because it is called by the internal callback handler, + // which is not a member of RtAudio. External use of this function + // will most likely produce highly undesireable results! + bool callbackEvent( unsigned long nframes ); + + private: + + bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, + unsigned int firstChannel, unsigned int sampleRate, + RtAudioFormat format, unsigned int *bufferSize, + RtAudio::StreamOptions *options ); +}; + +#endif + +#if defined(__WINDOWS_ASIO__) + +class RtApiAsio: public RtApi +{ +public: + + RtApiAsio(); + ~RtApiAsio(); + RtAudio::Api getCurrentApi( void ) { return RtAudio::WINDOWS_ASIO; } + unsigned int getDeviceCount( void ); + RtAudio::DeviceInfo getDeviceInfo( unsigned int device ); + void closeStream( void ); + void startStream( void ); + void stopStream( void ); + void abortStream( void ); + long getStreamLatency( void ); + + // This function is intended for internal use only. It must be + // public because it is called by the internal callback handler, + // which is not a member of RtAudio. External use of this function + // will most likely produce highly undesireable results! + bool callbackEvent( long bufferIndex ); + + private: + + std::vector devices_; + void saveDeviceInfo( void ); + bool coInitialized_; + bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, + unsigned int firstChannel, unsigned int sampleRate, + RtAudioFormat format, unsigned int *bufferSize, + RtAudio::StreamOptions *options ); +}; + +#endif + +#if defined(__WINDOWS_DS__) + +class RtApiDs: public RtApi +{ +public: + + RtApiDs(); + ~RtApiDs(); + RtAudio::Api getCurrentApi( void ) { return RtAudio::WINDOWS_DS; } + unsigned int getDeviceCount( void ); + unsigned int getDefaultOutputDevice( void ); + unsigned int getDefaultInputDevice( void ); + RtAudio::DeviceInfo getDeviceInfo( unsigned int device ); + void closeStream( void ); + void startStream( void ); + void stopStream( void ); + void abortStream( void ); + long getStreamLatency( void ); + + // This function is intended for internal use only. It must be + // public because it is called by the internal callback handler, + // which is not a member of RtAudio. External use of this function + // will most likely produce highly undesireable results! + void callbackEvent( void ); + + private: + + bool coInitialized_; + bool buffersRolling; + long duplexPrerollBytes; + std::vector dsDevices; + bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, + unsigned int firstChannel, unsigned int sampleRate, + RtAudioFormat format, unsigned int *bufferSize, + RtAudio::StreamOptions *options ); +}; + +#endif + +#if defined(__WINDOWS_WASAPI__) + +struct IMMDeviceEnumerator; + +class RtApiWasapi : public RtApi +{ +public: + RtApiWasapi(); + ~RtApiWasapi(); + + RtAudio::Api getCurrentApi( void ) { return RtAudio::WINDOWS_WASAPI; } + unsigned int getDeviceCount( void ); + RtAudio::DeviceInfo getDeviceInfo( unsigned int device ); + unsigned int getDefaultOutputDevice( void ); + unsigned int getDefaultInputDevice( void ); + void closeStream( void ); + void startStream( void ); + void stopStream( void ); + void abortStream( void ); + +private: + bool coInitialized_; + IMMDeviceEnumerator* deviceEnumerator_; + + bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, + unsigned int firstChannel, unsigned int sampleRate, + RtAudioFormat format, unsigned int* bufferSize, + RtAudio::StreamOptions* options ); + + static DWORD WINAPI runWasapiThread( void* wasapiPtr ); + static DWORD WINAPI stopWasapiThread( void* wasapiPtr ); + static DWORD WINAPI abortWasapiThread( void* wasapiPtr ); + void wasapiThread(); +}; + +#endif + +#if defined(__LINUX_ALSA__) + +class RtApiAlsa: public RtApi +{ +public: + + RtApiAlsa(); + ~RtApiAlsa(); + RtAudio::Api getCurrentApi() { return RtAudio::LINUX_ALSA; } + unsigned int getDeviceCount( void ); + RtAudio::DeviceInfo getDeviceInfo( unsigned int device ); + void closeStream( void ); + void startStream( void ); + void stopStream( void ); + void abortStream( void ); + + // This function is intended for internal use only. It must be + // public because it is called by the internal callback handler, + // which is not a member of RtAudio. External use of this function + // will most likely produce highly undesireable results! + void callbackEvent( void ); + + private: + + std::vector devices_; + void saveDeviceInfo( void ); + bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, + unsigned int firstChannel, unsigned int sampleRate, + RtAudioFormat format, unsigned int *bufferSize, + RtAudio::StreamOptions *options ); +}; + +#endif + +#if defined(__LINUX_PULSE__) + +class RtApiPulse: public RtApi +{ +public: + ~RtApiPulse(); + RtAudio::Api getCurrentApi() { return RtAudio::LINUX_PULSE; } + unsigned int getDeviceCount( void ); + RtAudio::DeviceInfo getDeviceInfo( unsigned int device ); + void closeStream( void ); + void startStream( void ); + void stopStream( void ); + void abortStream( void ); + + // This function is intended for internal use only. It must be + // public because it is called by the internal callback handler, + // which is not a member of RtAudio. External use of this function + // will most likely produce highly undesireable results! + void callbackEvent( void ); + + private: + + std::vector devices_; + void saveDeviceInfo( void ); + bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, + unsigned int firstChannel, unsigned int sampleRate, + RtAudioFormat format, unsigned int *bufferSize, + RtAudio::StreamOptions *options ); +}; + +#endif + +#if defined(__LINUX_OSS__) + +class RtApiOss: public RtApi +{ +public: + + RtApiOss(); + ~RtApiOss(); + RtAudio::Api getCurrentApi() { return RtAudio::LINUX_OSS; } + unsigned int getDeviceCount( void ); + RtAudio::DeviceInfo getDeviceInfo( unsigned int device ); + void closeStream( void ); + void startStream( void ); + void stopStream( void ); + void abortStream( void ); + + // This function is intended for internal use only. It must be + // public because it is called by the internal callback handler, + // which is not a member of RtAudio. External use of this function + // will most likely produce highly undesireable results! + void callbackEvent( void ); + + private: + + bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, + unsigned int firstChannel, unsigned int sampleRate, + RtAudioFormat format, unsigned int *bufferSize, + RtAudio::StreamOptions *options ); +}; + +#endif + +#if defined(__RTAUDIO_DUMMY__) + +class RtApiDummy: public RtApi +{ +public: + + RtApiDummy() { errorText_ = "RtApiDummy: This class provides no functionality."; error( RtAudioError::WARNING ); } + RtAudio::Api getCurrentApi( void ) { return RtAudio::RTAUDIO_DUMMY; } + unsigned int getDeviceCount( void ) { return 0; } + RtAudio::DeviceInfo getDeviceInfo( unsigned int /*device*/ ) { RtAudio::DeviceInfo info; return info; } + void closeStream( void ) {} + void startStream( void ) {} + void stopStream( void ) {} + void abortStream( void ) {} + + private: + + bool probeDeviceOpen( unsigned int /*device*/, StreamMode /*mode*/, unsigned int /*channels*/, + unsigned int /*firstChannel*/, unsigned int /*sampleRate*/, + RtAudioFormat /*format*/, unsigned int * /*bufferSize*/, + RtAudio::StreamOptions * /*options*/ ) { return false; } +}; + +#endif + +#endif + +// Indentation settings for Vim and Emacs +// +// Local Variables: +// c-basic-offset: 2 +// indent-tabs-mode: nil +// End: +// +// vim: et sts=2 sw=2 diff --git a/src/rtaudio-mod/config/config.guess b/src/rtaudio-mod/config/config.guess new file mode 100644 index 0000000..313be34 --- /dev/null +++ b/src/rtaudio-mod/config/config.guess @@ -0,0 +1,1371 @@ +#! /bin/sh +# Attempt to guess a canonical system name. +# Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001 +# Free Software Foundation, Inc. + +timestamp='2004-02-26' + +# This file is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 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, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + +# Written by Per Bothner . +# Please send patches to . +# +# This script attempts to guess a canonical system name similar to +# config.sub. If it succeeds, it prints the system name on stdout, and +# exits with 0. Otherwise, it exits with 1. +# +# The plan is that this can be called by configure scripts if you +# don't specify an explicit build system type. + +me=`echo "$0" | sed -e 's,.*/,,'` + +usage="\ +Usage: $0 [OPTION] + +Output the configuration name of the system \`$me' is run on. + +Operation modes: + -h, --help print this help, then exit + -t, --time-stamp print date of last modification, then exit + -v, --version print version number, then exit + +Report bugs and patches to ." + +version="\ +GNU config.guess ($timestamp) + +Originally written by Per Bothner. +Copyright (C) 1992, 93, 94, 95, 96, 97, 98, 99, 2000 +Free Software Foundation, Inc. + +This is free software; see the source for copying conditions. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." + +help=" +Try \`$me --help' for more information." + +# Parse command line +while test $# -gt 0 ; do + case $1 in + --time-stamp | --time* | -t ) + echo "$timestamp" ; exit 0 ;; + --version | -v ) + echo "$version" ; exit 0 ;; + --help | --h* | -h ) + echo "$usage"; exit 0 ;; + -- ) # Stop option processing + shift; break ;; + - ) # Use stdin as input. + break ;; + -* ) + echo "$me: invalid option $1$help" >&2 + exit 1 ;; + * ) + break ;; + esac +done + +if test $# != 0; then + echo "$me: too many arguments$help" >&2 + exit 1 +fi + + +dummy=dummy-$$ +trap 'rm -f $dummy.c $dummy.o $dummy.rel $dummy; exit 1' 1 2 15 + +# CC_FOR_BUILD -- compiler used by this script. +# Historically, `CC_FOR_BUILD' used to be named `HOST_CC'. We still +# use `HOST_CC' if defined, but it is deprecated. + +case $CC_FOR_BUILD,$HOST_CC,$CC in + ,,) echo "int dummy(){}" > $dummy.c + for c in cc gcc c89 ; do + ($c $dummy.c -c -o $dummy.o) >/dev/null 2>&1 + if test $? = 0 ; then + CC_FOR_BUILD="$c"; break + fi + done + rm -f $dummy.c $dummy.o $dummy.rel + if test x"$CC_FOR_BUILD" = x ; then + CC_FOR_BUILD=no_compiler_found + fi + ;; + ,,*) CC_FOR_BUILD=$CC ;; + ,*,*) CC_FOR_BUILD=$HOST_CC ;; +esac + +# This is needed to find uname on a Pyramid OSx when run in the BSD universe. +# (ghazi@noc.rutgers.edu 8/24/94.) +if (test -f /.attbin/uname) >/dev/null 2>&1 ; then + PATH=$PATH:/.attbin ; export PATH +fi + +UNAME_MACHINE=`(uname -m) 2>/dev/null` || UNAME_MACHINE=unknown +UNAME_RELEASE=`(uname -r) 2>/dev/null` || UNAME_RELEASE=unknown +UNAME_SYSTEM=`(uname -s) 2>/dev/null` || UNAME_SYSTEM=unknown +UNAME_VERSION=`(uname -v) 2>/dev/null` || UNAME_VERSION=unknown + +# Note: order is significant - the case branches are not exclusive. + +case "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" in + *:NetBSD:*:*) + # Netbsd (nbsd) targets should (where applicable) match one or + # more of the tupples: *-*-netbsdelf*, *-*-netbsdaout*, + # *-*-netbsdecoff* and *-*-netbsd*. For targets that recently + # switched to ELF, *-*-netbsd* would select the old + # object file format. This provides both forward + # compatibility and a consistent mechanism for selecting the + # object file format. + # Determine the machine/vendor (is the vendor relevant). + case "${UNAME_MACHINE}" in + amiga) machine=m68k-unknown ;; + arm32) machine=arm-unknown ;; + atari*) machine=m68k-atari ;; + sun3*) machine=m68k-sun ;; + mac68k) machine=m68k-apple ;; + macppc) machine=powerpc-apple ;; + hp3[0-9][05]) machine=m68k-hp ;; + ibmrt|romp-ibm) machine=romp-ibm ;; + *) machine=${UNAME_MACHINE}-unknown ;; + esac + # The Operating System including object format, if it has switched + # to ELF recently, or will in the future. + case "${UNAME_MACHINE}" in + i386|sparc|amiga|arm*|hp300|mvme68k|vax|atari|luna68k|mac68k|news68k|next68k|pc532|sun3*|x68k) + if echo __ELF__ | $CC_FOR_BUILD -E - 2>/dev/null \ + | grep __ELF__ >/dev/null + then + # Once all utilities can be ECOFF (netbsdecoff) or a.out (netbsdaout). + # Return netbsd for either. FIX? + os=netbsd + else + os=netbsdelf + fi + ;; + *) + os=netbsd + ;; + esac + # The OS release + release=`echo ${UNAME_RELEASE}|sed -e 's/[-_].*/\./'` + # Since CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM: + # contains redundant information, the shorter form: + # CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM is used. + echo "${machine}-${os}${release}" + exit 0 ;; + alpha:OSF1:*:*) + if test $UNAME_RELEASE = "V4.0"; then + UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $3}'` + fi + # A Vn.n version is a released version. + # A Tn.n version is a released field test version. + # A Xn.n version is an unreleased experimental baselevel. + # 1.2 uses "1.2" for uname -r. + cat <$dummy.s + .data +\$Lformat: + .byte 37,100,45,37,120,10,0 # "%d-%x\n" + + .text + .globl main + .align 4 + .ent main +main: + .frame \$30,16,\$26,0 + ldgp \$29,0(\$27) + .prologue 1 + .long 0x47e03d80 # implver \$0 + lda \$2,-1 + .long 0x47e20c21 # amask \$2,\$1 + lda \$16,\$Lformat + mov \$0,\$17 + not \$1,\$18 + jsr \$26,printf + ldgp \$29,0(\$26) + mov 0,\$16 + jsr \$26,exit + .end main +EOF + $CC_FOR_BUILD $dummy.s -o $dummy 2>/dev/null + if test "$?" = 0 ; then + case `./$dummy` in + 0-0) + UNAME_MACHINE="alpha" + ;; + 1-0) + UNAME_MACHINE="alphaev5" + ;; + 1-1) + UNAME_MACHINE="alphaev56" + ;; + 1-101) + UNAME_MACHINE="alphapca56" + ;; + 2-303) + UNAME_MACHINE="alphaev6" + ;; + 2-307) + UNAME_MACHINE="alphaev67" + ;; + esac + fi + rm -f $dummy.s $dummy + echo ${UNAME_MACHINE}-dec-osf`echo ${UNAME_RELEASE} | sed -e 's/^[VTX]//' | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz'` + exit 0 ;; + Alpha\ *:Windows_NT*:*) + # How do we know it's Interix rather than the generic POSIX subsystem? + # Should we change UNAME_MACHINE based on the output of uname instead + # of the specific Alpha model? + echo alpha-pc-interix + exit 0 ;; + 21064:Windows_NT:50:3) + echo alpha-dec-winnt3.5 + exit 0 ;; + Amiga*:UNIX_System_V:4.0:*) + echo m68k-unknown-sysv4 + exit 0;; + amiga:OpenBSD:*:*) + echo m68k-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + *:[Aa]miga[Oo][Ss]:*:*) + echo ${UNAME_MACHINE}-unknown-amigaos + exit 0 ;; + arc64:OpenBSD:*:*) + echo mips64el-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + arc:OpenBSD:*:*) + echo mipsel-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + hkmips:OpenBSD:*:*) + echo mips-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + pmax:OpenBSD:*:*) + echo mipsel-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + sgi:OpenBSD:*:*) + echo mips-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + wgrisc:OpenBSD:*:*) + echo mipsel-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + *:OS/390:*:*) + echo i370-ibm-openedition + exit 0 ;; + arm:RISC*:1.[012]*:*|arm:riscix:1.[012]*:*) + echo arm-acorn-riscix${UNAME_RELEASE} + exit 0;; + SR2?01:HI-UX/MPP:*:* | SR8000:HI-UX/MPP:*:*) + echo hppa1.1-hitachi-hiuxmpp + exit 0;; + Pyramid*:OSx*:*:* | MIS*:OSx*:*:* | MIS*:SMP_DC-OSx*:*:*) + # akee@wpdis03.wpafb.af.mil (Earle F. Ake) contributed MIS and NILE. + if test "`(/bin/universe) 2>/dev/null`" = att ; then + echo pyramid-pyramid-sysv3 + else + echo pyramid-pyramid-bsd + fi + exit 0 ;; + NILE*:*:*:dcosx) + echo pyramid-pyramid-svr4 + exit 0 ;; + sun4H:SunOS:5.*:*) + echo sparc-hal-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit 0 ;; + sun4*:SunOS:5.*:* | tadpole*:SunOS:5.*:*) + echo sparc-sun-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit 0 ;; + i86pc:SunOS:5.*:*) + echo i386-pc-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit 0 ;; + sun4*:SunOS:6*:*) + # According to config.sub, this is the proper way to canonicalize + # SunOS6. Hard to guess exactly what SunOS6 will be like, but + # it's likely to be more like Solaris than SunOS4. + echo sparc-sun-solaris3`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit 0 ;; + sun4*:SunOS:*:*) + case "`/usr/bin/arch -k`" in + Series*|S4*) + UNAME_RELEASE=`uname -v` + ;; + esac + # Japanese Language versions have a version number like `4.1.3-JL'. + echo sparc-sun-sunos`echo ${UNAME_RELEASE}|sed -e 's/-/_/'` + exit 0 ;; + sun3*:SunOS:*:*) + echo m68k-sun-sunos${UNAME_RELEASE} + exit 0 ;; + sun*:*:4.2BSD:*) + UNAME_RELEASE=`(head -1 /etc/motd | awk '{print substr($5,1,3)}') 2>/dev/null` + test "x${UNAME_RELEASE}" = "x" && UNAME_RELEASE=3 + case "`/bin/arch`" in + sun3) + echo m68k-sun-sunos${UNAME_RELEASE} + ;; + sun4) + echo sparc-sun-sunos${UNAME_RELEASE} + ;; + esac + exit 0 ;; + aushp:SunOS:*:*) + echo sparc-auspex-sunos${UNAME_RELEASE} + exit 0 ;; + atari*:OpenBSD:*:*) + echo m68k-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + # The situation for MiNT is a little confusing. The machine name + # can be virtually everything (everything which is not + # "atarist" or "atariste" at least should have a processor + # > m68000). The system name ranges from "MiNT" over "FreeMiNT" + # to the lowercase version "mint" (or "freemint"). Finally + # the system name "TOS" denotes a system which is actually not + # MiNT. But MiNT is downward compatible to TOS, so this should + # be no problem. + atarist[e]:*MiNT:*:* | atarist[e]:*mint:*:* | atarist[e]:*TOS:*:*) + echo m68k-atari-mint${UNAME_RELEASE} + exit 0 ;; + atari*:*MiNT:*:* | atari*:*mint:*:* | atarist[e]:*TOS:*:*) + echo m68k-atari-mint${UNAME_RELEASE} + exit 0 ;; + *falcon*:*MiNT:*:* | *falcon*:*mint:*:* | *falcon*:*TOS:*:*) + echo m68k-atari-mint${UNAME_RELEASE} + exit 0 ;; + milan*:*MiNT:*:* | milan*:*mint:*:* | *milan*:*TOS:*:*) + echo m68k-milan-mint${UNAME_RELEASE} + exit 0 ;; + hades*:*MiNT:*:* | hades*:*mint:*:* | *hades*:*TOS:*:*) + echo m68k-hades-mint${UNAME_RELEASE} + exit 0 ;; + *:*MiNT:*:* | *:*mint:*:* | *:*TOS:*:*) + echo m68k-unknown-mint${UNAME_RELEASE} + exit 0 ;; + sun3*:OpenBSD:*:*) + echo m68k-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + mac68k:OpenBSD:*:*) + echo m68k-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + mvme68k:OpenBSD:*:*) + echo m68k-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + mvme88k:OpenBSD:*:*) + echo m88k-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + powerpc:machten:*:*) + echo powerpc-apple-machten${UNAME_RELEASE} + exit 0 ;; + RISC*:Mach:*:*) + echo mips-dec-mach_bsd4.3 + exit 0 ;; + RISC*:ULTRIX:*:*) + echo mips-dec-ultrix${UNAME_RELEASE} + exit 0 ;; + VAX*:ULTRIX*:*:*) + echo vax-dec-ultrix${UNAME_RELEASE} + exit 0 ;; + 2020:CLIX:*:* | 2430:CLIX:*:*) + echo clipper-intergraph-clix${UNAME_RELEASE} + exit 0 ;; + mips:*:*:UMIPS | mips:*:*:RISCos) + sed 's/^ //' << EOF >$dummy.c +#ifdef __cplusplus +#include /* for printf() prototype */ + int main (int argc, char *argv[]) { +#else + int main (argc, argv) int argc; char *argv[]; { +#endif + #if defined (host_mips) && defined (MIPSEB) + #if defined (SYSTYPE_SYSV) + printf ("mips-mips-riscos%ssysv\n", argv[1]); exit (0); + #endif + #if defined (SYSTYPE_SVR4) + printf ("mips-mips-riscos%ssvr4\n", argv[1]); exit (0); + #endif + #if defined (SYSTYPE_BSD43) || defined(SYSTYPE_BSD) + printf ("mips-mips-riscos%sbsd\n", argv[1]); exit (0); + #endif + #endif + exit (-1); + } +EOF + $CC_FOR_BUILD $dummy.c -o $dummy \ + && ./$dummy `echo "${UNAME_RELEASE}" | sed -n 's/\([0-9]*\).*/\1/p'` \ + && rm -f $dummy.c $dummy && exit 0 + rm -f $dummy.c $dummy + echo mips-mips-riscos${UNAME_RELEASE} + exit 0 ;; + Motorola:PowerMAX_OS:*:*) + echo powerpc-motorola-powermax + exit 0 ;; + Night_Hawk:Power_UNIX:*:*) + echo powerpc-harris-powerunix + exit 0 ;; + m88k:CX/UX:7*:*) + echo m88k-harris-cxux7 + exit 0 ;; + m88k:*:4*:R4*) + echo m88k-motorola-sysv4 + exit 0 ;; + m88k:*:3*:R3*) + echo m88k-motorola-sysv3 + exit 0 ;; + AViiON:dgux:*:*) + # DG/UX returns AViiON for all architectures + UNAME_PROCESSOR=`/usr/bin/uname -p` + if [ $UNAME_PROCESSOR = mc88100 ] || [ $UNAME_PROCESSOR = mc88110 ] + then + if [ ${TARGET_BINARY_INTERFACE}x = m88kdguxelfx ] || \ + [ ${TARGET_BINARY_INTERFACE}x = x ] + then + echo m88k-dg-dgux${UNAME_RELEASE} + else + echo m88k-dg-dguxbcs${UNAME_RELEASE} + fi + else + echo i586-dg-dgux${UNAME_RELEASE} + fi + exit 0 ;; + M88*:DolphinOS:*:*) # DolphinOS (SVR3) + echo m88k-dolphin-sysv3 + exit 0 ;; + M88*:*:R3*:*) + # Delta 88k system running SVR3 + echo m88k-motorola-sysv3 + exit 0 ;; + XD88*:*:*:*) # Tektronix XD88 system running UTekV (SVR3) + echo m88k-tektronix-sysv3 + exit 0 ;; + Tek43[0-9][0-9]:UTek:*:*) # Tektronix 4300 system running UTek (BSD) + echo m68k-tektronix-bsd + exit 0 ;; + *:IRIX*:*:*) + echo mips-sgi-irix`echo ${UNAME_RELEASE}|sed -e 's/-/_/g'` + exit 0 ;; + ????????:AIX?:[12].1:2) # AIX 2.2.1 or AIX 2.1.1 is RT/PC AIX. + echo romp-ibm-aix # uname -m gives an 8 hex-code CPU id + exit 0 ;; # Note that: echo "'`uname -s`'" gives 'AIX ' + i*86:AIX:*:*) + echo i386-ibm-aix + exit 0 ;; + ia64:AIX:*:*) + if [ -x /usr/bin/oslevel ] ; then + IBM_REV=`/usr/bin/oslevel` + else + IBM_REV=${UNAME_VERSION}.${UNAME_RELEASE} + fi + echo ${UNAME_MACHINE}-ibm-aix${IBM_REV} + exit 0 ;; + *:AIX:2:3) + if grep bos325 /usr/include/stdio.h >/dev/null 2>&1; then + sed 's/^ //' << EOF >$dummy.c + #include + + main() + { + if (!__power_pc()) + exit(1); + puts("powerpc-ibm-aix3.2.5"); + exit(0); + } +EOF + $CC_FOR_BUILD $dummy.c -o $dummy && ./$dummy && rm -f $dummy.c $dummy && exit 0 + rm -f $dummy.c $dummy + echo rs6000-ibm-aix3.2.5 + elif grep bos324 /usr/include/stdio.h >/dev/null 2>&1; then + echo rs6000-ibm-aix3.2.4 + else + echo rs6000-ibm-aix3.2 + fi + exit 0 ;; + *:AIX:*:[45]) + IBM_CPU_ID=`/usr/sbin/lsdev -C -c processor -S available | head -1 | awk '{ print $1 }'` + if /usr/sbin/lsattr -El ${IBM_CPU_ID} | grep ' POWER' >/dev/null 2>&1; then + IBM_ARCH=rs6000 + else + IBM_ARCH=powerpc + fi + if [ -x /usr/bin/oslevel ] ; then + IBM_REV=`/usr/bin/oslevel` + else + IBM_REV=${UNAME_VERSION}.${UNAME_RELEASE} + fi + echo ${IBM_ARCH}-ibm-aix${IBM_REV} + exit 0 ;; + *:AIX:*:*) + echo rs6000-ibm-aix + exit 0 ;; + ibmrt:4.4BSD:*|romp-ibm:BSD:*) + echo romp-ibm-bsd4.4 + exit 0 ;; + ibmrt:*BSD:*|romp-ibm:BSD:*) # covers RT/PC BSD and + echo romp-ibm-bsd${UNAME_RELEASE} # 4.3 with uname added to + exit 0 ;; # report: romp-ibm BSD 4.3 + *:BOSX:*:*) + echo rs6000-bull-bosx + exit 0 ;; + DPX/2?00:B.O.S.:*:*) + echo m68k-bull-sysv3 + exit 0 ;; + 9000/[34]??:4.3bsd:1.*:*) + echo m68k-hp-bsd + exit 0 ;; + hp300:4.4BSD:*:* | 9000/[34]??:4.3bsd:2.*:*) + echo m68k-hp-bsd4.4 + exit 0 ;; + 9000/[34678]??:HP-UX:*:*) + HPUX_REV=`echo ${UNAME_RELEASE}|sed -e 's/[^.]*.[0B]*//'` + case "${UNAME_MACHINE}" in + 9000/31? ) HP_ARCH=m68000 ;; + 9000/[34]?? ) HP_ARCH=m68k ;; + 9000/[678][0-9][0-9]) + case "${HPUX_REV}" in + 11.[0-9][0-9]) + if [ -x /usr/bin/getconf ]; then + sc_cpu_version=`/usr/bin/getconf SC_CPU_VERSION 2>/dev/null` + sc_kernel_bits=`/usr/bin/getconf SC_KERNEL_BITS 2>/dev/null` + case "${sc_cpu_version}" in + 523) HP_ARCH="hppa1.0" ;; # CPU_PA_RISC1_0 + 528) HP_ARCH="hppa1.1" ;; # CPU_PA_RISC1_1 + 532) # CPU_PA_RISC2_0 + case "${sc_kernel_bits}" in + 32) HP_ARCH="hppa2.0n" ;; + 64) HP_ARCH="hppa2.0w" ;; + esac ;; + esac + fi ;; + esac + if [ "${HP_ARCH}" = "" ]; then + sed 's/^ //' << EOF >$dummy.c + + #define _HPUX_SOURCE + #include + #include + + int main () + { + #if defined(_SC_KERNEL_BITS) + long bits = sysconf(_SC_KERNEL_BITS); + #endif + long cpu = sysconf (_SC_CPU_VERSION); + + switch (cpu) + { + case CPU_PA_RISC1_0: puts ("hppa1.0"); break; + case CPU_PA_RISC1_1: puts ("hppa1.1"); break; + case CPU_PA_RISC2_0: + #if defined(_SC_KERNEL_BITS) + switch (bits) + { + case 64: puts ("hppa2.0w"); break; + case 32: puts ("hppa2.0n"); break; + default: puts ("hppa2.0"); break; + } break; + #else /* !defined(_SC_KERNEL_BITS) */ + puts ("hppa2.0"); break; + #endif + default: puts ("hppa1.0"); break; + } + exit (0); + } +EOF + (CCOPTS= $CC_FOR_BUILD $dummy.c -o $dummy 2>/dev/null ) && HP_ARCH=`./$dummy` + if test -z "$HP_ARCH"; then HP_ARCH=hppa; fi + rm -f $dummy.c $dummy + fi ;; + esac + echo ${HP_ARCH}-hp-hpux${HPUX_REV} + exit 0 ;; + ia64:HP-UX:*:*) + HPUX_REV=`echo ${UNAME_RELEASE}|sed -e 's/[^.]*.[0B]*//'` + echo ia64-hp-hpux${HPUX_REV} + exit 0 ;; + 3050*:HI-UX:*:*) + sed 's/^ //' << EOF >$dummy.c + #include + int + main () + { + long cpu = sysconf (_SC_CPU_VERSION); + /* The order matters, because CPU_IS_HP_MC68K erroneously returns + true for CPU_PA_RISC1_0. CPU_IS_PA_RISC returns correct + results, however. */ + if (CPU_IS_PA_RISC (cpu)) + { + switch (cpu) + { + case CPU_PA_RISC1_0: puts ("hppa1.0-hitachi-hiuxwe2"); break; + case CPU_PA_RISC1_1: puts ("hppa1.1-hitachi-hiuxwe2"); break; + case CPU_PA_RISC2_0: puts ("hppa2.0-hitachi-hiuxwe2"); break; + default: puts ("hppa-hitachi-hiuxwe2"); break; + } + } + else if (CPU_IS_HP_MC68K (cpu)) + puts ("m68k-hitachi-hiuxwe2"); + else puts ("unknown-hitachi-hiuxwe2"); + exit (0); + } +EOF + $CC_FOR_BUILD $dummy.c -o $dummy && ./$dummy && rm -f $dummy.c $dummy && exit 0 + rm -f $dummy.c $dummy + echo unknown-hitachi-hiuxwe2 + exit 0 ;; + 9000/7??:4.3bsd:*:* | 9000/8?[79]:4.3bsd:*:* ) + echo hppa1.1-hp-bsd + exit 0 ;; + 9000/8??:4.3bsd:*:*) + echo hppa1.0-hp-bsd + exit 0 ;; + *9??*:MPE/iX:*:*) + echo hppa1.0-hp-mpeix + exit 0 ;; + hp7??:OSF1:*:* | hp8?[79]:OSF1:*:* ) + echo hppa1.1-hp-osf + exit 0 ;; + hp8??:OSF1:*:*) + echo hppa1.0-hp-osf + exit 0 ;; + i*86:OSF1:*:*) + if [ -x /usr/sbin/sysversion ] ; then + echo ${UNAME_MACHINE}-unknown-osf1mk + else + echo ${UNAME_MACHINE}-unknown-osf1 + fi + exit 0 ;; + parisc*:Lites*:*:*) + echo hppa1.1-hp-lites + exit 0 ;; + hppa*:OpenBSD:*:*) + echo hppa-unknown-openbsd + exit 0 ;; + C1*:ConvexOS:*:* | convex:ConvexOS:C1*:*) + echo c1-convex-bsd + exit 0 ;; + C2*:ConvexOS:*:* | convex:ConvexOS:C2*:*) + if getsysinfo -f scalar_acc + then echo c32-convex-bsd + else echo c2-convex-bsd + fi + exit 0 ;; + C34*:ConvexOS:*:* | convex:ConvexOS:C34*:*) + echo c34-convex-bsd + exit 0 ;; + C38*:ConvexOS:*:* | convex:ConvexOS:C38*:*) + echo c38-convex-bsd + exit 0 ;; + C4*:ConvexOS:*:* | convex:ConvexOS:C4*:*) + echo c4-convex-bsd + exit 0 ;; + CRAY*X-MP:*:*:*) + echo xmp-cray-unicos + exit 0 ;; + CRAY*Y-MP:*:*:*) + echo ymp-cray-unicos${UNAME_RELEASE} + exit 0 ;; + CRAY*[A-Z]90:*:*:*) + echo ${UNAME_MACHINE}-cray-unicos${UNAME_RELEASE} \ + | sed -e 's/CRAY.*\([A-Z]90\)/\1/' \ + -e y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/ + exit 0 ;; + CRAY*TS:*:*:*) + echo t90-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' + exit 0 ;; + CRAY*T3D:*:*:*) + echo alpha-cray-unicosmk${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' + exit 0 ;; + CRAY*T3E:*:*:*) + echo alphaev5-cray-unicosmk${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' + exit 0 ;; + CRAY*SV1:*:*:*) + echo sv1-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' + exit 0 ;; + CRAY-2:*:*:*) + echo cray2-cray-unicos + exit 0 ;; + F30[01]:UNIX_System_V:*:* | F700:UNIX_System_V:*:*) + FUJITSU_PROC=`uname -m | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz'` + FUJITSU_SYS=`uname -p | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/\///'` + FUJITSU_REL=`echo ${UNAME_RELEASE} | sed -e 's/ /_/'` + echo "${FUJITSU_PROC}-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}" + exit 0 ;; + hp300:OpenBSD:*:*) + echo m68k-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + i*86:BSD/386:*:* | i*86:BSD/OS:*:* | *:Ascend\ Embedded/OS:*:*) + echo ${UNAME_MACHINE}-pc-bsdi${UNAME_RELEASE} + exit 0 ;; + sparc*:BSD/OS:*:*) + echo sparc-unknown-bsdi${UNAME_RELEASE} + exit 0 ;; + *:BSD/OS:*:*) + echo ${UNAME_MACHINE}-unknown-bsdi${UNAME_RELEASE} + exit 0 ;; + *:FreeBSD:*:*) + echo ${UNAME_MACHINE}-unknown-freebsd`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'` + exit 0 ;; + *:OpenBSD:*:*) + echo ${UNAME_MACHINE}-unknown-openbsd`echo ${UNAME_RELEASE}|sed -e 's/[-_].*/\./'` + exit 0 ;; + i*:CYGWIN*:*) + echo ${UNAME_MACHINE}-pc-cygwin + exit 0 ;; + i*:MINGW*:*) + echo ${UNAME_MACHINE}-pc-mingw32 + exit 0 ;; + i*:PW*:*) + echo ${UNAME_MACHINE}-pc-pw32 + exit 0 ;; + i*:Windows_NT*:* | Pentium*:Windows_NT*:*) + # How do we know it's Interix rather than the generic POSIX subsystem? + # It also conflicts with pre-2.0 versions of AT&T UWIN. Should we + # UNAME_MACHINE based on the output of uname instead of i386? + echo i386-pc-interix + exit 0 ;; + i*:UWIN*:*) + echo ${UNAME_MACHINE}-pc-uwin + exit 0 ;; + p*:CYGWIN*:*) + echo powerpcle-unknown-cygwin + exit 0 ;; + prep*:SunOS:5.*:*) + echo powerpcle-unknown-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit 0 ;; + *:GNU:*:*) + echo `echo ${UNAME_MACHINE}|sed -e 's,[-/].*$,,'`-unknown-gnu`echo ${UNAME_RELEASE}|sed -e 's,/.*$,,'` + exit 0 ;; + i*86:Minix:*:*) + echo ${UNAME_MACHINE}-pc-minix + exit 0 ;; + arm*:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-gnu + exit 0 ;; + ia64:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux + exit 0 ;; + m68*:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-gnu + exit 0 ;; + mips:Linux:*:*) + cat >$dummy.c < /* for printf() prototype */ +int main (int argc, char *argv[]) { +#else +int main (argc, argv) int argc; char *argv[]; { +#endif +#ifdef __MIPSEB__ + printf ("%s-unknown-linux-gnu\n", argv[1]); +#endif +#ifdef __MIPSEL__ + printf ("%sel-unknown-linux-gnu\n", argv[1]); +#endif + return 0; +} +EOF + $CC_FOR_BUILD $dummy.c -o $dummy 2>/dev/null && ./$dummy "${UNAME_MACHINE}" && rm -f $dummy.c $dummy && exit 0 + rm -f $dummy.c $dummy + ;; + ppc:Linux:*:*) + # Determine Lib Version + cat >$dummy.c < +#if defined(__GLIBC__) +extern char __libc_version[]; +extern char __libc_release[]; +#endif +main(argc, argv) + int argc; + char *argv[]; +{ +#if defined(__GLIBC__) + printf("%s %s\n", __libc_version, __libc_release); +#else + printf("unknown\n"); +#endif + return 0; +} +EOF + LIBC="" + $CC_FOR_BUILD $dummy.c -o $dummy 2>/dev/null + if test "$?" = 0 ; then + ./$dummy | grep 1\.99 > /dev/null + if test "$?" = 0 ; then LIBC="libc1" ; fi + fi + rm -f $dummy.c $dummy + echo powerpc-unknown-linux-gnu${LIBC} + exit 0 ;; + alpha:Linux:*:*) + cat <$dummy.s + .data + \$Lformat: + .byte 37,100,45,37,120,10,0 # "%d-%x\n" + .text + .globl main + .align 4 + .ent main + main: + .frame \$30,16,\$26,0 + ldgp \$29,0(\$27) + .prologue 1 + .long 0x47e03d80 # implver \$0 + lda \$2,-1 + .long 0x47e20c21 # amask \$2,\$1 + lda \$16,\$Lformat + mov \$0,\$17 + not \$1,\$18 + jsr \$26,printf + ldgp \$29,0(\$26) + mov 0,\$16 + jsr \$26,exit + .end main +EOF + LIBC="" + $CC_FOR_BUILD $dummy.s -o $dummy 2>/dev/null + if test "$?" = 0 ; then + case `./$dummy` in + 0-0) UNAME_MACHINE="alpha" ;; + 1-0) UNAME_MACHINE="alphaev5" ;; + 1-1) UNAME_MACHINE="alphaev56" ;; + 1-101) UNAME_MACHINE="alphapca56" ;; + 2-303) UNAME_MACHINE="alphaev6" ;; + 2-307) UNAME_MACHINE="alphaev67" ;; + esac + objdump --private-headers $dummy | \ + grep ld.so.1 > /dev/null + if test "$?" = 0 ; then + LIBC="libc1" + fi + fi + rm -f $dummy.s $dummy + echo ${UNAME_MACHINE}-unknown-linux-gnu${LIBC} + exit 0 ;; + parisc:Linux:*:* | hppa:Linux:*:*) + # Look for CPU level + case `grep '^cpu[^a-z]*:' /proc/cpuinfo 2>/dev/null | cut -d' ' -f2` in + PA7*) echo hppa1.1-unknown-linux-gnu ;; + PA8*) echo hppa2.0-unknown-linux-gnu ;; + *) echo hppa-unknown-linux-gnu ;; + esac + exit 0 ;; + parisc64:Linux:*:* | hppa64:Linux:*:*) + echo hppa64-unknown-linux-gnu + exit 0 ;; + s390:Linux:*:* | s390x:Linux:*:*) + echo ${UNAME_MACHINE}-ibm-linux + exit 0 ;; + sh*:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-gnu + exit 0 ;; + sparc:Linux:*:* | sparc64:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-gnu + exit 0 ;; + x86_64:Linux:*:*) + echo x86_64-unknown-linux-gnu + exit 0 ;; + i*86:Linux:*:*) + # The BFD linker knows what the default object file format is, so + # first see if it will tell us. cd to the root directory to prevent + # problems with other programs or directories called `ld' in the path. + ld_supported_emulations=`cd /; ld --help 2>&1 \ + | sed -ne '/supported emulations:/!d + s/[ ][ ]*/ /g + s/.*supported emulations: *// + s/ .*// + p'` + case "$ld_supported_emulations" in + i*86linux) + echo "${UNAME_MACHINE}-pc-linux-gnuaout" + exit 0 + ;; + elf_i*86) + TENTATIVE="${UNAME_MACHINE}-pc-linux-gnu" + ;; + i*86coff) + echo "${UNAME_MACHINE}-pc-linux-gnucoff" + exit 0 + ;; + esac + # Either a pre-BFD a.out linker (linux-gnuoldld) + # or one that does not give us useful --help. + # GCC wants to distinguish between linux-gnuoldld and linux-gnuaout. + # If ld does not provide *any* "supported emulations:" + # that means it is gnuoldld. + test -z "$ld_supported_emulations" && echo "${UNAME_MACHINE}-pc-linux-gnuoldld" && exit 0 + case "${UNAME_MACHINE}" in + i*86) + VENDOR=pc; + ;; + *) + VENDOR=unknown; + ;; + esac + # Determine whether the default compiler is a.out or elf + cat >$dummy.c < +#ifdef __cplusplus +#include /* for printf() prototype */ + int main (int argc, char *argv[]) { +#else + int main (argc, argv) int argc; char *argv[]; { +#endif +#ifdef __ELF__ +# ifdef __GLIBC__ +# if __GLIBC__ >= 2 + printf ("%s-${VENDOR}-linux-gnu\n", argv[1]); +# else + printf ("%s-${VENDOR}-linux-gnulibc1\n", argv[1]); +# endif +# else + printf ("%s-${VENDOR}-linux-gnulibc1\n", argv[1]); +# endif +#else + printf ("%s-${VENDOR}-linux-gnuaout\n", argv[1]); +#endif + return 0; +} +EOF + $CC_FOR_BUILD $dummy.c -o $dummy 2>/dev/null && ./$dummy "${UNAME_MACHINE}" && rm -f $dummy.c $dummy && exit 0 + rm -f $dummy.c $dummy + test x"${TENTATIVE}" != x && echo "${TENTATIVE}" && exit 0 + ;; +# ptx 4.0 does uname -s correctly, with DYNIX/ptx in there. earlier versions +# are messed up and put the nodename in both sysname and nodename. + i*86:DYNIX/ptx:4*:*) + echo i386-sequent-sysv4 + exit 0 ;; + i*86:UNIX_SV:4.2MP:2.*) + # Unixware is an offshoot of SVR4, but it has its own version + # number series starting with 2... + # I am not positive that other SVR4 systems won't match this, + # I just have to hope. -- rms. + # Use sysv4.2uw... so that sysv4* matches it. + echo ${UNAME_MACHINE}-pc-sysv4.2uw${UNAME_VERSION} + exit 0 ;; + i*86:*:4.*:* | i*86:SYSTEM_V:4.*:*) + UNAME_REL=`echo ${UNAME_RELEASE} | sed 's/\/MP$//'` + if grep Novell /usr/include/link.h >/dev/null 2>/dev/null; then + echo ${UNAME_MACHINE}-univel-sysv${UNAME_REL} + else + echo ${UNAME_MACHINE}-pc-sysv${UNAME_REL} + fi + exit 0 ;; + i*86:*:5:7*) + # Fixed at (any) Pentium or better + UNAME_MACHINE=i586 + if [ ${UNAME_SYSTEM} = "UnixWare" ] ; then + echo ${UNAME_MACHINE}-sco-sysv${UNAME_RELEASE}uw${UNAME_VERSION} + else + echo ${UNAME_MACHINE}-pc-sysv${UNAME_RELEASE} + fi + exit 0 ;; + i*86:*:3.2:*) + if test -f /usr/options/cb.name; then + UNAME_REL=`sed -n 's/.*Version //p' /dev/null >/dev/null ; then + UNAME_REL=`(/bin/uname -X|egrep Release|sed -e 's/.*= //')` + (/bin/uname -X|egrep i80486 >/dev/null) && UNAME_MACHINE=i486 + (/bin/uname -X|egrep '^Machine.*Pentium' >/dev/null) \ + && UNAME_MACHINE=i586 + (/bin/uname -X|egrep '^Machine.*Pent ?II' >/dev/null) \ + && UNAME_MACHINE=i686 + (/bin/uname -X|egrep '^Machine.*Pentium Pro' >/dev/null) \ + && UNAME_MACHINE=i686 + echo ${UNAME_MACHINE}-pc-sco$UNAME_REL + else + echo ${UNAME_MACHINE}-pc-sysv32 + fi + exit 0 ;; + i*86:*DOS:*:*) + echo ${UNAME_MACHINE}-pc-msdosdjgpp + exit 0 ;; + pc:*:*:*) + # Left here for compatibility: + # uname -m prints for DJGPP always 'pc', but it prints nothing about + # the processor, so we play safe by assuming i386. + echo i386-pc-msdosdjgpp + exit 0 ;; + Intel:Mach:3*:*) + echo i386-pc-mach3 + exit 0 ;; + paragon:*:*:*) + echo i860-intel-osf1 + exit 0 ;; + i860:*:4.*:*) # i860-SVR4 + if grep Stardent /usr/include/sys/uadmin.h >/dev/null 2>&1 ; then + echo i860-stardent-sysv${UNAME_RELEASE} # Stardent Vistra i860-SVR4 + else # Add other i860-SVR4 vendors below as they are discovered. + echo i860-unknown-sysv${UNAME_RELEASE} # Unknown i860-SVR4 + fi + exit 0 ;; + mini*:CTIX:SYS*5:*) + # "miniframe" + echo m68010-convergent-sysv + exit 0 ;; + M68*:*:R3V[567]*:*) + test -r /sysV68 && echo 'm68k-motorola-sysv' && exit 0 ;; + 3[34]??:*:4.0:3.0 | 3[34]??A:*:4.0:3.0 | 3[34]??,*:*:4.0:3.0 | 4850:*:4.0:3.0) + OS_REL='' + test -r /etc/.relid \ + && OS_REL=.`sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid` + /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ + && echo i486-ncr-sysv4.3${OS_REL} && exit 0 + /bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \ + && echo i586-ncr-sysv4.3${OS_REL} && exit 0 ;; + 3[34]??:*:4.0:* | 3[34]??,*:*:4.0:*) + /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ + && echo i486-ncr-sysv4 && exit 0 ;; + m68*:LynxOS:2.*:* | m68*:LynxOS:3.0*:*) + echo m68k-unknown-lynxos${UNAME_RELEASE} + exit 0 ;; + mc68030:UNIX_System_V:4.*:*) + echo m68k-atari-sysv4 + exit 0 ;; + i*86:LynxOS:2.*:* | i*86:LynxOS:3.[01]*:* | i*86:LynxOS:4.0*:*) + echo i386-unknown-lynxos${UNAME_RELEASE} + exit 0 ;; + TSUNAMI:LynxOS:2.*:*) + echo sparc-unknown-lynxos${UNAME_RELEASE} + exit 0 ;; + rs6000:LynxOS:2.*:*) + echo rs6000-unknown-lynxos${UNAME_RELEASE} + exit 0 ;; + PowerPC:LynxOS:2.*:* | PowerPC:LynxOS:3.[01]*:* | PowerPC:LynxOS:4.0*:*) + echo powerpc-unknown-lynxos${UNAME_RELEASE} + exit 0 ;; + SM[BE]S:UNIX_SV:*:*) + echo mips-dde-sysv${UNAME_RELEASE} + exit 0 ;; + RM*:ReliantUNIX-*:*:*) + echo mips-sni-sysv4 + exit 0 ;; + RM*:SINIX-*:*:*) + echo mips-sni-sysv4 + exit 0 ;; + *:SINIX-*:*:*) + if uname -p 2>/dev/null >/dev/null ; then + UNAME_MACHINE=`(uname -p) 2>/dev/null` + echo ${UNAME_MACHINE}-sni-sysv4 + else + echo ns32k-sni-sysv + fi + exit 0 ;; + PENTIUM:CPunix:4.0*:*) # Unisys `ClearPath HMP IX 4000' SVR4/MP effort + # says + echo i586-unisys-sysv4 + exit 0 ;; + *:UNIX_System_V:4*:FTX*) + # From Gerald Hewes . + # How about differentiating between stratus architectures? -djm + echo hppa1.1-stratus-sysv4 + exit 0 ;; + *:*:*:FTX*) + # From seanf@swdc.stratus.com. + echo i860-stratus-sysv4 + exit 0 ;; + mc68*:A/UX:*:*) + echo m68k-apple-aux${UNAME_RELEASE} + exit 0 ;; + news*:NEWS-OS:6*:*) + echo mips-sony-newsos6 + exit 0 ;; + R[34]000:*System_V*:*:* | R4000:UNIX_SYSV:*:* | R*000:UNIX_SV:*:*) + if [ -d /usr/nec ]; then + echo mips-nec-sysv${UNAME_RELEASE} + else + echo mips-unknown-sysv${UNAME_RELEASE} + fi + exit 0 ;; + BeBox:BeOS:*:*) # BeOS running on hardware made by Be, PPC only. + echo powerpc-be-beos + exit 0 ;; + BeMac:BeOS:*:*) # BeOS running on Mac or Mac clone, PPC only. + echo powerpc-apple-beos + exit 0 ;; + BePC:BeOS:*:*) # BeOS running on Intel PC compatible. + echo i586-pc-beos + exit 0 ;; + SX-4:SUPER-UX:*:*) + echo sx4-nec-superux${UNAME_RELEASE} + exit 0 ;; + SX-5:SUPER-UX:*:*) + echo sx5-nec-superux${UNAME_RELEASE} + exit 0 ;; + Power*:Rhapsody:*:*) + echo powerpc-apple-rhapsody${UNAME_RELEASE} + exit 0 ;; + *:Rhapsody:*:*) + echo ${UNAME_MACHINE}-apple-rhapsody${UNAME_RELEASE} + exit 0 ;; + *:Darwin:*:*) + echo `uname -p`-apple-darwin${UNAME_RELEASE} + exit 0 ;; + *:procnto*:*:* | *:QNX:[0123456789]*:*) + if test "${UNAME_MACHINE}" = "x86pc"; then + UNAME_MACHINE=pc + fi + echo `uname -p`-${UNAME_MACHINE}-nto-qnx + exit 0 ;; + *:QNX:*:4*) + echo i386-pc-qnx + exit 0 ;; + NSR-[KW]:NONSTOP_KERNEL:*:*) + echo nsr-tandem-nsk${UNAME_RELEASE} + exit 0 ;; + *:NonStop-UX:*:*) + echo mips-compaq-nonstopux + exit 0 ;; + BS2000:POSIX*:*:*) + echo bs2000-siemens-sysv + exit 0 ;; + DS/*:UNIX_System_V:*:*) + echo ${UNAME_MACHINE}-${UNAME_SYSTEM}-${UNAME_RELEASE} + exit 0 ;; + *:Plan9:*:*) + # "uname -m" is not consistent, so use $cputype instead. 386 + # is converted to i386 for consistency with other x86 + # operating systems. + if test "$cputype" = "386"; then + UNAME_MACHINE=i386 + else + UNAME_MACHINE="$cputype" + fi + echo ${UNAME_MACHINE}-unknown-plan9 + exit 0 ;; + i*86:OS/2:*:*) + # If we were able to find `uname', then EMX Unix compatibility + # is probably installed. + echo ${UNAME_MACHINE}-pc-os2-emx + exit 0 ;; + *:TOPS-10:*:*) + echo pdp10-unknown-tops10 + exit 0 ;; + *:TENEX:*:*) + echo pdp10-unknown-tenex + exit 0 ;; + KS10:TOPS-20:*:* | KL10:TOPS-20:*:* | TYPE4:TOPS-20:*:*) + echo pdp10-dec-tops20 + exit 0 ;; + XKL-1:TOPS-20:*:* | TYPE5:TOPS-20:*:*) + echo pdp10-xkl-tops20 + exit 0 ;; + *:TOPS-20:*:*) + echo pdp10-unknown-tops20 + exit 0 ;; + *:ITS:*:*) + echo pdp10-unknown-its + exit 0 ;; +esac + +#echo '(No uname command or uname output not recognized.)' 1>&2 +#echo "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" 1>&2 + +cat >$dummy.c < +# include +#endif +main () +{ +#if defined (sony) +#if defined (MIPSEB) + /* BFD wants "bsd" instead of "newsos". Perhaps BFD should be changed, + I don't know.... */ + printf ("mips-sony-bsd\n"); exit (0); +#else +#include + printf ("m68k-sony-newsos%s\n", +#ifdef NEWSOS4 + "4" +#else + "" +#endif + ); exit (0); +#endif +#endif + +#if defined (__arm) && defined (__acorn) && defined (__unix) + printf ("arm-acorn-riscix"); exit (0); +#endif + +#if defined (hp300) && !defined (hpux) + printf ("m68k-hp-bsd\n"); exit (0); +#endif + +#if defined (NeXT) +#if !defined (__ARCHITECTURE__) +#define __ARCHITECTURE__ "m68k" +#endif + int version; + version=`(hostinfo | sed -n 's/.*NeXT Mach \([0-9]*\).*/\1/p') 2>/dev/null`; + if (version < 4) + printf ("%s-next-nextstep%d\n", __ARCHITECTURE__, version); + else + printf ("%s-next-openstep%d\n", __ARCHITECTURE__, version); + exit (0); +#endif + +#if defined (MULTIMAX) || defined (n16) +#if defined (UMAXV) + printf ("ns32k-encore-sysv\n"); exit (0); +#else +#if defined (CMU) + printf ("ns32k-encore-mach\n"); exit (0); +#else + printf ("ns32k-encore-bsd\n"); exit (0); +#endif +#endif +#endif + +#if defined (__386BSD__) + printf ("i386-pc-bsd\n"); exit (0); +#endif + +#if defined (sequent) +#if defined (i386) + printf ("i386-sequent-dynix\n"); exit (0); +#endif +#if defined (ns32000) + printf ("ns32k-sequent-dynix\n"); exit (0); +#endif +#endif + +#if defined (_SEQUENT_) + struct utsname un; + + uname(&un); + + if (strncmp(un.version, "V2", 2) == 0) { + printf ("i386-sequent-ptx2\n"); exit (0); + } + if (strncmp(un.version, "V1", 2) == 0) { /* XXX is V1 correct? */ + printf ("i386-sequent-ptx1\n"); exit (0); + } + printf ("i386-sequent-ptx\n"); exit (0); + +#endif + +#if defined (vax) +# if !defined (ultrix) +# include +# if defined (BSD) +# if BSD == 43 + printf ("vax-dec-bsd4.3\n"); exit (0); +# else +# if BSD == 199006 + printf ("vax-dec-bsd4.3reno\n"); exit (0); +# else + printf ("vax-dec-bsd\n"); exit (0); +# endif +# endif +# else + printf ("vax-dec-bsd\n"); exit (0); +# endif +# else + printf ("vax-dec-ultrix\n"); exit (0); +# endif +#endif + +#if defined (alliant) && defined (i860) + printf ("i860-alliant-bsd\n"); exit (0); +#endif + + exit (1); +} +EOF + +$CC_FOR_BUILD $dummy.c -o $dummy 2>/dev/null && ./$dummy && rm -f $dummy.c $dummy && exit 0 +rm -f $dummy.c $dummy + +# Apollos put the system type in the environment. + +test -d /usr/apollo && { echo ${ISP}-apollo-${SYSTYPE}; exit 0; } + +# Convex versions that predate uname can use getsysinfo(1) + +if [ -x /usr/convex/getsysinfo ] +then + case `getsysinfo -f cpu_type` in + c1*) + echo c1-convex-bsd + exit 0 ;; + c2*) + if getsysinfo -f scalar_acc + then echo c32-convex-bsd + else echo c2-convex-bsd + fi + exit 0 ;; + c34*) + echo c34-convex-bsd + exit 0 ;; + c38*) + echo c38-convex-bsd + exit 0 ;; + c4*) + echo c4-convex-bsd + exit 0 ;; + esac +fi + +cat >&2 < in order to provide the needed +information to handle your system. + +config.guess timestamp = $timestamp + +uname -m = `(uname -m) 2>/dev/null || echo unknown` +uname -r = `(uname -r) 2>/dev/null || echo unknown` +uname -s = `(uname -s) 2>/dev/null || echo unknown` +uname -v = `(uname -v) 2>/dev/null || echo unknown` + +/usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null` +/bin/uname -X = `(/bin/uname -X) 2>/dev/null` + +hostinfo = `(hostinfo) 2>/dev/null` +/bin/universe = `(/bin/universe) 2>/dev/null` +/usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null` +/bin/arch = `(/bin/arch) 2>/dev/null` +/usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null` +/usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null` + +UNAME_MACHINE = ${UNAME_MACHINE} +UNAME_RELEASE = ${UNAME_RELEASE} +UNAME_SYSTEM = ${UNAME_SYSTEM} +UNAME_VERSION = ${UNAME_VERSION} +EOF + +exit 1 + +# Local variables: +# eval: (add-hook 'write-file-hooks 'time-stamp) +# time-stamp-start: "timestamp='" +# time-stamp-format: "%:y-%02m-%02d" +# time-stamp-end: "'" +# End: diff --git a/src/rtaudio-mod/config/config.sub b/src/rtaudio-mod/config/config.sub new file mode 100755 index 0000000..9a7d59a --- /dev/null +++ b/src/rtaudio-mod/config/config.sub @@ -0,0 +1,1366 @@ +#! /bin/sh +# Configuration validation subroutine script. +# Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001 +# Free Software Foundation, Inc. + +timestamp='2012-11-19' + +# This file is (in principle) common to ALL GNU software. +# The presence of a machine in this file suggests that SOME GNU software +# can handle that machine. It does not imply ALL GNU software can. +# +# This file is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 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, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. + +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + +# Please send patches to . +# +# Configuration subroutine to validate and canonicalize a configuration type. +# Supply the specified configuration type as an argument. +# If it is invalid, we print an error message on stderr and exit with code 1. +# Otherwise, we print the canonical config type on stdout and succeed. + +# This file is supposed to be the same for all GNU packages +# and recognize all the CPU types, system types and aliases +# that are meaningful with *any* GNU software. +# Each package is responsible for reporting which valid configurations +# it does not support. The user should be able to distinguish +# a failure to support a valid configuration from a meaningless +# configuration. + +# The goal of this file is to map all the various variations of a given +# machine specification into a single specification in the form: +# CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM +# or in some cases, the newer four-part form: +# CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM +# It is wrong to echo any other type of specification. + +me=`echo "$0" | sed -e 's,.*/,,'` + +usage="\ +Usage: $0 [OPTION] CPU-MFR-OPSYS + $0 [OPTION] ALIAS + +Canonicalize a configuration name. + +Operation modes: + -h, --help print this help, then exit + -t, --time-stamp print date of last modification, then exit + -v, --version print version number, then exit + +Report bugs and patches to ." + +version="\ +GNU config.sub ($timestamp) + +Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001 +Free Software Foundation, Inc. + +This is free software; see the source for copying conditions. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." + +help=" +Try \`$me --help' for more information." + +# Parse command line +while test $# -gt 0 ; do + case $1 in + --time-stamp | --time* | -t ) + echo "$timestamp" ; exit 0 ;; + --version | -v ) + echo "$version" ; exit 0 ;; + --help | --h* | -h ) + echo "$usage"; exit 0 ;; + -- ) # Stop option processing + shift; break ;; + - ) # Use stdin as input. + break ;; + -* ) + echo "$me: invalid option $1$help" + exit 1 ;; + + *local*) + # First pass through any local machine types. + echo $1 + exit 0;; + + * ) + break ;; + esac +done + +case $# in + 0) echo "$me: missing argument$help" >&2 + exit 1;; + 1) ;; + *) echo "$me: too many arguments$help" >&2 + exit 1;; +esac + +# Separate what the user gave into CPU-COMPANY and OS or KERNEL-OS (if any). +# Here we must recognize all the valid KERNEL-OS combinations. +maybe_os=`echo $1 | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\2/'` +case $maybe_os in + nto-qnx* | linux-gnu* | storm-chaos* | os2-emx*) + os=-$maybe_os + basic_machine=`echo $1 | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\1/'` + ;; + *) + basic_machine=`echo $1 | sed 's/-[^-]*$//'` + if [ $basic_machine != $1 ] + then os=`echo $1 | sed 's/.*-/-/'` + else os=; fi + ;; +esac + +### Let's recognize common machines as not being operating systems so +### that things like config.sub decstation-3100 work. We also +### recognize some manufacturers as not being operating systems, so we +### can provide default operating systems below. +case $os in + -sun*os*) + # Prevent following clause from handling this invalid input. + ;; + -dec* | -mips* | -sequent* | -encore* | -pc532* | -sgi* | -sony* | \ + -att* | -7300* | -3300* | -delta* | -motorola* | -sun[234]* | \ + -unicom* | -ibm* | -next | -hp | -isi* | -apollo | -altos* | \ + -convergent* | -ncr* | -news | -32* | -3600* | -3100* | -hitachi* |\ + -c[123]* | -convex* | -sun | -crds | -omron* | -dg | -ultra | -tti* | \ + -harris | -dolphin | -highlevel | -gould | -cbm | -ns | -masscomp | \ + -apple | -axis) + os= + basic_machine=$1 + ;; + -sim | -cisco | -oki | -wec | -winbond) + os= + basic_machine=$1 + ;; + -scout) + ;; + -wrs) + os=-vxworks + basic_machine=$1 + ;; + -hiux*) + os=-hiuxwe2 + ;; + -sco5) + os=-sco3.2v5 + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -sco4) + os=-sco3.2v4 + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -sco3.2.[4-9]*) + os=`echo $os | sed -e 's/sco3.2./sco3.2v/'` + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -sco3.2v[4-9]*) + # Don't forget version if it is 3.2v4 or newer. + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -sco*) + os=-sco3.2v2 + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -udk*) + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -isc) + os=-isc2.2 + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -clix*) + basic_machine=clipper-intergraph + ;; + -isc*) + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -lynx*) + os=-lynxos + ;; + -ptx*) + basic_machine=`echo $1 | sed -e 's/86-.*/86-sequent/'` + ;; + -windowsnt*) + os=`echo $os | sed -e 's/windowsnt/winnt/'` + ;; + -psos*) + os=-psos + ;; + -mint | -mint[0-9]*) + basic_machine=m68k-atari + os=-mint + ;; +esac + +# Decode aliases for certain CPU-COMPANY combinations. +case $basic_machine in + # Recognize the basic CPU types without company name. + # Some are omitted here because they have special meanings below. + tahoe | i860 | ia64 | m32r | m68k | m68000 | m88k | ns32k | arc \ + | arm | arme[lb] | arm[bl]e | armv[2345] | armv[345][lb] | strongarm | xscale \ + | pyramid | mn10200 | mn10300 | tron | a29k \ + | 580 | i960 | h8300 \ + | x86 | ppcbe | mipsbe | mipsle | shbe | shle \ + | hppa | hppa1.0 | hppa1.1 | hppa2.0 | hppa2.0w | hppa2.0n \ + | hppa64 \ + | alpha | alphaev[4-8] | alphaev56 | alphapca5[67] \ + | alphaev6[78] \ + | we32k | ns16k | clipper | i370 | sh | sh[34] \ + | powerpc | powerpc64 | powerpcle \ + | 1750a | dsp16xx | pdp10 | pdp11 \ + | mips16 | mips64 | mipsel | mips64el \ + | mips64orion | mips64orionel | mipstx39 | mipstx39el \ + | mips64vr4300 | mips64vr4300el | mips64vr4100 | mips64vr4100el \ + | mips64vr5000 | miprs64vr5000el | mcore | s390 | s390x \ + | sparc | sparclet | sparclite | sparc64 | sparcv9 | sparcv9b \ + | v850 | c4x \ + | thumb | d10v | d30v | fr30 | avr | openrisc | tic80 \ + | pj | pjl | h8500) + basic_machine=$basic_machine-unknown + ;; + m6811 | m68hc11 | m6812 | m68hc12) + # Motorola 68HC11/12. + basic_machine=$basic_machine-unknown + os=-none + ;; + m88110 | m680[12346]0 | m683?2 | m68360 | m5200 | z8k | v70 | w65) + ;; + + # We use `pc' rather than `unknown' + # because (1) that's what they normally are, and + # (2) the word "unknown" tends to confuse beginning users. + i*86 | x86_64) + basic_machine=$basic_machine-pc + ;; + # Object if more than one company name word. + *-*-*) + echo Invalid configuration \`$1\': machine \`$basic_machine\' not recognized 1>&2 + exit 1 + ;; + # Recognize the basic CPU types with company name. + # FIXME: clean up the formatting here. + vax-* | tahoe-* | i*86-* | i860-* | ia64-* | m32r-* | m68k-* | m68000-* \ + | m88k-* | sparc-* | ns32k-* | fx80-* | arc-* | c[123]* \ + | arm-* | armbe-* | armle-* | armv*-* | strongarm-* | xscale-* \ + | mips-* | pyramid-* | tron-* | a29k-* | romp-* | rs6000-* \ + | power-* | none-* | 580-* | cray2-* | h8300-* | h8500-* | i960-* \ + | xmp-* | ymp-* \ + | x86-* | ppcbe-* | mipsbe-* | mipsle-* | shbe-* | shle-* \ + | hppa-* | hppa1.0-* | hppa1.1-* | hppa2.0-* | hppa2.0w-* \ + | hppa2.0n-* | hppa64-* \ + | alpha-* | alphaev[4-8]-* | alphaev56-* | alphapca5[67]-* \ + | alphaev6[78]-* \ + | we32k-* | cydra-* | ns16k-* | pn-* | np1-* | xps100-* \ + | clipper-* | orion-* \ + | sparclite-* | pdp10-* | pdp11-* | sh-* | powerpc-* | powerpc64-* | powerpcle-* \ + | sparc64-* | sparcv9-* | sparcv9b-* | sparc86x-* \ + | mips16-* | mips64-* | mipsel-* \ + | mips64el-* | mips64orion-* | mips64orionel-* \ + | mips64vr4100-* | mips64vr4100el-* | mips64vr4300-* | mips64vr4300el-* \ + | mipstx39-* | mipstx39el-* | mcore-* \ + | f30[01]-* | f700-* | s390-* | s390x-* | sv1-* | t3e-* \ + | [cjt]90-* \ + | m88110-* | m680[01234]0-* | m683?2-* | m68360-* | z8k-* | d10v-* \ + | thumb-* | v850-* | d30v-* | tic30-* | tic80-* | c30-* | fr30-* \ + | bs2000-* | tic54x-* | c54x-* | x86_64-* | pj-* | pjl-*) + ;; + # Recognize the various machine names and aliases which stand + # for a CPU type and a company and sometimes even an OS. + 386bsd) + basic_machine=i386-unknown + os=-bsd + ;; + 3b1 | 7300 | 7300-att | att-7300 | pc7300 | safari | unixpc) + basic_machine=m68000-att + ;; + 3b*) + basic_machine=we32k-att + ;; + a29khif) + basic_machine=a29k-amd + os=-udi + ;; + adobe68k) + basic_machine=m68010-adobe + os=-scout + ;; + alliant | fx80) + basic_machine=fx80-alliant + ;; + altos | altos3068) + basic_machine=m68k-altos + ;; + am29k) + basic_machine=a29k-none + os=-bsd + ;; + amdahl) + basic_machine=580-amdahl + os=-sysv + ;; + amiga | amiga-*) + basic_machine=m68k-unknown + ;; + amigaos | amigados) + basic_machine=m68k-unknown + os=-amigaos + ;; + amigaunix | amix) + basic_machine=m68k-unknown + os=-sysv4 + ;; + apollo68) + basic_machine=m68k-apollo + os=-sysv + ;; + apollo68bsd) + basic_machine=m68k-apollo + os=-bsd + ;; + aux) + basic_machine=m68k-apple + os=-aux + ;; + balance) + basic_machine=ns32k-sequent + os=-dynix + ;; + convex-c1) + basic_machine=c1-convex + os=-bsd + ;; + convex-c2) + basic_machine=c2-convex + os=-bsd + ;; + convex-c32) + basic_machine=c32-convex + os=-bsd + ;; + convex-c34) + basic_machine=c34-convex + os=-bsd + ;; + convex-c38) + basic_machine=c38-convex + os=-bsd + ;; + cray | ymp) + basic_machine=ymp-cray + os=-unicos + ;; + cray2) + basic_machine=cray2-cray + os=-unicos + ;; + [cjt]90) + basic_machine=${basic_machine}-cray + os=-unicos + ;; + crds | unos) + basic_machine=m68k-crds + ;; + cris | cris-* | etrax*) + basic_machine=cris-axis + ;; + da30 | da30-*) + basic_machine=m68k-da30 + ;; + decstation | decstation-3100 | pmax | pmax-* | pmin | dec3100 | decstatn) + basic_machine=mips-dec + ;; + delta | 3300 | motorola-3300 | motorola-delta \ + | 3300-motorola | delta-motorola) + basic_machine=m68k-motorola + ;; + delta88) + basic_machine=m88k-motorola + os=-sysv3 + ;; + dpx20 | dpx20-*) + basic_machine=rs6000-bull + os=-bosx + ;; + dpx2* | dpx2*-bull) + basic_machine=m68k-bull + os=-sysv3 + ;; + ebmon29k) + basic_machine=a29k-amd + os=-ebmon + ;; + elxsi) + basic_machine=elxsi-elxsi + os=-bsd + ;; + encore | umax | mmax) + basic_machine=ns32k-encore + ;; + es1800 | OSE68k | ose68k | ose | OSE) + basic_machine=m68k-ericsson + os=-ose + ;; + fx2800) + basic_machine=i860-alliant + ;; + genix) + basic_machine=ns32k-ns + ;; + gmicro) + basic_machine=tron-gmicro + os=-sysv + ;; + go32) + basic_machine=i386-pc + os=-go32 + ;; + h3050r* | hiux*) + basic_machine=hppa1.1-hitachi + os=-hiuxwe2 + ;; + h8300hms) + basic_machine=h8300-hitachi + os=-hms + ;; + h8300xray) + basic_machine=h8300-hitachi + os=-xray + ;; + h8500hms) + basic_machine=h8500-hitachi + os=-hms + ;; + harris) + basic_machine=m88k-harris + os=-sysv3 + ;; + hp300-*) + basic_machine=m68k-hp + ;; + hp300bsd) + basic_machine=m68k-hp + os=-bsd + ;; + hp300hpux) + basic_machine=m68k-hp + os=-hpux + ;; + hp3k9[0-9][0-9] | hp9[0-9][0-9]) + basic_machine=hppa1.0-hp + ;; + hp9k2[0-9][0-9] | hp9k31[0-9]) + basic_machine=m68000-hp + ;; + hp9k3[2-9][0-9]) + basic_machine=m68k-hp + ;; + hp9k6[0-9][0-9] | hp6[0-9][0-9]) + basic_machine=hppa1.0-hp + ;; + hp9k7[0-79][0-9] | hp7[0-79][0-9]) + basic_machine=hppa1.1-hp + ;; + hp9k78[0-9] | hp78[0-9]) + # FIXME: really hppa2.0-hp + basic_machine=hppa1.1-hp + ;; + hp9k8[67]1 | hp8[67]1 | hp9k80[24] | hp80[24] | hp9k8[78]9 | hp8[78]9 | hp9k893 | hp893) + # FIXME: really hppa2.0-hp + basic_machine=hppa1.1-hp + ;; + hp9k8[0-9][13679] | hp8[0-9][13679]) + basic_machine=hppa1.1-hp + ;; + hp9k8[0-9][0-9] | hp8[0-9][0-9]) + basic_machine=hppa1.0-hp + ;; + hppa-next) + os=-nextstep3 + ;; + hppaosf) + basic_machine=hppa1.1-hp + os=-osf + ;; + hppro) + basic_machine=hppa1.1-hp + os=-proelf + ;; + i370-ibm* | ibm*) + basic_machine=i370-ibm + ;; +# I'm not sure what "Sysv32" means. Should this be sysv3.2? + i*86v32) + basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` + os=-sysv32 + ;; + i*86v4*) + basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` + os=-sysv4 + ;; + i*86v) + basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` + os=-sysv + ;; + i*86sol2) + basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` + os=-solaris2 + ;; + i386mach) + basic_machine=i386-mach + os=-mach + ;; + i386-vsta | vsta) + basic_machine=i386-unknown + os=-vsta + ;; + iris | iris4d) + basic_machine=mips-sgi + case $os in + -irix*) + ;; + *) + os=-irix4 + ;; + esac + ;; + isi68 | isi) + basic_machine=m68k-isi + os=-sysv + ;; + m88k-omron*) + basic_machine=m88k-omron + ;; + magnum | m3230) + basic_machine=mips-mips + os=-sysv + ;; + merlin) + basic_machine=ns32k-utek + os=-sysv + ;; + mingw32) + basic_machine=i386-pc + os=-mingw32 + ;; + miniframe) + basic_machine=m68000-convergent + ;; + *mint | -mint[0-9]* | *MiNT | *MiNT[0-9]*) + basic_machine=m68k-atari + os=-mint + ;; + mipsel*-linux*) + basic_machine=mipsel-unknown + os=-linux-gnu + ;; + mips*-linux*) + basic_machine=mips-unknown + os=-linux-gnu + ;; + mips3*-*) + basic_machine=`echo $basic_machine | sed -e 's/mips3/mips64/'` + ;; + mips3*) + basic_machine=`echo $basic_machine | sed -e 's/mips3/mips64/'`-unknown + ;; + mmix*) + basic_machine=mmix-knuth + os=-mmixware + ;; + monitor) + basic_machine=m68k-rom68k + os=-coff + ;; + msdos) + basic_machine=i386-pc + os=-msdos + ;; + mvs) + basic_machine=i370-ibm + os=-mvs + ;; + ncr3000) + basic_machine=i486-ncr + os=-sysv4 + ;; + netbsd386) + basic_machine=i386-unknown + os=-netbsd + ;; + netwinder) + basic_machine=armv4l-rebel + os=-linux + ;; + news | news700 | news800 | news900) + basic_machine=m68k-sony + os=-newsos + ;; + news1000) + basic_machine=m68030-sony + os=-newsos + ;; + news-3600 | risc-news) + basic_machine=mips-sony + os=-newsos + ;; + necv70) + basic_machine=v70-nec + os=-sysv + ;; + next | m*-next ) + basic_machine=m68k-next + case $os in + -nextstep* ) + ;; + -ns2*) + os=-nextstep2 + ;; + *) + os=-nextstep3 + ;; + esac + ;; + nh3000) + basic_machine=m68k-harris + os=-cxux + ;; + nh[45]000) + basic_machine=m88k-harris + os=-cxux + ;; + nindy960) + basic_machine=i960-intel + os=-nindy + ;; + mon960) + basic_machine=i960-intel + os=-mon960 + ;; + nonstopux) + basic_machine=mips-compaq + os=-nonstopux + ;; + np1) + basic_machine=np1-gould + ;; + nsr-tandem) + basic_machine=nsr-tandem + ;; + op50n-* | op60c-*) + basic_machine=hppa1.1-oki + os=-proelf + ;; + OSE68000 | ose68000) + basic_machine=m68000-ericsson + os=-ose + ;; + os68k) + basic_machine=m68k-none + os=-os68k + ;; + pa-hitachi) + basic_machine=hppa1.1-hitachi + os=-hiuxwe2 + ;; + paragon) + basic_machine=i860-intel + os=-osf + ;; + pbd) + basic_machine=sparc-tti + ;; + pbb) + basic_machine=m68k-tti + ;; + pc532 | pc532-*) + basic_machine=ns32k-pc532 + ;; + pentium | p5 | k5 | k6 | nexgen) + basic_machine=i586-pc + ;; + pentiumpro | p6 | 6x86 | athlon) + basic_machine=i686-pc + ;; + pentiumii | pentium2) + basic_machine=i686-pc + ;; + pentium-* | p5-* | k5-* | k6-* | nexgen-*) + basic_machine=i586-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + pentiumpro-* | p6-* | 6x86-* | athlon-*) + basic_machine=i686-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + pentiumii-* | pentium2-*) + basic_machine=i686-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + pn) + basic_machine=pn-gould + ;; + power) basic_machine=power-ibm + ;; + ppc) basic_machine=powerpc-unknown + ;; + ppc-*) basic_machine=powerpc-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + ppc64) basic_machine=powerpc64-unknown + ;; + ppc64-*) basic_machine=powerpc64-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + ppcle | powerpclittle | ppc-le | powerpc-little) + basic_machine=powerpcle-unknown + ;; + ppcle-* | powerpclittle-*) + basic_machine=powerpcle-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + ps2) + basic_machine=i386-ibm + ;; + pw32) + basic_machine=i586-unknown + os=-pw32 + ;; + rom68k) + basic_machine=m68k-rom68k + os=-coff + ;; + rm[46]00) + basic_machine=mips-siemens + ;; + rtpc | rtpc-*) + basic_machine=romp-ibm + ;; + sa29200) + basic_machine=a29k-amd + os=-udi + ;; + sequent) + basic_machine=i386-sequent + ;; + sh) + basic_machine=sh-hitachi + os=-hms + ;; + sparclite-wrs) + basic_machine=sparclite-wrs + os=-vxworks + ;; + sps7) + basic_machine=m68k-bull + os=-sysv2 + ;; + spur) + basic_machine=spur-unknown + ;; + st2000) + basic_machine=m68k-tandem + ;; + stratus) + basic_machine=i860-stratus + os=-sysv4 + ;; + sun2) + basic_machine=m68000-sun + ;; + sun2os3) + basic_machine=m68000-sun + os=-sunos3 + ;; + sun2os4) + basic_machine=m68000-sun + os=-sunos4 + ;; + sun3os3) + basic_machine=m68k-sun + os=-sunos3 + ;; + sun3os4) + basic_machine=m68k-sun + os=-sunos4 + ;; + sun4os3) + basic_machine=sparc-sun + os=-sunos3 + ;; + sun4os4) + basic_machine=sparc-sun + os=-sunos4 + ;; + sun4sol2) + basic_machine=sparc-sun + os=-solaris2 + ;; + sun3 | sun3-*) + basic_machine=m68k-sun + ;; + sun4) + basic_machine=sparc-sun + ;; + sun386 | sun386i | roadrunner) + basic_machine=i386-sun + ;; + sv1) + basic_machine=sv1-cray + os=-unicos + ;; + symmetry) + basic_machine=i386-sequent + os=-dynix + ;; + t3e) + basic_machine=t3e-cray + os=-unicos + ;; + tic54x | c54x*) + basic_machine=tic54x-unknown + os=-coff + ;; + tx39) + basic_machine=mipstx39-unknown + ;; + tx39el) + basic_machine=mipstx39el-unknown + ;; + tower | tower-32) + basic_machine=m68k-ncr + ;; + udi29k) + basic_machine=a29k-amd + os=-udi + ;; + ultra3) + basic_machine=a29k-nyu + os=-sym1 + ;; + v810 | necv810) + basic_machine=v810-nec + os=-none + ;; + vaxv) + basic_machine=vax-dec + os=-sysv + ;; + vms) + basic_machine=vax-dec + os=-vms + ;; + vpp*|vx|vx-*) + basic_machine=f301-fujitsu + ;; + vxworks960) + basic_machine=i960-wrs + os=-vxworks + ;; + vxworks68) + basic_machine=m68k-wrs + os=-vxworks + ;; + vxworks29k) + basic_machine=a29k-wrs + os=-vxworks + ;; + w65*) + basic_machine=w65-wdc + os=-none + ;; + w89k-*) + basic_machine=hppa1.1-winbond + os=-proelf + ;; + xmp) + basic_machine=xmp-cray + os=-unicos + ;; + xps | xps100) + basic_machine=xps100-honeywell + ;; + z8k-*-coff) + basic_machine=z8k-unknown + os=-sim + ;; + none) + basic_machine=none-none + os=-none + ;; + +# Here we handle the default manufacturer of certain CPU types. It is in +# some cases the only manufacturer, in others, it is the most popular. + w89k) + basic_machine=hppa1.1-winbond + ;; + op50n) + basic_machine=hppa1.1-oki + ;; + op60c) + basic_machine=hppa1.1-oki + ;; + mips) + if [ x$os = x-linux-gnu ]; then + basic_machine=mips-unknown + else + basic_machine=mips-mips + fi + ;; + romp) + basic_machine=romp-ibm + ;; + rs6000) + basic_machine=rs6000-ibm + ;; + vax) + basic_machine=vax-dec + ;; + pdp10) + # there are many clones, so DEC is not a safe bet + basic_machine=pdp10-unknown + ;; + pdp11) + basic_machine=pdp11-dec + ;; + we32k) + basic_machine=we32k-att + ;; + sh3 | sh4) + basic_machine=sh-unknown + ;; + sparc | sparcv9 | sparcv9b) + basic_machine=sparc-sun + ;; + cydra) + basic_machine=cydra-cydrome + ;; + orion) + basic_machine=orion-highlevel + ;; + orion105) + basic_machine=clipper-highlevel + ;; + mac | mpw | mac-mpw) + basic_machine=m68k-apple + ;; + pmac | pmac-mpw) + basic_machine=powerpc-apple + ;; + c4x*) + basic_machine=c4x-none + os=-coff + ;; + *-unknown) + # Make sure to match an already-canonicalized machine name. + ;; + *) + echo Invalid configuration \`$1\': machine \`$basic_machine\' not recognized 1>&2 + exit 1 + ;; +esac + +# Here we canonicalize certain aliases for manufacturers. +case $basic_machine in + *-digital*) + basic_machine=`echo $basic_machine | sed 's/digital.*/dec/'` + ;; + *-commodore*) + basic_machine=`echo $basic_machine | sed 's/commodore.*/cbm/'` + ;; + *) + ;; +esac + +# Decode manufacturer-specific aliases for certain operating systems. + +if [ x"$os" != x"" ] +then +case $os in + # First match some system type aliases + # that might get confused with valid system types. + # -solaris* is a basic system type, with this one exception. + -solaris1 | -solaris1.*) + os=`echo $os | sed -e 's|solaris1|sunos4|'` + ;; + -solaris) + os=-solaris2 + ;; + -svr4*) + os=-sysv4 + ;; + -unixware*) + os=-sysv4.2uw + ;; + -gnu/linux*) + os=`echo $os | sed -e 's|gnu/linux|linux-gnu|'` + ;; + # First accept the basic system types. + # The portable systems comes first. + # Each alternative MUST END IN A *, to match a version number. + # -sysv* is not here because it comes later, after sysvr4. + -gnu* | -bsd* | -mach* | -minix* | -genix* | -ultrix* | -irix* \ + | -*vms* | -sco* | -esix* | -isc* | -aix* | -sunos | -sunos[34]*\ + | -hpux* | -unos* | -osf* | -luna* | -dgux* | -solaris* | -sym* \ + | -amigaos* | -amigados* | -msdos* | -newsos* | -unicos* | -aof* \ + | -aos* \ + | -nindy* | -vxsim* | -vxworks* | -ebmon* | -hms* | -mvs* \ + | -clix* | -riscos* | -uniplus* | -iris* | -rtu* | -xenix* \ + | -hiux* | -386bsd* | -netbsd* | -openbsd* | -freebsd* | -riscix* \ + | -lynxos* | -bosx* | -nextstep* | -cxux* | -aout* | -elf* | -oabi* \ + | -ptx* | -coff* | -ecoff* | -winnt* | -domain* | -vsta* \ + | -udi* | -eabi* | -lites* | -ieee* | -go32* | -aux* \ + | -cygwin* | -pe* | -psos* | -moss* | -proelf* | -rtems* \ + | -mingw32* | -linux-gnu* | -uxpv* | -beos* | -mpeix* | -udk* \ + | -interix* | -uwin* | -rhapsody* | -darwin* | -opened* \ + | -openstep* | -oskit* | -conix* | -pw32* | -nonstopux* \ + | -storm-chaos* | -tops10* | -tenex* | -tops20* | -its* | -os2*) + # Remember, each alternative MUST END IN *, to match a version number. + ;; + -qnx*) + case $basic_machine in + x86-* | i*86-*) + ;; + *) + os=-nto$os + ;; + esac + ;; + -nto*) + os=-nto-qnx + ;; + -sim | -es1800* | -hms* | -xray | -os68k* | -none* | -v88r* \ + | -windows* | -osx | -abug | -netware* | -os9* | -beos* \ + | -macos* | -mpw* | -magic* | -mmixware* | -mon960* | -lnews*) + ;; + -mac*) + os=`echo $os | sed -e 's|mac|macos|'` + ;; + -linux*) + os=`echo $os | sed -e 's|linux|linux-gnu|'` + ;; + -sunos5*) + os=`echo $os | sed -e 's|sunos5|solaris2|'` + ;; + -sunos6*) + os=`echo $os | sed -e 's|sunos6|solaris3|'` + ;; + -opened*) + os=-openedition + ;; + -wince*) + os=-wince + ;; + -osfrose*) + os=-osfrose + ;; + -osf*) + os=-osf + ;; + -utek*) + os=-bsd + ;; + -dynix*) + os=-bsd + ;; + -acis*) + os=-aos + ;; + -386bsd) + os=-bsd + ;; + -ctix* | -uts*) + os=-sysv + ;; + -ns2 ) + os=-nextstep2 + ;; + -nsk*) + os=-nsk + ;; + # Preserve the version number of sinix5. + -sinix5.*) + os=`echo $os | sed -e 's|sinix|sysv|'` + ;; + -sinix*) + os=-sysv4 + ;; + -triton*) + os=-sysv3 + ;; + -oss*) + os=-sysv3 + ;; + -svr4) + os=-sysv4 + ;; + -svr3) + os=-sysv3 + ;; + -sysvr4) + os=-sysv4 + ;; + # This must come after -sysvr4. + -sysv*) + ;; + -ose*) + os=-ose + ;; + -es1800*) + os=-ose + ;; + -xenix) + os=-xenix + ;; + -*mint | -mint[0-9]* | -*MiNT | -MiNT[0-9]*) + os=-mint + ;; + -none) + ;; + *) + # Get rid of the `-' at the beginning of $os. + os=`echo $os | sed 's/[^-]*-//'` + echo Invalid configuration \`$1\': system \`$os\' not recognized 1>&2 + exit 1 + ;; +esac +else + +# Here we handle the default operating systems that come with various machines. +# The value should be what the vendor currently ships out the door with their +# machine or put another way, the most popular os provided with the machine. + +# Note that if you're going to try to match "-MANUFACTURER" here (say, +# "-sun"), then you have to tell the case statement up towards the top +# that MANUFACTURER isn't an operating system. Otherwise, code above +# will signal an error saying that MANUFACTURER isn't an operating +# system, and we'll never get to this point. + +case $basic_machine in + *-acorn) + os=-riscix1.2 + ;; + arm*-rebel) + os=-linux + ;; + arm*-semi) + os=-aout + ;; + pdp10-*) + os=-tops20 + ;; + pdp11-*) + os=-none + ;; + *-dec | vax-*) + os=-ultrix4.2 + ;; + m68*-apollo) + os=-domain + ;; + i386-sun) + os=-sunos4.0.2 + ;; + m68000-sun) + os=-sunos3 + # This also exists in the configure program, but was not the + # default. + # os=-sunos4 + ;; + m68*-cisco) + os=-aout + ;; + mips*-cisco) + os=-elf + ;; + mips*-*) + os=-elf + ;; + *-tti) # must be before sparc entry or we get the wrong os. + os=-sysv3 + ;; + sparc-* | *-sun) + os=-sunos4.1.1 + ;; + *-be) + os=-beos + ;; + *-ibm) + os=-aix + ;; + *-wec) + os=-proelf + ;; + *-winbond) + os=-proelf + ;; + *-oki) + os=-proelf + ;; + *-hp) + os=-hpux + ;; + *-hitachi) + os=-hiux + ;; + i860-* | *-att | *-ncr | *-altos | *-motorola | *-convergent) + os=-sysv + ;; + *-cbm) + os=-amigaos + ;; + *-dg) + os=-dgux + ;; + *-dolphin) + os=-sysv3 + ;; + m68k-ccur) + os=-rtu + ;; + m88k-omron*) + os=-luna + ;; + *-next ) + os=-nextstep + ;; + *-sequent) + os=-ptx + ;; + *-crds) + os=-unos + ;; + *-ns) + os=-genix + ;; + i370-*) + os=-mvs + ;; + *-next) + os=-nextstep3 + ;; + *-gould) + os=-sysv + ;; + *-highlevel) + os=-bsd + ;; + *-encore) + os=-bsd + ;; + *-sgi) + os=-irix + ;; + *-siemens) + os=-sysv4 + ;; + *-masscomp) + os=-rtu + ;; + f30[01]-fujitsu | f700-fujitsu) + os=-uxpv + ;; + *-rom68k) + os=-coff + ;; + *-*bug) + os=-coff + ;; + *-apple) + os=-macos + ;; + *-atari*) + os=-mint + ;; + *) + os=-none + ;; +esac +fi + +# Here we handle the case where we know the os, and the CPU type, but not the +# manufacturer. We pick the logical manufacturer. +vendor=unknown +case $basic_machine in + *-unknown) + case $os in + -riscix*) + vendor=acorn + ;; + -sunos*) + vendor=sun + ;; + -aix*) + vendor=ibm + ;; + -beos*) + vendor=be + ;; + -hpux*) + vendor=hp + ;; + -mpeix*) + vendor=hp + ;; + -hiux*) + vendor=hitachi + ;; + -unos*) + vendor=crds + ;; + -dgux*) + vendor=dg + ;; + -luna*) + vendor=omron + ;; + -genix*) + vendor=ns + ;; + -mvs* | -opened*) + vendor=ibm + ;; + -ptx*) + vendor=sequent + ;; + -vxsim* | -vxworks*) + vendor=wrs + ;; + -aux*) + vendor=apple + ;; + -hms*) + vendor=hitachi + ;; + -mpw* | -macos*) + vendor=apple + ;; + -*mint | -mint[0-9]* | -*MiNT | -MiNT[0-9]*) + vendor=atari + ;; + esac + basic_machine=`echo $basic_machine | sed "s/unknown/$vendor/"` + ;; +esac + +echo $basic_machine$os +exit 0 + +# Local variables: +# eval: (add-hook 'write-file-hooks 'time-stamp) +# time-stamp-start: "timestamp='" +# time-stamp-format: "%:y-%02m-%02d" +# time-stamp-end: "'" +# End: diff --git a/src/rtaudio-mod/config/install.sh b/src/rtaudio-mod/config/install.sh new file mode 100755 index 0000000..e69de29 diff --git a/src/rtaudio-mod/configure b/src/rtaudio-mod/configure new file mode 100755 index 0000000..9f3d3f8 --- /dev/null +++ b/src/rtaudio-mod/configure @@ -0,0 +1,5888 @@ +#! /bin/sh +# Guess values for system-dependent variables and create Makefiles. +# Generated by GNU Autoconf 2.69 for RtAudio 4.1. +# +# Report bugs to . +# +# +# Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc. +# +# +# This configure script is free software; the Free Software Foundation +# gives unlimited permission to copy, distribute and modify it. +## -------------------- ## +## M4sh Initialization. ## +## -------------------- ## + +# Be more Bourne compatible +DUALCASE=1; export DUALCASE # for MKS sh +if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then : + emulate sh + NULLCMD=: + # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which + # is contrary to our usage. Disable this feature. + alias -g '${1+"$@"}'='"$@"' + setopt NO_GLOB_SUBST +else + case `(set -o) 2>/dev/null` in #( + *posix*) : + set -o posix ;; #( + *) : + ;; +esac +fi + + +as_nl=' +' +export as_nl +# Printing a long string crashes Solaris 7 /usr/bin/printf. +as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' +as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo +as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo +# Prefer a ksh shell builtin over an external printf program on Solaris, +# but without wasting forks for bash or zsh. +if test -z "$BASH_VERSION$ZSH_VERSION" \ + && (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then + as_echo='print -r --' + as_echo_n='print -rn --' +elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then + as_echo='printf %s\n' + as_echo_n='printf %s' +else + if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then + as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"' + as_echo_n='/usr/ucb/echo -n' + else + as_echo_body='eval expr "X$1" : "X\\(.*\\)"' + as_echo_n_body='eval + arg=$1; + case $arg in #( + *"$as_nl"*) + expr "X$arg" : "X\\(.*\\)$as_nl"; + arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;; + esac; + expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl" + ' + export as_echo_n_body + as_echo_n='sh -c $as_echo_n_body as_echo' + fi + export as_echo_body + as_echo='sh -c $as_echo_body as_echo' +fi + +# The user is always right. +if test "${PATH_SEPARATOR+set}" != set; then + PATH_SEPARATOR=: + (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { + (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || + PATH_SEPARATOR=';' + } +fi + + +# IFS +# We need space, tab and new line, in precisely that order. Quoting is +# there to prevent editors from complaining about space-tab. +# (If _AS_PATH_WALK were called with IFS unset, it would disable word +# splitting by setting IFS to empty value.) +IFS=" "" $as_nl" + +# Find who we are. Look in the path if we contain no directory separator. +as_myself= +case $0 in #(( + *[\\/]* ) as_myself=$0 ;; + *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break + done +IFS=$as_save_IFS + + ;; +esac +# We did not find ourselves, most probably we were run as `sh COMMAND' +# in which case we are not to be found in the path. +if test "x$as_myself" = x; then + as_myself=$0 +fi +if test ! -f "$as_myself"; then + $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 + exit 1 +fi + +# Unset variables that we do not need and which cause bugs (e.g. in +# pre-3.0 UWIN ksh). But do not cause bugs in bash 2.01; the "|| exit 1" +# suppresses any "Segmentation fault" message there. '((' could +# trigger a bug in pdksh 5.2.14. +for as_var in BASH_ENV ENV MAIL MAILPATH +do eval test x\${$as_var+set} = xset \ + && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : +done +PS1='$ ' +PS2='> ' +PS4='+ ' + +# NLS nuisances. +LC_ALL=C +export LC_ALL +LANGUAGE=C +export LANGUAGE + +# CDPATH. +(unset CDPATH) >/dev/null 2>&1 && unset CDPATH + +# Use a proper internal environment variable to ensure we don't fall + # into an infinite loop, continuously re-executing ourselves. + if test x"${_as_can_reexec}" != xno && test "x$CONFIG_SHELL" != x; then + _as_can_reexec=no; export _as_can_reexec; + # We cannot yet assume a decent shell, so we have to provide a +# neutralization value for shells without unset; and this also +# works around shells that cannot unset nonexistent variables. +# Preserve -v and -x to the replacement shell. +BASH_ENV=/dev/null +ENV=/dev/null +(unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV +case $- in # (((( + *v*x* | *x*v* ) as_opts=-vx ;; + *v* ) as_opts=-v ;; + *x* ) as_opts=-x ;; + * ) as_opts= ;; +esac +exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"} +# Admittedly, this is quite paranoid, since all the known shells bail +# out after a failed `exec'. +$as_echo "$0: could not re-execute with $CONFIG_SHELL" >&2 +as_fn_exit 255 + fi + # We don't want this to propagate to other subprocesses. + { _as_can_reexec=; unset _as_can_reexec;} +if test "x$CONFIG_SHELL" = x; then + as_bourne_compatible="if test -n \"\${ZSH_VERSION+set}\" && (emulate sh) >/dev/null 2>&1; then : + emulate sh + NULLCMD=: + # Pre-4.2 versions of Zsh do word splitting on \${1+\"\$@\"}, which + # is contrary to our usage. Disable this feature. + alias -g '\${1+\"\$@\"}'='\"\$@\"' + setopt NO_GLOB_SUBST +else + case \`(set -o) 2>/dev/null\` in #( + *posix*) : + set -o posix ;; #( + *) : + ;; +esac +fi +" + as_required="as_fn_return () { (exit \$1); } +as_fn_success () { as_fn_return 0; } +as_fn_failure () { as_fn_return 1; } +as_fn_ret_success () { return 0; } +as_fn_ret_failure () { return 1; } + +exitcode=0 +as_fn_success || { exitcode=1; echo as_fn_success failed.; } +as_fn_failure && { exitcode=1; echo as_fn_failure succeeded.; } +as_fn_ret_success || { exitcode=1; echo as_fn_ret_success failed.; } +as_fn_ret_failure && { exitcode=1; echo as_fn_ret_failure succeeded.; } +if ( set x; as_fn_ret_success y && test x = \"\$1\" ); then : + +else + exitcode=1; echo positional parameters were not saved. +fi +test x\$exitcode = x0 || exit 1 +test -x / || exit 1" + as_suggested=" as_lineno_1=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_1a=\$LINENO + as_lineno_2=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_2a=\$LINENO + eval 'test \"x\$as_lineno_1'\$as_run'\" != \"x\$as_lineno_2'\$as_run'\" && + test \"x\`expr \$as_lineno_1'\$as_run' + 1\`\" = \"x\$as_lineno_2'\$as_run'\"' || exit 1 +test \$(( 1 + 1 )) = 2 || exit 1" + if (eval "$as_required") 2>/dev/null; then : + as_have_required=yes +else + as_have_required=no +fi + if test x$as_have_required = xyes && (eval "$as_suggested") 2>/dev/null; then : + +else + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +as_found=false +for as_dir in /bin$PATH_SEPARATOR/usr/bin$PATH_SEPARATOR$PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + as_found=: + case $as_dir in #( + /*) + for as_base in sh bash ksh sh5; do + # Try only shells that exist, to save several forks. + as_shell=$as_dir/$as_base + if { test -f "$as_shell" || test -f "$as_shell.exe"; } && + { $as_echo "$as_bourne_compatible""$as_required" | as_run=a "$as_shell"; } 2>/dev/null; then : + CONFIG_SHELL=$as_shell as_have_required=yes + if { $as_echo "$as_bourne_compatible""$as_suggested" | as_run=a "$as_shell"; } 2>/dev/null; then : + break 2 +fi +fi + done;; + esac + as_found=false +done +$as_found || { if { test -f "$SHELL" || test -f "$SHELL.exe"; } && + { $as_echo "$as_bourne_compatible""$as_required" | as_run=a "$SHELL"; } 2>/dev/null; then : + CONFIG_SHELL=$SHELL as_have_required=yes +fi; } +IFS=$as_save_IFS + + + if test "x$CONFIG_SHELL" != x; then : + export CONFIG_SHELL + # We cannot yet assume a decent shell, so we have to provide a +# neutralization value for shells without unset; and this also +# works around shells that cannot unset nonexistent variables. +# Preserve -v and -x to the replacement shell. +BASH_ENV=/dev/null +ENV=/dev/null +(unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV +case $- in # (((( + *v*x* | *x*v* ) as_opts=-vx ;; + *v* ) as_opts=-v ;; + *x* ) as_opts=-x ;; + * ) as_opts= ;; +esac +exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"} +# Admittedly, this is quite paranoid, since all the known shells bail +# out after a failed `exec'. +$as_echo "$0: could not re-execute with $CONFIG_SHELL" >&2 +exit 255 +fi + + if test x$as_have_required = xno; then : + $as_echo "$0: This script requires a shell more modern than all" + $as_echo "$0: the shells that I found on your system." + if test x${ZSH_VERSION+set} = xset ; then + $as_echo "$0: In particular, zsh $ZSH_VERSION has bugs and should" + $as_echo "$0: be upgraded to zsh 4.3.4 or later." + else + $as_echo "$0: Please tell bug-autoconf@gnu.org and +$0: gary@music.mcgill.ca about your system, including any +$0: error possibly output before this message. Then install +$0: a modern shell, or manually run the script under such a +$0: shell if you do have one." + fi + exit 1 +fi +fi +fi +SHELL=${CONFIG_SHELL-/bin/sh} +export SHELL +# Unset more variables known to interfere with behavior of common tools. +CLICOLOR_FORCE= GREP_OPTIONS= +unset CLICOLOR_FORCE GREP_OPTIONS + +## --------------------- ## +## M4sh Shell Functions. ## +## --------------------- ## +# as_fn_unset VAR +# --------------- +# Portably unset VAR. +as_fn_unset () +{ + { eval $1=; unset $1;} +} +as_unset=as_fn_unset + +# as_fn_set_status STATUS +# ----------------------- +# Set $? to STATUS, without forking. +as_fn_set_status () +{ + return $1 +} # as_fn_set_status + +# as_fn_exit STATUS +# ----------------- +# Exit the shell with STATUS, even in a "trap 0" or "set -e" context. +as_fn_exit () +{ + set +e + as_fn_set_status $1 + exit $1 +} # as_fn_exit + +# as_fn_mkdir_p +# ------------- +# Create "$as_dir" as a directory, including parents if necessary. +as_fn_mkdir_p () +{ + + case $as_dir in #( + -*) as_dir=./$as_dir;; + esac + test -d "$as_dir" || eval $as_mkdir_p || { + as_dirs= + while :; do + case $as_dir in #( + *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( + *) as_qdir=$as_dir;; + esac + as_dirs="'$as_qdir' $as_dirs" + as_dir=`$as_dirname -- "$as_dir" || +$as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$as_dir" : 'X\(//\)[^/]' \| \ + X"$as_dir" : 'X\(//\)$' \| \ + X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X"$as_dir" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + test -d "$as_dir" && break + done + test -z "$as_dirs" || eval "mkdir $as_dirs" + } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir" + + +} # as_fn_mkdir_p + +# as_fn_executable_p FILE +# ----------------------- +# Test if FILE is an executable regular file. +as_fn_executable_p () +{ + test -f "$1" && test -x "$1" +} # as_fn_executable_p +# as_fn_append VAR VALUE +# ---------------------- +# Append the text in VALUE to the end of the definition contained in VAR. Take +# advantage of any shell optimizations that allow amortized linear growth over +# repeated appends, instead of the typical quadratic growth present in naive +# implementations. +if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then : + eval 'as_fn_append () + { + eval $1+=\$2 + }' +else + as_fn_append () + { + eval $1=\$$1\$2 + } +fi # as_fn_append + +# as_fn_arith ARG... +# ------------------ +# Perform arithmetic evaluation on the ARGs, and store the result in the +# global $as_val. Take advantage of shells that can avoid forks. The arguments +# must be portable across $(()) and expr. +if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then : + eval 'as_fn_arith () + { + as_val=$(( $* )) + }' +else + as_fn_arith () + { + as_val=`expr "$@" || test $? -eq 1` + } +fi # as_fn_arith + + +# as_fn_error STATUS ERROR [LINENO LOG_FD] +# ---------------------------------------- +# Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are +# provided, also output the error to LOG_FD, referencing LINENO. Then exit the +# script with STATUS, using 1 if that was 0. +as_fn_error () +{ + as_status=$1; test $as_status -eq 0 && as_status=1 + if test "$4"; then + as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + $as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 + fi + $as_echo "$as_me: error: $2" >&2 + as_fn_exit $as_status +} # as_fn_error + +if expr a : '\(a\)' >/dev/null 2>&1 && + test "X`expr 00001 : '.*\(...\)'`" = X001; then + as_expr=expr +else + as_expr=false +fi + +if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then + as_basename=basename +else + as_basename=false +fi + +if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then + as_dirname=dirname +else + as_dirname=false +fi + +as_me=`$as_basename -- "$0" || +$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ + X"$0" : 'X\(//\)$' \| \ + X"$0" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X/"$0" | + sed '/^.*\/\([^/][^/]*\)\/*$/{ + s//\1/ + q + } + /^X\/\(\/\/\)$/{ + s//\1/ + q + } + /^X\/\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + +# Avoid depending upon Character Ranges. +as_cr_letters='abcdefghijklmnopqrstuvwxyz' +as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' +as_cr_Letters=$as_cr_letters$as_cr_LETTERS +as_cr_digits='0123456789' +as_cr_alnum=$as_cr_Letters$as_cr_digits + + + as_lineno_1=$LINENO as_lineno_1a=$LINENO + as_lineno_2=$LINENO as_lineno_2a=$LINENO + eval 'test "x$as_lineno_1'$as_run'" != "x$as_lineno_2'$as_run'" && + test "x`expr $as_lineno_1'$as_run' + 1`" = "x$as_lineno_2'$as_run'"' || { + # Blame Lee E. McMahon (1931-1989) for sed's syntax. :-) + sed -n ' + p + /[$]LINENO/= + ' <$as_myself | + sed ' + s/[$]LINENO.*/&-/ + t lineno + b + :lineno + N + :loop + s/[$]LINENO\([^'$as_cr_alnum'_].*\n\)\(.*\)/\2\1\2/ + t loop + s/-\n.*// + ' >$as_me.lineno && + chmod +x "$as_me.lineno" || + { $as_echo "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2; as_fn_exit 1; } + + # If we had to re-execute with $CONFIG_SHELL, we're ensured to have + # already done that, so ensure we don't try to do so again and fall + # in an infinite loop. This has already happened in practice. + _as_can_reexec=no; export _as_can_reexec + # Don't try to exec as it changes $[0], causing all sort of problems + # (the dirname of $[0] is not the place where we might find the + # original and so on. Autoconf is especially sensitive to this). + . "./$as_me.lineno" + # Exit status is that of the last command. + exit +} + +ECHO_C= ECHO_N= ECHO_T= +case `echo -n x` in #((((( +-n*) + case `echo 'xy\c'` in + *c*) ECHO_T=' ';; # ECHO_T is single tab character. + xy) ECHO_C='\c';; + *) echo `echo ksh88 bug on AIX 6.1` > /dev/null + ECHO_T=' ';; + esac;; +*) + ECHO_N='-n';; +esac + +rm -f conf$$ conf$$.exe conf$$.file +if test -d conf$$.dir; then + rm -f conf$$.dir/conf$$.file +else + rm -f conf$$.dir + mkdir conf$$.dir 2>/dev/null +fi +if (echo >conf$$.file) 2>/dev/null; then + if ln -s conf$$.file conf$$ 2>/dev/null; then + as_ln_s='ln -s' + # ... but there are two gotchas: + # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail. + # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable. + # In both cases, we have to default to `cp -pR'. + ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || + as_ln_s='cp -pR' + elif ln conf$$.file conf$$ 2>/dev/null; then + as_ln_s=ln + else + as_ln_s='cp -pR' + fi +else + as_ln_s='cp -pR' +fi +rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file +rmdir conf$$.dir 2>/dev/null + +if mkdir -p . 2>/dev/null; then + as_mkdir_p='mkdir -p "$as_dir"' +else + test -d ./-p && rmdir ./-p + as_mkdir_p=false +fi + +as_test_x='test -x' +as_executable_p=as_fn_executable_p + +# Sed expression to map a string onto a valid CPP name. +as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'" + +# Sed expression to map a string onto a valid variable name. +as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'" + + +test -n "$DJDIR" || exec 7<&0 &1 + +# Name of the host. +# hostname on some systems (SVR3.2, old GNU/Linux) returns a bogus exit status, +# so uname gets run too. +ac_hostname=`(hostname || uname -n) 2>/dev/null | sed 1q` + +# +# Initializations. +# +ac_default_prefix=/usr/local +ac_clean_files= +ac_config_libobj_dir=. +LIBOBJS= +cross_compiling=no +subdirs= +MFLAGS= +MAKEFLAGS= + +# Identity of this package. +PACKAGE_NAME='RtAudio' +PACKAGE_TARNAME='rtaudio' +PACKAGE_VERSION='4.1' +PACKAGE_STRING='RtAudio 4.1' +PACKAGE_BUGREPORT='gary@music.mcgill.ca' +PACKAGE_URL='' + +ac_unique_file="RtAudio.cpp" +# Factoring default headers for most tests. +ac_includes_default="\ +#include +#ifdef HAVE_SYS_TYPES_H +# include +#endif +#ifdef HAVE_SYS_STAT_H +# include +#endif +#ifdef STDC_HEADERS +# include +# include +#else +# ifdef HAVE_STDLIB_H +# include +# endif +#endif +#ifdef HAVE_STRING_H +# if !defined STDC_HEADERS && defined HAVE_MEMORY_H +# include +# endif +# include +#endif +#ifdef HAVE_STRINGS_H +# include +#endif +#ifdef HAVE_INTTYPES_H +# include +#endif +#ifdef HAVE_STDINT_H +# include +#endif +#ifdef HAVE_UNISTD_H +# include +#endif" + +ac_subst_vars='LTLIBOBJS +LIBOBJS +objects +PULSE_LIBS +PULSE_CFLAGS +req +api +libflags +sharedname +sharedlib +host_os +host_vendor +host_cpu +host +build_os +build_vendor +build_cpu +build +object_path +cxxflag +cppflag +EGREP +GREP +CPP +ac_ct_CC +CFLAGS +CC +AR +RANLIB +OBJEXT +EXEEXT +ac_ct_CXX +CPPFLAGS +LDFLAGS +CXXFLAGS +CXX +PKG_CONFIG_LIBDIR +PKG_CONFIG_PATH +PKG_CONFIG +GXX +target_alias +host_alias +build_alias +LIBS +ECHO_T +ECHO_N +ECHO_C +DEFS +mandir +localedir +libdir +psdir +pdfdir +dvidir +htmldir +infodir +docdir +oldincludedir +includedir +localstatedir +sharedstatedir +sysconfdir +datadir +datarootdir +libexecdir +sbindir +bindir +program_transform_name +prefix +exec_prefix +PACKAGE_URL +PACKAGE_BUGREPORT +PACKAGE_STRING +PACKAGE_VERSION +PACKAGE_TARNAME +PACKAGE_NAME +PATH_SEPARATOR +SHELL' +ac_subst_files='' +ac_user_opts=' +enable_option_checking +enable_debug +with_jack +with_alsa +with_pulse +with_oss +with_core +with_asio +with_ds +with_wasapi +' + ac_precious_vars='build_alias +host_alias +target_alias +PKG_CONFIG +PKG_CONFIG_PATH +PKG_CONFIG_LIBDIR +CXX +CXXFLAGS +LDFLAGS +LIBS +CPPFLAGS +CCC +CC +CFLAGS +CPP +PULSE_CFLAGS +PULSE_LIBS' + + +# Initialize some variables set by options. +ac_init_help= +ac_init_version=false +ac_unrecognized_opts= +ac_unrecognized_sep= +# The variables have the same names as the options, with +# dashes changed to underlines. +cache_file=/dev/null +exec_prefix=NONE +no_create= +no_recursion= +prefix=NONE +program_prefix=NONE +program_suffix=NONE +program_transform_name=s,x,x, +silent= +site= +srcdir= +verbose= +x_includes=NONE +x_libraries=NONE + +# Installation directory options. +# These are left unexpanded so users can "make install exec_prefix=/foo" +# and all the variables that are supposed to be based on exec_prefix +# by default will actually change. +# Use braces instead of parens because sh, perl, etc. also accept them. +# (The list follows the same order as the GNU Coding Standards.) +bindir='${exec_prefix}/bin' +sbindir='${exec_prefix}/sbin' +libexecdir='${exec_prefix}/libexec' +datarootdir='${prefix}/share' +datadir='${datarootdir}' +sysconfdir='${prefix}/etc' +sharedstatedir='${prefix}/com' +localstatedir='${prefix}/var' +includedir='${prefix}/include' +oldincludedir='/usr/include' +docdir='${datarootdir}/doc/${PACKAGE_TARNAME}' +infodir='${datarootdir}/info' +htmldir='${docdir}' +dvidir='${docdir}' +pdfdir='${docdir}' +psdir='${docdir}' +libdir='${exec_prefix}/lib' +localedir='${datarootdir}/locale' +mandir='${datarootdir}/man' + +ac_prev= +ac_dashdash= +for ac_option +do + # If the previous option needs an argument, assign it. + if test -n "$ac_prev"; then + eval $ac_prev=\$ac_option + ac_prev= + continue + fi + + case $ac_option in + *=?*) ac_optarg=`expr "X$ac_option" : '[^=]*=\(.*\)'` ;; + *=) ac_optarg= ;; + *) ac_optarg=yes ;; + esac + + # Accept the important Cygnus configure options, so we can diagnose typos. + + case $ac_dashdash$ac_option in + --) + ac_dashdash=yes ;; + + -bindir | --bindir | --bindi | --bind | --bin | --bi) + ac_prev=bindir ;; + -bindir=* | --bindir=* | --bindi=* | --bind=* | --bin=* | --bi=*) + bindir=$ac_optarg ;; + + -build | --build | --buil | --bui | --bu) + ac_prev=build_alias ;; + -build=* | --build=* | --buil=* | --bui=* | --bu=*) + build_alias=$ac_optarg ;; + + -cache-file | --cache-file | --cache-fil | --cache-fi \ + | --cache-f | --cache- | --cache | --cach | --cac | --ca | --c) + ac_prev=cache_file ;; + -cache-file=* | --cache-file=* | --cache-fil=* | --cache-fi=* \ + | --cache-f=* | --cache-=* | --cache=* | --cach=* | --cac=* | --ca=* | --c=*) + cache_file=$ac_optarg ;; + + --config-cache | -C) + cache_file=config.cache ;; + + -datadir | --datadir | --datadi | --datad) + ac_prev=datadir ;; + -datadir=* | --datadir=* | --datadi=* | --datad=*) + datadir=$ac_optarg ;; + + -datarootdir | --datarootdir | --datarootdi | --datarootd | --dataroot \ + | --dataroo | --dataro | --datar) + ac_prev=datarootdir ;; + -datarootdir=* | --datarootdir=* | --datarootdi=* | --datarootd=* \ + | --dataroot=* | --dataroo=* | --dataro=* | --datar=*) + datarootdir=$ac_optarg ;; + + -disable-* | --disable-*) + ac_useropt=`expr "x$ac_option" : 'x-*disable-\(.*\)'` + # Reject names that are not valid shell variable names. + expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && + as_fn_error $? "invalid feature name: $ac_useropt" + ac_useropt_orig=$ac_useropt + ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` + case $ac_user_opts in + *" +"enable_$ac_useropt" +"*) ;; + *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--disable-$ac_useropt_orig" + ac_unrecognized_sep=', ';; + esac + eval enable_$ac_useropt=no ;; + + -docdir | --docdir | --docdi | --doc | --do) + ac_prev=docdir ;; + -docdir=* | --docdir=* | --docdi=* | --doc=* | --do=*) + docdir=$ac_optarg ;; + + -dvidir | --dvidir | --dvidi | --dvid | --dvi | --dv) + ac_prev=dvidir ;; + -dvidir=* | --dvidir=* | --dvidi=* | --dvid=* | --dvi=* | --dv=*) + dvidir=$ac_optarg ;; + + -enable-* | --enable-*) + ac_useropt=`expr "x$ac_option" : 'x-*enable-\([^=]*\)'` + # Reject names that are not valid shell variable names. + expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && + as_fn_error $? "invalid feature name: $ac_useropt" + ac_useropt_orig=$ac_useropt + ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` + case $ac_user_opts in + *" +"enable_$ac_useropt" +"*) ;; + *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--enable-$ac_useropt_orig" + ac_unrecognized_sep=', ';; + esac + eval enable_$ac_useropt=\$ac_optarg ;; + + -exec-prefix | --exec_prefix | --exec-prefix | --exec-prefi \ + | --exec-pref | --exec-pre | --exec-pr | --exec-p | --exec- \ + | --exec | --exe | --ex) + ac_prev=exec_prefix ;; + -exec-prefix=* | --exec_prefix=* | --exec-prefix=* | --exec-prefi=* \ + | --exec-pref=* | --exec-pre=* | --exec-pr=* | --exec-p=* | --exec-=* \ + | --exec=* | --exe=* | --ex=*) + exec_prefix=$ac_optarg ;; + + -gas | --gas | --ga | --g) + # Obsolete; use --with-gas. + with_gas=yes ;; + + -help | --help | --hel | --he | -h) + ac_init_help=long ;; + -help=r* | --help=r* | --hel=r* | --he=r* | -hr*) + ac_init_help=recursive ;; + -help=s* | --help=s* | --hel=s* | --he=s* | -hs*) + ac_init_help=short ;; + + -host | --host | --hos | --ho) + ac_prev=host_alias ;; + -host=* | --host=* | --hos=* | --ho=*) + host_alias=$ac_optarg ;; + + -htmldir | --htmldir | --htmldi | --htmld | --html | --htm | --ht) + ac_prev=htmldir ;; + -htmldir=* | --htmldir=* | --htmldi=* | --htmld=* | --html=* | --htm=* \ + | --ht=*) + htmldir=$ac_optarg ;; + + -includedir | --includedir | --includedi | --included | --include \ + | --includ | --inclu | --incl | --inc) + ac_prev=includedir ;; + -includedir=* | --includedir=* | --includedi=* | --included=* | --include=* \ + | --includ=* | --inclu=* | --incl=* | --inc=*) + includedir=$ac_optarg ;; + + -infodir | --infodir | --infodi | --infod | --info | --inf) + ac_prev=infodir ;; + -infodir=* | --infodir=* | --infodi=* | --infod=* | --info=* | --inf=*) + infodir=$ac_optarg ;; + + -libdir | --libdir | --libdi | --libd) + ac_prev=libdir ;; + -libdir=* | --libdir=* | --libdi=* | --libd=*) + libdir=$ac_optarg ;; + + -libexecdir | --libexecdir | --libexecdi | --libexecd | --libexec \ + | --libexe | --libex | --libe) + ac_prev=libexecdir ;; + -libexecdir=* | --libexecdir=* | --libexecdi=* | --libexecd=* | --libexec=* \ + | --libexe=* | --libex=* | --libe=*) + libexecdir=$ac_optarg ;; + + -localedir | --localedir | --localedi | --localed | --locale) + ac_prev=localedir ;; + -localedir=* | --localedir=* | --localedi=* | --localed=* | --locale=*) + localedir=$ac_optarg ;; + + -localstatedir | --localstatedir | --localstatedi | --localstated \ + | --localstate | --localstat | --localsta | --localst | --locals) + ac_prev=localstatedir ;; + -localstatedir=* | --localstatedir=* | --localstatedi=* | --localstated=* \ + | --localstate=* | --localstat=* | --localsta=* | --localst=* | --locals=*) + localstatedir=$ac_optarg ;; + + -mandir | --mandir | --mandi | --mand | --man | --ma | --m) + ac_prev=mandir ;; + -mandir=* | --mandir=* | --mandi=* | --mand=* | --man=* | --ma=* | --m=*) + mandir=$ac_optarg ;; + + -nfp | --nfp | --nf) + # Obsolete; use --without-fp. + with_fp=no ;; + + -no-create | --no-create | --no-creat | --no-crea | --no-cre \ + | --no-cr | --no-c | -n) + no_create=yes ;; + + -no-recursion | --no-recursion | --no-recursio | --no-recursi \ + | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r) + no_recursion=yes ;; + + -oldincludedir | --oldincludedir | --oldincludedi | --oldincluded \ + | --oldinclude | --oldinclud | --oldinclu | --oldincl | --oldinc \ + | --oldin | --oldi | --old | --ol | --o) + ac_prev=oldincludedir ;; + -oldincludedir=* | --oldincludedir=* | --oldincludedi=* | --oldincluded=* \ + | --oldinclude=* | --oldinclud=* | --oldinclu=* | --oldincl=* | --oldinc=* \ + | --oldin=* | --oldi=* | --old=* | --ol=* | --o=*) + oldincludedir=$ac_optarg ;; + + -prefix | --prefix | --prefi | --pref | --pre | --pr | --p) + ac_prev=prefix ;; + -prefix=* | --prefix=* | --prefi=* | --pref=* | --pre=* | --pr=* | --p=*) + prefix=$ac_optarg ;; + + -program-prefix | --program-prefix | --program-prefi | --program-pref \ + | --program-pre | --program-pr | --program-p) + ac_prev=program_prefix ;; + -program-prefix=* | --program-prefix=* | --program-prefi=* \ + | --program-pref=* | --program-pre=* | --program-pr=* | --program-p=*) + program_prefix=$ac_optarg ;; + + -program-suffix | --program-suffix | --program-suffi | --program-suff \ + | --program-suf | --program-su | --program-s) + ac_prev=program_suffix ;; + -program-suffix=* | --program-suffix=* | --program-suffi=* \ + | --program-suff=* | --program-suf=* | --program-su=* | --program-s=*) + program_suffix=$ac_optarg ;; + + -program-transform-name | --program-transform-name \ + | --program-transform-nam | --program-transform-na \ + | --program-transform-n | --program-transform- \ + | --program-transform | --program-transfor \ + | --program-transfo | --program-transf \ + | --program-trans | --program-tran \ + | --progr-tra | --program-tr | --program-t) + ac_prev=program_transform_name ;; + -program-transform-name=* | --program-transform-name=* \ + | --program-transform-nam=* | --program-transform-na=* \ + | --program-transform-n=* | --program-transform-=* \ + | --program-transform=* | --program-transfor=* \ + | --program-transfo=* | --program-transf=* \ + | --program-trans=* | --program-tran=* \ + | --progr-tra=* | --program-tr=* | --program-t=*) + program_transform_name=$ac_optarg ;; + + -pdfdir | --pdfdir | --pdfdi | --pdfd | --pdf | --pd) + ac_prev=pdfdir ;; + -pdfdir=* | --pdfdir=* | --pdfdi=* | --pdfd=* | --pdf=* | --pd=*) + pdfdir=$ac_optarg ;; + + -psdir | --psdir | --psdi | --psd | --ps) + ac_prev=psdir ;; + -psdir=* | --psdir=* | --psdi=* | --psd=* | --ps=*) + psdir=$ac_optarg ;; + + -q | -quiet | --quiet | --quie | --qui | --qu | --q \ + | -silent | --silent | --silen | --sile | --sil) + silent=yes ;; + + -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb) + ac_prev=sbindir ;; + -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \ + | --sbi=* | --sb=*) + sbindir=$ac_optarg ;; + + -sharedstatedir | --sharedstatedir | --sharedstatedi \ + | --sharedstated | --sharedstate | --sharedstat | --sharedsta \ + | --sharedst | --shareds | --shared | --share | --shar \ + | --sha | --sh) + ac_prev=sharedstatedir ;; + -sharedstatedir=* | --sharedstatedir=* | --sharedstatedi=* \ + | --sharedstated=* | --sharedstate=* | --sharedstat=* | --sharedsta=* \ + | --sharedst=* | --shareds=* | --shared=* | --share=* | --shar=* \ + | --sha=* | --sh=*) + sharedstatedir=$ac_optarg ;; + + -site | --site | --sit) + ac_prev=site ;; + -site=* | --site=* | --sit=*) + site=$ac_optarg ;; + + -srcdir | --srcdir | --srcdi | --srcd | --src | --sr) + ac_prev=srcdir ;; + -srcdir=* | --srcdir=* | --srcdi=* | --srcd=* | --src=* | --sr=*) + srcdir=$ac_optarg ;; + + -sysconfdir | --sysconfdir | --sysconfdi | --sysconfd | --sysconf \ + | --syscon | --sysco | --sysc | --sys | --sy) + ac_prev=sysconfdir ;; + -sysconfdir=* | --sysconfdir=* | --sysconfdi=* | --sysconfd=* | --sysconf=* \ + | --syscon=* | --sysco=* | --sysc=* | --sys=* | --sy=*) + sysconfdir=$ac_optarg ;; + + -target | --target | --targe | --targ | --tar | --ta | --t) + ac_prev=target_alias ;; + -target=* | --target=* | --targe=* | --targ=* | --tar=* | --ta=* | --t=*) + target_alias=$ac_optarg ;; + + -v | -verbose | --verbose | --verbos | --verbo | --verb) + verbose=yes ;; + + -version | --version | --versio | --versi | --vers | -V) + ac_init_version=: ;; + + -with-* | --with-*) + ac_useropt=`expr "x$ac_option" : 'x-*with-\([^=]*\)'` + # Reject names that are not valid shell variable names. + expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && + as_fn_error $? "invalid package name: $ac_useropt" + ac_useropt_orig=$ac_useropt + ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` + case $ac_user_opts in + *" +"with_$ac_useropt" +"*) ;; + *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--with-$ac_useropt_orig" + ac_unrecognized_sep=', ';; + esac + eval with_$ac_useropt=\$ac_optarg ;; + + -without-* | --without-*) + ac_useropt=`expr "x$ac_option" : 'x-*without-\(.*\)'` + # Reject names that are not valid shell variable names. + expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && + as_fn_error $? "invalid package name: $ac_useropt" + ac_useropt_orig=$ac_useropt + ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` + case $ac_user_opts in + *" +"with_$ac_useropt" +"*) ;; + *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--without-$ac_useropt_orig" + ac_unrecognized_sep=', ';; + esac + eval with_$ac_useropt=no ;; + + --x) + # Obsolete; use --with-x. + with_x=yes ;; + + -x-includes | --x-includes | --x-include | --x-includ | --x-inclu \ + | --x-incl | --x-inc | --x-in | --x-i) + ac_prev=x_includes ;; + -x-includes=* | --x-includes=* | --x-include=* | --x-includ=* | --x-inclu=* \ + | --x-incl=* | --x-inc=* | --x-in=* | --x-i=*) + x_includes=$ac_optarg ;; + + -x-libraries | --x-libraries | --x-librarie | --x-librari \ + | --x-librar | --x-libra | --x-libr | --x-lib | --x-li | --x-l) + ac_prev=x_libraries ;; + -x-libraries=* | --x-libraries=* | --x-librarie=* | --x-librari=* \ + | --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*) + x_libraries=$ac_optarg ;; + + -*) as_fn_error $? "unrecognized option: \`$ac_option' +Try \`$0 --help' for more information" + ;; + + *=*) + ac_envvar=`expr "x$ac_option" : 'x\([^=]*\)='` + # Reject names that are not valid shell variable names. + case $ac_envvar in #( + '' | [0-9]* | *[!_$as_cr_alnum]* ) + as_fn_error $? "invalid variable name: \`$ac_envvar'" ;; + esac + eval $ac_envvar=\$ac_optarg + export $ac_envvar ;; + + *) + # FIXME: should be removed in autoconf 3.0. + $as_echo "$as_me: WARNING: you should use --build, --host, --target" >&2 + expr "x$ac_option" : ".*[^-._$as_cr_alnum]" >/dev/null && + $as_echo "$as_me: WARNING: invalid host type: $ac_option" >&2 + : "${build_alias=$ac_option} ${host_alias=$ac_option} ${target_alias=$ac_option}" + ;; + + esac +done + +if test -n "$ac_prev"; then + ac_option=--`echo $ac_prev | sed 's/_/-/g'` + as_fn_error $? "missing argument to $ac_option" +fi + +if test -n "$ac_unrecognized_opts"; then + case $enable_option_checking in + no) ;; + fatal) as_fn_error $? "unrecognized options: $ac_unrecognized_opts" ;; + *) $as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2 ;; + esac +fi + +# Check all directory arguments for consistency. +for ac_var in exec_prefix prefix bindir sbindir libexecdir datarootdir \ + datadir sysconfdir sharedstatedir localstatedir includedir \ + oldincludedir docdir infodir htmldir dvidir pdfdir psdir \ + libdir localedir mandir +do + eval ac_val=\$$ac_var + # Remove trailing slashes. + case $ac_val in + */ ) + ac_val=`expr "X$ac_val" : 'X\(.*[^/]\)' \| "X$ac_val" : 'X\(.*\)'` + eval $ac_var=\$ac_val;; + esac + # Be sure to have absolute directory names. + case $ac_val in + [\\/$]* | ?:[\\/]* ) continue;; + NONE | '' ) case $ac_var in *prefix ) continue;; esac;; + esac + as_fn_error $? "expected an absolute directory name for --$ac_var: $ac_val" +done + +# There might be people who depend on the old broken behavior: `$host' +# used to hold the argument of --host etc. +# FIXME: To remove some day. +build=$build_alias +host=$host_alias +target=$target_alias + +# FIXME: To remove some day. +if test "x$host_alias" != x; then + if test "x$build_alias" = x; then + cross_compiling=maybe + elif test "x$build_alias" != "x$host_alias"; then + cross_compiling=yes + fi +fi + +ac_tool_prefix= +test -n "$host_alias" && ac_tool_prefix=$host_alias- + +test "$silent" = yes && exec 6>/dev/null + + +ac_pwd=`pwd` && test -n "$ac_pwd" && +ac_ls_di=`ls -di .` && +ac_pwd_ls_di=`cd "$ac_pwd" && ls -di .` || + as_fn_error $? "working directory cannot be determined" +test "X$ac_ls_di" = "X$ac_pwd_ls_di" || + as_fn_error $? "pwd does not report name of working directory" + + +# Find the source files, if location was not specified. +if test -z "$srcdir"; then + ac_srcdir_defaulted=yes + # Try the directory containing this script, then the parent directory. + ac_confdir=`$as_dirname -- "$as_myself" || +$as_expr X"$as_myself" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$as_myself" : 'X\(//\)[^/]' \| \ + X"$as_myself" : 'X\(//\)$' \| \ + X"$as_myself" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X"$as_myself" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + srcdir=$ac_confdir + if test ! -r "$srcdir/$ac_unique_file"; then + srcdir=.. + fi +else + ac_srcdir_defaulted=no +fi +if test ! -r "$srcdir/$ac_unique_file"; then + test "$ac_srcdir_defaulted" = yes && srcdir="$ac_confdir or .." + as_fn_error $? "cannot find sources ($ac_unique_file) in $srcdir" +fi +ac_msg="sources are in $srcdir, but \`cd $srcdir' does not work" +ac_abs_confdir=`( + cd "$srcdir" && test -r "./$ac_unique_file" || as_fn_error $? "$ac_msg" + pwd)` +# When building in place, set srcdir=. +if test "$ac_abs_confdir" = "$ac_pwd"; then + srcdir=. +fi +# Remove unnecessary trailing slashes from srcdir. +# Double slashes in file names in object file debugging info +# mess up M-x gdb in Emacs. +case $srcdir in +*/) srcdir=`expr "X$srcdir" : 'X\(.*[^/]\)' \| "X$srcdir" : 'X\(.*\)'`;; +esac +for ac_var in $ac_precious_vars; do + eval ac_env_${ac_var}_set=\${${ac_var}+set} + eval ac_env_${ac_var}_value=\$${ac_var} + eval ac_cv_env_${ac_var}_set=\${${ac_var}+set} + eval ac_cv_env_${ac_var}_value=\$${ac_var} +done + +# +# Report the --help message. +# +if test "$ac_init_help" = "long"; then + # Omit some internal or obsolete options to make the list less imposing. + # This message is too long to be a string in the A/UX 3.1 sh. + cat <<_ACEOF +\`configure' configures RtAudio 4.1 to adapt to many kinds of systems. + +Usage: $0 [OPTION]... [VAR=VALUE]... + +To assign environment variables (e.g., CC, CFLAGS...), specify them as +VAR=VALUE. See below for descriptions of some of the useful variables. + +Defaults for the options are specified in brackets. + +Configuration: + -h, --help display this help and exit + --help=short display options specific to this package + --help=recursive display the short help of all the included packages + -V, --version display version information and exit + -q, --quiet, --silent do not print \`checking ...' messages + --cache-file=FILE cache test results in FILE [disabled] + -C, --config-cache alias for \`--cache-file=config.cache' + -n, --no-create do not create output files + --srcdir=DIR find the sources in DIR [configure dir or \`..'] + +Installation directories: + --prefix=PREFIX install architecture-independent files in PREFIX + [$ac_default_prefix] + --exec-prefix=EPREFIX install architecture-dependent files in EPREFIX + [PREFIX] + +By default, \`make install' will install all the files in +\`$ac_default_prefix/bin', \`$ac_default_prefix/lib' etc. You can specify +an installation prefix other than \`$ac_default_prefix' using \`--prefix', +for instance \`--prefix=\$HOME'. + +For better control, use the options below. + +Fine tuning of the installation directories: + --bindir=DIR user executables [EPREFIX/bin] + --sbindir=DIR system admin executables [EPREFIX/sbin] + --libexecdir=DIR program executables [EPREFIX/libexec] + --sysconfdir=DIR read-only single-machine data [PREFIX/etc] + --sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com] + --localstatedir=DIR modifiable single-machine data [PREFIX/var] + --libdir=DIR object code libraries [EPREFIX/lib] + --includedir=DIR C header files [PREFIX/include] + --oldincludedir=DIR C header files for non-gcc [/usr/include] + --datarootdir=DIR read-only arch.-independent data root [PREFIX/share] + --datadir=DIR read-only architecture-independent data [DATAROOTDIR] + --infodir=DIR info documentation [DATAROOTDIR/info] + --localedir=DIR locale-dependent data [DATAROOTDIR/locale] + --mandir=DIR man documentation [DATAROOTDIR/man] + --docdir=DIR documentation root [DATAROOTDIR/doc/rtaudio] + --htmldir=DIR html documentation [DOCDIR] + --dvidir=DIR dvi documentation [DOCDIR] + --pdfdir=DIR pdf documentation [DOCDIR] + --psdir=DIR ps documentation [DOCDIR] +_ACEOF + + cat <<\_ACEOF + +System types: + --build=BUILD configure for building on BUILD [guessed] + --host=HOST cross-compile to build programs to run on HOST [BUILD] +_ACEOF +fi + +if test -n "$ac_init_help"; then + case $ac_init_help in + short | recursive ) echo "Configuration of RtAudio 4.1:";; + esac + cat <<\_ACEOF + +Optional Features: + --disable-option-checking ignore unrecognized --enable/--with options + --disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no) + --enable-FEATURE[=ARG] include FEATURE [ARG=yes] + --enable-debug = enable various debug output + +Optional Packages: + --with-PACKAGE[=ARG] use PACKAGE [ARG=yes] + --without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no) + --with-jack = choose JACK server support (mac and linux only) + --with-alsa = choose native ALSA API support (linux only) + --with-pulse = choose PulseAudio API support (linux only) + --with-oss = choose OSS API support (linux only) + --with-jack = choose JACK server support (unix only) + --with-core = choose CoreAudio API support (mac only) + --with-asio = choose ASIO API support (windoze only) + --with-ds = choose DirectSound API support (windoze only) + --with-wasapi = choose Windows Audio Session API support (windoze only) + +Some influential environment variables: + PKG_CONFIG path to pkg-config utility + PKG_CONFIG_PATH + directories to add to pkg-config's search path + PKG_CONFIG_LIBDIR + path overriding pkg-config's built-in search path + CXX C++ compiler command + CXXFLAGS C++ compiler flags + LDFLAGS linker flags, e.g. -L if you have libraries in a + nonstandard directory + LIBS libraries to pass to the linker, e.g. -l + CPPFLAGS (Objective) C/C++ preprocessor flags, e.g. -I if + you have headers in a nonstandard directory + CC C compiler command + CFLAGS C compiler flags + CPP C preprocessor + PULSE_CFLAGS + C compiler flags for PULSE, overriding pkg-config + PULSE_LIBS linker flags for PULSE, overriding pkg-config + +Use these variables to override the choices made by `configure' or to help +it to find libraries and programs with nonstandard names/locations. + +Report bugs to . +_ACEOF +ac_status=$? +fi + +if test "$ac_init_help" = "recursive"; then + # If there are subdirs, report their specific --help. + for ac_dir in : $ac_subdirs_all; do test "x$ac_dir" = x: && continue + test -d "$ac_dir" || + { cd "$srcdir" && ac_pwd=`pwd` && srcdir=. && test -d "$ac_dir"; } || + continue + ac_builddir=. + +case "$ac_dir" in +.) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; +*) + ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'` + # A ".." for each directory in $ac_dir_suffix. + ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` + case $ac_top_builddir_sub in + "") ac_top_builddir_sub=. ac_top_build_prefix= ;; + *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; + esac ;; +esac +ac_abs_top_builddir=$ac_pwd +ac_abs_builddir=$ac_pwd$ac_dir_suffix +# for backward compatibility: +ac_top_builddir=$ac_top_build_prefix + +case $srcdir in + .) # We are building in place. + ac_srcdir=. + ac_top_srcdir=$ac_top_builddir_sub + ac_abs_top_srcdir=$ac_pwd ;; + [\\/]* | ?:[\\/]* ) # Absolute name. + ac_srcdir=$srcdir$ac_dir_suffix; + ac_top_srcdir=$srcdir + ac_abs_top_srcdir=$srcdir ;; + *) # Relative name. + ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix + ac_top_srcdir=$ac_top_build_prefix$srcdir + ac_abs_top_srcdir=$ac_pwd/$srcdir ;; +esac +ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix + + cd "$ac_dir" || { ac_status=$?; continue; } + # Check for guested configure. + if test -f "$ac_srcdir/configure.gnu"; then + echo && + $SHELL "$ac_srcdir/configure.gnu" --help=recursive + elif test -f "$ac_srcdir/configure"; then + echo && + $SHELL "$ac_srcdir/configure" --help=recursive + else + $as_echo "$as_me: WARNING: no configuration information is in $ac_dir" >&2 + fi || ac_status=$? + cd "$ac_pwd" || { ac_status=$?; break; } + done +fi + +test -n "$ac_init_help" && exit $ac_status +if $ac_init_version; then + cat <<\_ACEOF +RtAudio configure 4.1 +generated by GNU Autoconf 2.69 + +Copyright (C) 2012 Free Software Foundation, Inc. +This configure script is free software; the Free Software Foundation +gives unlimited permission to copy, distribute and modify it. +_ACEOF + exit +fi + +## ------------------------ ## +## Autoconf initialization. ## +## ------------------------ ## + +# ac_fn_cxx_try_compile LINENO +# ---------------------------- +# Try to compile conftest.$ac_ext, and return whether this succeeded. +ac_fn_cxx_try_compile () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + rm -f conftest.$ac_objext + if { { ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_compile") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + grep -v '^ *+' conftest.err >conftest.er1 + cat conftest.er1 >&5 + mv -f conftest.er1 conftest.err + fi + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && { + test -z "$ac_cxx_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then : + ac_retval=0 +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_retval=1 +fi + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + as_fn_set_status $ac_retval + +} # ac_fn_cxx_try_compile + +# ac_fn_c_try_compile LINENO +# -------------------------- +# Try to compile conftest.$ac_ext, and return whether this succeeded. +ac_fn_c_try_compile () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + rm -f conftest.$ac_objext + if { { ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_compile") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + grep -v '^ *+' conftest.err >conftest.er1 + cat conftest.er1 >&5 + mv -f conftest.er1 conftest.err + fi + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then : + ac_retval=0 +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_retval=1 +fi + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + as_fn_set_status $ac_retval + +} # ac_fn_c_try_compile + +# ac_fn_c_try_cpp LINENO +# ---------------------- +# Try to preprocess conftest.$ac_ext, and return whether this succeeded. +ac_fn_c_try_cpp () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + if { { ac_try="$ac_cpp conftest.$ac_ext" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_cpp conftest.$ac_ext") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + grep -v '^ *+' conftest.err >conftest.er1 + cat conftest.er1 >&5 + mv -f conftest.er1 conftest.err + fi + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } > conftest.i && { + test -z "$ac_c_preproc_warn_flag$ac_c_werror_flag" || + test ! -s conftest.err + }; then : + ac_retval=0 +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_retval=1 +fi + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + as_fn_set_status $ac_retval + +} # ac_fn_c_try_cpp + +# ac_fn_c_try_run LINENO +# ---------------------- +# Try to link conftest.$ac_ext, and return whether this succeeded. Assumes +# that executables *can* be run. +ac_fn_c_try_run () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + if { { ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_link") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && { ac_try='./conftest$ac_exeext' + { { case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_try") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; }; then : + ac_retval=0 +else + $as_echo "$as_me: program exited with status $ac_status" >&5 + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_retval=$ac_status +fi + rm -rf conftest.dSYM conftest_ipa8_conftest.oo + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + as_fn_set_status $ac_retval + +} # ac_fn_c_try_run + +# ac_fn_c_check_header_mongrel LINENO HEADER VAR INCLUDES +# ------------------------------------------------------- +# Tests whether HEADER exists, giving a warning if it cannot be compiled using +# the include files in INCLUDES and setting the cache variable VAR +# accordingly. +ac_fn_c_check_header_mongrel () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + if eval \${$3+:} false; then : + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 +$as_echo_n "checking for $2... " >&6; } +if eval \${$3+:} false; then : + $as_echo_n "(cached) " >&6 +fi +eval ac_res=\$$3 + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } +else + # Is the header compilable? +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking $2 usability" >&5 +$as_echo_n "checking $2 usability... " >&6; } +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +$4 +#include <$2> +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_header_compiler=yes +else + ac_header_compiler=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_header_compiler" >&5 +$as_echo "$ac_header_compiler" >&6; } + +# Is the header present? +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking $2 presence" >&5 +$as_echo_n "checking $2 presence... " >&6; } +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include <$2> +_ACEOF +if ac_fn_c_try_cpp "$LINENO"; then : + ac_header_preproc=yes +else + ac_header_preproc=no +fi +rm -f conftest.err conftest.i conftest.$ac_ext +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_header_preproc" >&5 +$as_echo "$ac_header_preproc" >&6; } + +# So? What about this header? +case $ac_header_compiler:$ac_header_preproc:$ac_c_preproc_warn_flag in #(( + yes:no: ) + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: accepted by the compiler, rejected by the preprocessor!" >&5 +$as_echo "$as_me: WARNING: $2: accepted by the compiler, rejected by the preprocessor!" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: proceeding with the compiler's result" >&5 +$as_echo "$as_me: WARNING: $2: proceeding with the compiler's result" >&2;} + ;; + no:yes:* ) + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: present but cannot be compiled" >&5 +$as_echo "$as_me: WARNING: $2: present but cannot be compiled" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: check for missing prerequisite headers?" >&5 +$as_echo "$as_me: WARNING: $2: check for missing prerequisite headers?" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: see the Autoconf documentation" >&5 +$as_echo "$as_me: WARNING: $2: see the Autoconf documentation" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: section \"Present But Cannot Be Compiled\"" >&5 +$as_echo "$as_me: WARNING: $2: section \"Present But Cannot Be Compiled\"" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: proceeding with the compiler's result" >&5 +$as_echo "$as_me: WARNING: $2: proceeding with the compiler's result" >&2;} +( $as_echo "## ----------------------------------- ## +## Report this to gary@music.mcgill.ca ## +## ----------------------------------- ##" + ) | sed "s/^/$as_me: WARNING: /" >&2 + ;; +esac + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 +$as_echo_n "checking for $2... " >&6; } +if eval \${$3+:} false; then : + $as_echo_n "(cached) " >&6 +else + eval "$3=\$ac_header_compiler" +fi +eval ac_res=\$$3 + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } +fi + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + +} # ac_fn_c_check_header_mongrel + +# ac_fn_c_check_header_compile LINENO HEADER VAR INCLUDES +# ------------------------------------------------------- +# Tests whether HEADER exists and can be compiled using the include files in +# INCLUDES, setting the cache variable VAR accordingly. +ac_fn_c_check_header_compile () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 +$as_echo_n "checking for $2... " >&6; } +if eval \${$3+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +$4 +#include <$2> +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + eval "$3=yes" +else + eval "$3=no" +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +eval ac_res=\$$3 + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + +} # ac_fn_c_check_header_compile + +# ac_fn_c_try_link LINENO +# ----------------------- +# Try to link conftest.$ac_ext, and return whether this succeeded. +ac_fn_c_try_link () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + rm -f conftest.$ac_objext conftest$ac_exeext + if { { ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_link") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + grep -v '^ *+' conftest.err >conftest.er1 + cat conftest.er1 >&5 + mv -f conftest.er1 conftest.err + fi + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest$ac_exeext && { + test "$cross_compiling" = yes || + test -x conftest$ac_exeext + }; then : + ac_retval=0 +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_retval=1 +fi + # Delete the IPA/IPO (Inter Procedural Analysis/Optimization) information + # created by the PGI compiler (conftest_ipa8_conftest.oo), as it would + # interfere with the next link command; also delete a directory that is + # left behind by Apple's compiler. We do this before executing the actions. + rm -rf conftest.dSYM conftest_ipa8_conftest.oo + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + as_fn_set_status $ac_retval + +} # ac_fn_c_try_link + +# ac_fn_c_check_func LINENO FUNC VAR +# ---------------------------------- +# Tests whether FUNC exists, setting the cache variable VAR accordingly +ac_fn_c_check_func () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 +$as_echo_n "checking for $2... " >&6; } +if eval \${$3+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +/* Define $2 to an innocuous variant, in case declares $2. + For example, HP-UX 11i declares gettimeofday. */ +#define $2 innocuous_$2 + +/* System header to define __stub macros and hopefully few prototypes, + which can conflict with char $2 (); below. + Prefer to if __STDC__ is defined, since + exists even on freestanding compilers. */ + +#ifdef __STDC__ +# include +#else +# include +#endif + +#undef $2 + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char $2 (); +/* The GNU C library defines this for functions which it implements + to always fail with ENOSYS. Some functions are actually named + something starting with __ and the normal name is an alias. */ +#if defined __stub_$2 || defined __stub___$2 +choke me +#endif + +int +main () +{ +return $2 (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + eval "$3=yes" +else + eval "$3=no" +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +fi +eval ac_res=\$$3 + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + +} # ac_fn_c_check_func +cat >config.log <<_ACEOF +This file contains any messages produced by compilers while +running configure, to aid debugging if configure makes a mistake. + +It was created by RtAudio $as_me 4.1, which was +generated by GNU Autoconf 2.69. Invocation command line was + + $ $0 $@ + +_ACEOF +exec 5>>config.log +{ +cat <<_ASUNAME +## --------- ## +## Platform. ## +## --------- ## + +hostname = `(hostname || uname -n) 2>/dev/null | sed 1q` +uname -m = `(uname -m) 2>/dev/null || echo unknown` +uname -r = `(uname -r) 2>/dev/null || echo unknown` +uname -s = `(uname -s) 2>/dev/null || echo unknown` +uname -v = `(uname -v) 2>/dev/null || echo unknown` + +/usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null || echo unknown` +/bin/uname -X = `(/bin/uname -X) 2>/dev/null || echo unknown` + +/bin/arch = `(/bin/arch) 2>/dev/null || echo unknown` +/usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null || echo unknown` +/usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null || echo unknown` +/usr/bin/hostinfo = `(/usr/bin/hostinfo) 2>/dev/null || echo unknown` +/bin/machine = `(/bin/machine) 2>/dev/null || echo unknown` +/usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null || echo unknown` +/bin/universe = `(/bin/universe) 2>/dev/null || echo unknown` + +_ASUNAME + +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + $as_echo "PATH: $as_dir" + done +IFS=$as_save_IFS + +} >&5 + +cat >&5 <<_ACEOF + + +## ----------- ## +## Core tests. ## +## ----------- ## + +_ACEOF + + +# Keep a trace of the command line. +# Strip out --no-create and --no-recursion so they do not pile up. +# Strip out --silent because we don't want to record it for future runs. +# Also quote any args containing shell meta-characters. +# Make two passes to allow for proper duplicate-argument suppression. +ac_configure_args= +ac_configure_args0= +ac_configure_args1= +ac_must_keep_next=false +for ac_pass in 1 2 +do + for ac_arg + do + case $ac_arg in + -no-create | --no-c* | -n | -no-recursion | --no-r*) continue ;; + -q | -quiet | --quiet | --quie | --qui | --qu | --q \ + | -silent | --silent | --silen | --sile | --sil) + continue ;; + *\'*) + ac_arg=`$as_echo "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;; + esac + case $ac_pass in + 1) as_fn_append ac_configure_args0 " '$ac_arg'" ;; + 2) + as_fn_append ac_configure_args1 " '$ac_arg'" + if test $ac_must_keep_next = true; then + ac_must_keep_next=false # Got value, back to normal. + else + case $ac_arg in + *=* | --config-cache | -C | -disable-* | --disable-* \ + | -enable-* | --enable-* | -gas | --g* | -nfp | --nf* \ + | -q | -quiet | --q* | -silent | --sil* | -v | -verb* \ + | -with-* | --with-* | -without-* | --without-* | --x) + case "$ac_configure_args0 " in + "$ac_configure_args1"*" '$ac_arg' "* ) continue ;; + esac + ;; + -* ) ac_must_keep_next=true ;; + esac + fi + as_fn_append ac_configure_args " '$ac_arg'" + ;; + esac + done +done +{ ac_configure_args0=; unset ac_configure_args0;} +{ ac_configure_args1=; unset ac_configure_args1;} + +# When interrupted or exit'd, cleanup temporary files, and complete +# config.log. We remove comments because anyway the quotes in there +# would cause problems or look ugly. +# WARNING: Use '\'' to represent an apostrophe within the trap. +# WARNING: Do not start the trap code with a newline, due to a FreeBSD 4.0 bug. +trap 'exit_status=$? + # Save into config.log some information that might help in debugging. + { + echo + + $as_echo "## ---------------- ## +## Cache variables. ## +## ---------------- ##" + echo + # The following way of writing the cache mishandles newlines in values, +( + for ac_var in `(set) 2>&1 | sed -n '\''s/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'\''`; do + eval ac_val=\$$ac_var + case $ac_val in #( + *${as_nl}*) + case $ac_var in #( + *_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 +$as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; + esac + case $ac_var in #( + _ | IFS | as_nl) ;; #( + BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #( + *) { eval $ac_var=; unset $ac_var;} ;; + esac ;; + esac + done + (set) 2>&1 | + case $as_nl`(ac_space='\'' '\''; set) 2>&1` in #( + *${as_nl}ac_space=\ *) + sed -n \ + "s/'\''/'\''\\\\'\'''\''/g; + s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\''\\2'\''/p" + ;; #( + *) + sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p" + ;; + esac | + sort +) + echo + + $as_echo "## ----------------- ## +## Output variables. ## +## ----------------- ##" + echo + for ac_var in $ac_subst_vars + do + eval ac_val=\$$ac_var + case $ac_val in + *\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; + esac + $as_echo "$ac_var='\''$ac_val'\''" + done | sort + echo + + if test -n "$ac_subst_files"; then + $as_echo "## ------------------- ## +## File substitutions. ## +## ------------------- ##" + echo + for ac_var in $ac_subst_files + do + eval ac_val=\$$ac_var + case $ac_val in + *\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; + esac + $as_echo "$ac_var='\''$ac_val'\''" + done | sort + echo + fi + + if test -s confdefs.h; then + $as_echo "## ----------- ## +## confdefs.h. ## +## ----------- ##" + echo + cat confdefs.h + echo + fi + test "$ac_signal" != 0 && + $as_echo "$as_me: caught signal $ac_signal" + $as_echo "$as_me: exit $exit_status" + } >&5 + rm -f core *.core core.conftest.* && + rm -f -r conftest* confdefs* conf$$* $ac_clean_files && + exit $exit_status +' 0 +for ac_signal in 1 2 13 15; do + trap 'ac_signal='$ac_signal'; as_fn_exit 1' $ac_signal +done +ac_signal=0 + +# confdefs.h avoids OS command line length limits that DEFS can exceed. +rm -f -r conftest* confdefs.h + +$as_echo "/* confdefs.h */" > confdefs.h + +# Predefined preprocessor variables. + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_NAME "$PACKAGE_NAME" +_ACEOF + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_TARNAME "$PACKAGE_TARNAME" +_ACEOF + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_VERSION "$PACKAGE_VERSION" +_ACEOF + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_STRING "$PACKAGE_STRING" +_ACEOF + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_BUGREPORT "$PACKAGE_BUGREPORT" +_ACEOF + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_URL "$PACKAGE_URL" +_ACEOF + + +# Let the site file select an alternate cache file if it wants to. +# Prefer an explicitly selected file to automatically selected ones. +ac_site_file1=NONE +ac_site_file2=NONE +if test -n "$CONFIG_SITE"; then + # We do not want a PATH search for config.site. + case $CONFIG_SITE in #(( + -*) ac_site_file1=./$CONFIG_SITE;; + */*) ac_site_file1=$CONFIG_SITE;; + *) ac_site_file1=./$CONFIG_SITE;; + esac +elif test "x$prefix" != xNONE; then + ac_site_file1=$prefix/share/config.site + ac_site_file2=$prefix/etc/config.site +else + ac_site_file1=$ac_default_prefix/share/config.site + ac_site_file2=$ac_default_prefix/etc/config.site +fi +for ac_site_file in "$ac_site_file1" "$ac_site_file2" +do + test "x$ac_site_file" = xNONE && continue + if test /dev/null != "$ac_site_file" && test -r "$ac_site_file"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: loading site script $ac_site_file" >&5 +$as_echo "$as_me: loading site script $ac_site_file" >&6;} + sed 's/^/| /' "$ac_site_file" >&5 + . "$ac_site_file" \ + || { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "failed to load site script $ac_site_file +See \`config.log' for more details" "$LINENO" 5; } + fi +done + +if test -r "$cache_file"; then + # Some versions of bash will fail to source /dev/null (special files + # actually), so we avoid doing that. DJGPP emulates it as a regular file. + if test /dev/null != "$cache_file" && test -f "$cache_file"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: loading cache $cache_file" >&5 +$as_echo "$as_me: loading cache $cache_file" >&6;} + case $cache_file in + [\\/]* | ?:[\\/]* ) . "$cache_file";; + *) . "./$cache_file";; + esac + fi +else + { $as_echo "$as_me:${as_lineno-$LINENO}: creating cache $cache_file" >&5 +$as_echo "$as_me: creating cache $cache_file" >&6;} + >$cache_file +fi + +# Check that the precious variables saved in the cache have kept the same +# value. +ac_cache_corrupted=false +for ac_var in $ac_precious_vars; do + eval ac_old_set=\$ac_cv_env_${ac_var}_set + eval ac_new_set=\$ac_env_${ac_var}_set + eval ac_old_val=\$ac_cv_env_${ac_var}_value + eval ac_new_val=\$ac_env_${ac_var}_value + case $ac_old_set,$ac_new_set in + set,) + { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&5 +$as_echo "$as_me: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&2;} + ac_cache_corrupted=: ;; + ,set) + { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was not set in the previous run" >&5 +$as_echo "$as_me: error: \`$ac_var' was not set in the previous run" >&2;} + ac_cache_corrupted=: ;; + ,);; + *) + if test "x$ac_old_val" != "x$ac_new_val"; then + # differences in whitespace do not lead to failure. + ac_old_val_w=`echo x $ac_old_val` + ac_new_val_w=`echo x $ac_new_val` + if test "$ac_old_val_w" != "$ac_new_val_w"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' has changed since the previous run:" >&5 +$as_echo "$as_me: error: \`$ac_var' has changed since the previous run:" >&2;} + ac_cache_corrupted=: + else + { $as_echo "$as_me:${as_lineno-$LINENO}: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&5 +$as_echo "$as_me: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&2;} + eval $ac_var=\$ac_old_val + fi + { $as_echo "$as_me:${as_lineno-$LINENO}: former value: \`$ac_old_val'" >&5 +$as_echo "$as_me: former value: \`$ac_old_val'" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: current value: \`$ac_new_val'" >&5 +$as_echo "$as_me: current value: \`$ac_new_val'" >&2;} + fi;; + esac + # Pass precious variables to config.status. + if test "$ac_new_set" = set; then + case $ac_new_val in + *\'*) ac_arg=$ac_var=`$as_echo "$ac_new_val" | sed "s/'/'\\\\\\\\''/g"` ;; + *) ac_arg=$ac_var=$ac_new_val ;; + esac + case " $ac_configure_args " in + *" '$ac_arg' "*) ;; # Avoid dups. Use of quotes ensures accuracy. + *) as_fn_append ac_configure_args " '$ac_arg'" ;; + esac + fi +done +if $ac_cache_corrupted; then + { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: error: changes in the environment can compromise the build" >&5 +$as_echo "$as_me: error: changes in the environment can compromise the build" >&2;} + as_fn_error $? "run \`make distclean' and/or \`rm $cache_file' and start over" "$LINENO" 5 +fi +## -------------------- ## +## Main body of script. ## +## -------------------- ## + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + +ac_aux_dir= +for ac_dir in config "$srcdir"/config; do + if test -f "$ac_dir/install-sh"; then + ac_aux_dir=$ac_dir + ac_install_sh="$ac_aux_dir/install-sh -c" + break + elif test -f "$ac_dir/install.sh"; then + ac_aux_dir=$ac_dir + ac_install_sh="$ac_aux_dir/install.sh -c" + break + elif test -f "$ac_dir/shtool"; then + ac_aux_dir=$ac_dir + ac_install_sh="$ac_aux_dir/shtool install -c" + break + fi +done +if test -z "$ac_aux_dir"; then + as_fn_error $? "cannot find install-sh, install.sh, or shtool in config \"$srcdir\"/config" "$LINENO" 5 +fi + +# These three variables are undocumented and unsupported, +# and are intended to be withdrawn in a future Autoconf release. +# They can cause serious problems if a builder's source tree is in a directory +# whose full name contains unusual characters. +ac_config_guess="$SHELL $ac_aux_dir/config.guess" # Please don't use this var. +ac_config_sub="$SHELL $ac_aux_dir/config.sub" # Please don't use this var. +ac_configure="$SHELL $ac_aux_dir/configure" # Please don't use this var. + + + +ac_config_files="$ac_config_files rtaudio-config librtaudio.pc Makefile tests/Makefile" + + +# Fill GXX with something before test. +GXX="no" + + + + + + + + + + + +if test "x$ac_cv_env_PKG_CONFIG_set" != "xset"; then + if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}pkg-config", so it can be a program name with args. +set dummy ${ac_tool_prefix}pkg-config; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_path_PKG_CONFIG+:} false; then : + $as_echo_n "(cached) " >&6 +else + case $PKG_CONFIG in + [\\/]* | ?:[\\/]*) + ac_cv_path_PKG_CONFIG="$PKG_CONFIG" # Let the user override the test with a path. + ;; + *) + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_path_PKG_CONFIG="$as_dir/$ac_word$ac_exec_ext" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + + ;; +esac +fi +PKG_CONFIG=$ac_cv_path_PKG_CONFIG +if test -n "$PKG_CONFIG"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PKG_CONFIG" >&5 +$as_echo "$PKG_CONFIG" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_path_PKG_CONFIG"; then + ac_pt_PKG_CONFIG=$PKG_CONFIG + # Extract the first word of "pkg-config", so it can be a program name with args. +set dummy pkg-config; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_path_ac_pt_PKG_CONFIG+:} false; then : + $as_echo_n "(cached) " >&6 +else + case $ac_pt_PKG_CONFIG in + [\\/]* | ?:[\\/]*) + ac_cv_path_ac_pt_PKG_CONFIG="$ac_pt_PKG_CONFIG" # Let the user override the test with a path. + ;; + *) + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_path_ac_pt_PKG_CONFIG="$as_dir/$ac_word$ac_exec_ext" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + + ;; +esac +fi +ac_pt_PKG_CONFIG=$ac_cv_path_ac_pt_PKG_CONFIG +if test -n "$ac_pt_PKG_CONFIG"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_pt_PKG_CONFIG" >&5 +$as_echo "$ac_pt_PKG_CONFIG" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_pt_PKG_CONFIG" = x; then + PKG_CONFIG="" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + PKG_CONFIG=$ac_pt_PKG_CONFIG + fi +else + PKG_CONFIG="$ac_cv_path_PKG_CONFIG" +fi + +fi +if test -n "$PKG_CONFIG"; then + _pkg_min_version=0.9.0 + { $as_echo "$as_me:${as_lineno-$LINENO}: checking pkg-config is at least version $_pkg_min_version" >&5 +$as_echo_n "checking pkg-config is at least version $_pkg_min_version... " >&6; } + if $PKG_CONFIG --atleast-pkgconfig-version $_pkg_min_version; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + PKG_CONFIG="" + fi +fi + + + +# Checks for programs. +ac_ext=cpp +ac_cpp='$CXXCPP $CPPFLAGS' +ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_cxx_compiler_gnu +if test -z "$CXX"; then + if test -n "$CCC"; then + CXX=$CCC + else + if test -n "$ac_tool_prefix"; then + for ac_prog in g++ CC c++ cxx + do + # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args. +set dummy $ac_tool_prefix$ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CXX+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CXX"; then + ac_cv_prog_CXX="$CXX" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_CXX="$ac_tool_prefix$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +CXX=$ac_cv_prog_CXX +if test -n "$CXX"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CXX" >&5 +$as_echo "$CXX" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$CXX" && break + done +fi +if test -z "$CXX"; then + ac_ct_CXX=$CXX + for ac_prog in g++ CC c++ cxx +do + # Extract the first word of "$ac_prog", so it can be a program name with args. +set dummy $ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_CXX+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_CXX"; then + ac_cv_prog_ac_ct_CXX="$ac_ct_CXX" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_CXX="$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_CXX=$ac_cv_prog_ac_ct_CXX +if test -n "$ac_ct_CXX"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CXX" >&5 +$as_echo "$ac_ct_CXX" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$ac_ct_CXX" && break +done + + if test "x$ac_ct_CXX" = x; then + CXX="g++" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + CXX=$ac_ct_CXX + fi +fi + + fi +fi +# Provide some information about the compiler. +$as_echo "$as_me:${as_lineno-$LINENO}: checking for C++ compiler version" >&5 +set X $ac_compile +ac_compiler=$2 +for ac_option in --version -v -V -qversion; do + { { ac_try="$ac_compiler $ac_option >&5" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_compiler $ac_option >&5") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + sed '10a\ +... rest of stderr output deleted ... + 10q' conftest.err >conftest.er1 + cat conftest.er1 >&5 + fi + rm -f conftest.er1 conftest.err + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } +done + +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +ac_clean_files_save=$ac_clean_files +ac_clean_files="$ac_clean_files a.out a.out.dSYM a.exe b.out" +# Try to create an executable without -o first, disregard a.out. +# It will help us diagnose broken compilers, and finding out an intuition +# of exeext. +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the C++ compiler works" >&5 +$as_echo_n "checking whether the C++ compiler works... " >&6; } +ac_link_default=`$as_echo "$ac_link" | sed 's/ -o *conftest[^ ]*//'` + +# The possible output files: +ac_files="a.out conftest.exe conftest a.exe a_out.exe b.out conftest.*" + +ac_rmfiles= +for ac_file in $ac_files +do + case $ac_file in + *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;; + * ) ac_rmfiles="$ac_rmfiles $ac_file";; + esac +done +rm -f $ac_rmfiles + +if { { ac_try="$ac_link_default" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_link_default") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then : + # Autoconf-2.13 could set the ac_cv_exeext variable to `no'. +# So ignore a value of `no', otherwise this would lead to `EXEEXT = no' +# in a Makefile. We should not override ac_cv_exeext if it was cached, +# so that the user can short-circuit this test for compilers unknown to +# Autoconf. +for ac_file in $ac_files '' +do + test -f "$ac_file" || continue + case $ac_file in + *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) + ;; + [ab].out ) + # We found the default executable, but exeext='' is most + # certainly right. + break;; + *.* ) + if test "${ac_cv_exeext+set}" = set && test "$ac_cv_exeext" != no; + then :; else + ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'` + fi + # We set ac_cv_exeext here because the later test for it is not + # safe: cross compilers may not add the suffix if given an `-o' + # argument, so we may need to know it at that point already. + # Even if this section looks crufty: it has the advantage of + # actually working. + break;; + * ) + break;; + esac +done +test "$ac_cv_exeext" = no && ac_cv_exeext= + +else + ac_file='' +fi +if test -z "$ac_file"; then : + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +$as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + +{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error 77 "C++ compiler cannot create executables +See \`config.log' for more details" "$LINENO" 5; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for C++ compiler default output file name" >&5 +$as_echo_n "checking for C++ compiler default output file name... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_file" >&5 +$as_echo "$ac_file" >&6; } +ac_exeext=$ac_cv_exeext + +rm -f -r a.out a.out.dSYM a.exe conftest$ac_cv_exeext b.out +ac_clean_files=$ac_clean_files_save +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for suffix of executables" >&5 +$as_echo_n "checking for suffix of executables... " >&6; } +if { { ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_link") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then : + # If both `conftest.exe' and `conftest' are `present' (well, observable) +# catch `conftest.exe'. For instance with Cygwin, `ls conftest' will +# work properly (i.e., refer to `conftest.exe'), while it won't with +# `rm'. +for ac_file in conftest.exe conftest conftest.*; do + test -f "$ac_file" || continue + case $ac_file in + *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;; + *.* ) ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'` + break;; + * ) break;; + esac +done +else + { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "cannot compute suffix of executables: cannot compile and link +See \`config.log' for more details" "$LINENO" 5; } +fi +rm -f conftest conftest$ac_cv_exeext +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_exeext" >&5 +$as_echo "$ac_cv_exeext" >&6; } + +rm -f conftest.$ac_ext +EXEEXT=$ac_cv_exeext +ac_exeext=$EXEEXT +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +int +main () +{ +FILE *f = fopen ("conftest.out", "w"); + return ferror (f) || fclose (f) != 0; + + ; + return 0; +} +_ACEOF +ac_clean_files="$ac_clean_files conftest.out" +# Check that the compiler produces executables we can run. If not, either +# the compiler is broken, or we cross compile. +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are cross compiling" >&5 +$as_echo_n "checking whether we are cross compiling... " >&6; } +if test "$cross_compiling" != yes; then + { { ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_link") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } + if { ac_try='./conftest$ac_cv_exeext' + { { case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_try") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; }; then + cross_compiling=no + else + if test "$cross_compiling" = maybe; then + cross_compiling=yes + else + { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "cannot run C++ compiled programs. +If you meant to cross compile, use \`--host'. +See \`config.log' for more details" "$LINENO" 5; } + fi + fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $cross_compiling" >&5 +$as_echo "$cross_compiling" >&6; } + +rm -f conftest.$ac_ext conftest$ac_cv_exeext conftest.out +ac_clean_files=$ac_clean_files_save +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for suffix of object files" >&5 +$as_echo_n "checking for suffix of object files... " >&6; } +if ${ac_cv_objext+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +rm -f conftest.o conftest.obj +if { { ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_compile") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then : + for ac_file in conftest.o conftest.obj conftest.*; do + test -f "$ac_file" || continue; + case $ac_file in + *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM ) ;; + *) ac_cv_objext=`expr "$ac_file" : '.*\.\(.*\)'` + break;; + esac +done +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + +{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "cannot compute suffix of object files: cannot compile +See \`config.log' for more details" "$LINENO" 5; } +fi +rm -f conftest.$ac_cv_objext conftest.$ac_ext +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_objext" >&5 +$as_echo "$ac_cv_objext" >&6; } +OBJEXT=$ac_cv_objext +ac_objext=$OBJEXT +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are using the GNU C++ compiler" >&5 +$as_echo_n "checking whether we are using the GNU C++ compiler... " >&6; } +if ${ac_cv_cxx_compiler_gnu+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ +#ifndef __GNUC__ + choke me +#endif + + ; + return 0; +} +_ACEOF +if ac_fn_cxx_try_compile "$LINENO"; then : + ac_compiler_gnu=yes +else + ac_compiler_gnu=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +ac_cv_cxx_compiler_gnu=$ac_compiler_gnu + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_cxx_compiler_gnu" >&5 +$as_echo "$ac_cv_cxx_compiler_gnu" >&6; } +if test $ac_compiler_gnu = yes; then + GXX=yes +else + GXX= +fi +ac_test_CXXFLAGS=${CXXFLAGS+set} +ac_save_CXXFLAGS=$CXXFLAGS +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CXX accepts -g" >&5 +$as_echo_n "checking whether $CXX accepts -g... " >&6; } +if ${ac_cv_prog_cxx_g+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_save_cxx_werror_flag=$ac_cxx_werror_flag + ac_cxx_werror_flag=yes + ac_cv_prog_cxx_g=no + CXXFLAGS="-g" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_cxx_try_compile "$LINENO"; then : + ac_cv_prog_cxx_g=yes +else + CXXFLAGS="" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_cxx_try_compile "$LINENO"; then : + +else + ac_cxx_werror_flag=$ac_save_cxx_werror_flag + CXXFLAGS="-g" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_cxx_try_compile "$LINENO"; then : + ac_cv_prog_cxx_g=yes +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + ac_cxx_werror_flag=$ac_save_cxx_werror_flag +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cxx_g" >&5 +$as_echo "$ac_cv_prog_cxx_g" >&6; } +if test "$ac_test_CXXFLAGS" = set; then + CXXFLAGS=$ac_save_CXXFLAGS +elif test $ac_cv_prog_cxx_g = yes; then + if test "$GXX" = yes; then + CXXFLAGS="-g -O2" + else + CXXFLAGS="-g" + fi +else + if test "$GXX" = yes; then + CXXFLAGS="-O2" + else + CXXFLAGS= + fi +fi +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + +if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}ranlib", so it can be a program name with args. +set dummy ${ac_tool_prefix}ranlib; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_RANLIB+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$RANLIB"; then + ac_cv_prog_RANLIB="$RANLIB" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_RANLIB="${ac_tool_prefix}ranlib" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +RANLIB=$ac_cv_prog_RANLIB +if test -n "$RANLIB"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $RANLIB" >&5 +$as_echo "$RANLIB" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_RANLIB"; then + ac_ct_RANLIB=$RANLIB + # Extract the first word of "ranlib", so it can be a program name with args. +set dummy ranlib; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_RANLIB+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_RANLIB"; then + ac_cv_prog_ac_ct_RANLIB="$ac_ct_RANLIB" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_RANLIB="ranlib" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_RANLIB=$ac_cv_prog_ac_ct_RANLIB +if test -n "$ac_ct_RANLIB"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_RANLIB" >&5 +$as_echo "$ac_ct_RANLIB" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_RANLIB" = x; then + RANLIB=":" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + RANLIB=$ac_ct_RANLIB + fi +else + RANLIB="$ac_cv_prog_RANLIB" +fi + +# Extract the first word of "ar", so it can be a program name with args. +set dummy ar; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_path_AR+:} false; then : + $as_echo_n "(cached) " >&6 +else + case $AR in + [\\/]* | ?:[\\/]*) + ac_cv_path_AR="$AR" # Let the user override the test with a path. + ;; + *) + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_path_AR="$as_dir/$ac_word$ac_exec_ext" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + + test -z "$ac_cv_path_AR" && ac_cv_path_AR="no" + ;; +esac +fi +AR=$ac_cv_path_AR +if test -n "$AR"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $AR" >&5 +$as_echo "$AR" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +if [ $AR = "no" ] ; then + as_fn_error $? "\"Could not find ar - needed to create a library\"" "$LINENO" 5; +fi + +# Checks for header files. +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu +if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}gcc", so it can be a program name with args. +set dummy ${ac_tool_prefix}gcc; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_CC="${ac_tool_prefix}gcc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_CC"; then + ac_ct_CC=$CC + # Extract the first word of "gcc", so it can be a program name with args. +set dummy gcc; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_CC"; then + ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_CC="gcc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_CC=$ac_cv_prog_ac_ct_CC +if test -n "$ac_ct_CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 +$as_echo "$ac_ct_CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_CC" = x; then + CC="" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + CC=$ac_ct_CC + fi +else + CC="$ac_cv_prog_CC" +fi + +if test -z "$CC"; then + if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}cc", so it can be a program name with args. +set dummy ${ac_tool_prefix}cc; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_CC="${ac_tool_prefix}cc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + fi +fi +if test -z "$CC"; then + # Extract the first word of "cc", so it can be a program name with args. +set dummy cc; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else + ac_prog_rejected=no +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + if test "$as_dir/$ac_word$ac_exec_ext" = "/usr/ucb/cc"; then + ac_prog_rejected=yes + continue + fi + ac_cv_prog_CC="cc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +if test $ac_prog_rejected = yes; then + # We found a bogon in the path, so make sure we never use it. + set dummy $ac_cv_prog_CC + shift + if test $# != 0; then + # We chose a different compiler from the bogus one. + # However, it has the same basename, so the bogon will be chosen + # first if we set CC to just the basename; use the full file name. + shift + ac_cv_prog_CC="$as_dir/$ac_word${1+' '}$@" + fi +fi +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$CC"; then + if test -n "$ac_tool_prefix"; then + for ac_prog in cl.exe + do + # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args. +set dummy $ac_tool_prefix$ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_CC="$ac_tool_prefix$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$CC" && break + done +fi +if test -z "$CC"; then + ac_ct_CC=$CC + for ac_prog in cl.exe +do + # Extract the first word of "$ac_prog", so it can be a program name with args. +set dummy $ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_CC"; then + ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_CC="$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_CC=$ac_cv_prog_ac_ct_CC +if test -n "$ac_ct_CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 +$as_echo "$ac_ct_CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$ac_ct_CC" && break +done + + if test "x$ac_ct_CC" = x; then + CC="" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + CC=$ac_ct_CC + fi +fi + +fi + + +test -z "$CC" && { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "no acceptable C compiler found in \$PATH +See \`config.log' for more details" "$LINENO" 5; } + +# Provide some information about the compiler. +$as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler version" >&5 +set X $ac_compile +ac_compiler=$2 +for ac_option in --version -v -V -qversion; do + { { ac_try="$ac_compiler $ac_option >&5" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_compiler $ac_option >&5") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + sed '10a\ +... rest of stderr output deleted ... + 10q' conftest.err >conftest.er1 + cat conftest.er1 >&5 + fi + rm -f conftest.er1 conftest.err + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } +done + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are using the GNU C compiler" >&5 +$as_echo_n "checking whether we are using the GNU C compiler... " >&6; } +if ${ac_cv_c_compiler_gnu+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ +#ifndef __GNUC__ + choke me +#endif + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_compiler_gnu=yes +else + ac_compiler_gnu=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +ac_cv_c_compiler_gnu=$ac_compiler_gnu + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_compiler_gnu" >&5 +$as_echo "$ac_cv_c_compiler_gnu" >&6; } +if test $ac_compiler_gnu = yes; then + GCC=yes +else + GCC= +fi +ac_test_CFLAGS=${CFLAGS+set} +ac_save_CFLAGS=$CFLAGS +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC accepts -g" >&5 +$as_echo_n "checking whether $CC accepts -g... " >&6; } +if ${ac_cv_prog_cc_g+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_save_c_werror_flag=$ac_c_werror_flag + ac_c_werror_flag=yes + ac_cv_prog_cc_g=no + CFLAGS="-g" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_prog_cc_g=yes +else + CFLAGS="" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + +else + ac_c_werror_flag=$ac_save_c_werror_flag + CFLAGS="-g" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_prog_cc_g=yes +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + ac_c_werror_flag=$ac_save_c_werror_flag +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_g" >&5 +$as_echo "$ac_cv_prog_cc_g" >&6; } +if test "$ac_test_CFLAGS" = set; then + CFLAGS=$ac_save_CFLAGS +elif test $ac_cv_prog_cc_g = yes; then + if test "$GCC" = yes; then + CFLAGS="-g -O2" + else + CFLAGS="-g" + fi +else + if test "$GCC" = yes; then + CFLAGS="-O2" + else + CFLAGS= + fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $CC option to accept ISO C89" >&5 +$as_echo_n "checking for $CC option to accept ISO C89... " >&6; } +if ${ac_cv_prog_cc_c89+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_cv_prog_cc_c89=no +ac_save_CC=$CC +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +#include +struct stat; +/* Most of the following tests are stolen from RCS 5.7's src/conf.sh. */ +struct buf { int x; }; +FILE * (*rcsopen) (struct buf *, struct stat *, int); +static char *e (p, i) + char **p; + int i; +{ + return p[i]; +} +static char *f (char * (*g) (char **, int), char **p, ...) +{ + char *s; + va_list v; + va_start (v,p); + s = g (p, va_arg (v,int)); + va_end (v); + return s; +} + +/* OSF 4.0 Compaq cc is some sort of almost-ANSI by default. It has + function prototypes and stuff, but not '\xHH' hex character constants. + These don't provoke an error unfortunately, instead are silently treated + as 'x'. The following induces an error, until -std is added to get + proper ANSI mode. Curiously '\x00'!='x' always comes out true, for an + array size at least. It's necessary to write '\x00'==0 to get something + that's true only with -std. */ +int osf4_cc_array ['\x00' == 0 ? 1 : -1]; + +/* IBM C 6 for AIX is almost-ANSI by default, but it replaces macro parameters + inside strings and character constants. */ +#define FOO(x) 'x' +int xlc6_cc_array[FOO(a) == 'x' ? 1 : -1]; + +int test (int i, double x); +struct s1 {int (*f) (int a);}; +struct s2 {int (*f) (double a);}; +int pairnames (int, char **, FILE *(*)(struct buf *, struct stat *, int), int, int); +int argc; +char **argv; +int +main () +{ +return f (e, argv, 0) != argv[0] || f (e, argv, 1) != argv[1]; + ; + return 0; +} +_ACEOF +for ac_arg in '' -qlanglvl=extc89 -qlanglvl=ansi -std \ + -Ae "-Aa -D_HPUX_SOURCE" "-Xc -D__EXTENSIONS__" +do + CC="$ac_save_CC $ac_arg" + if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_prog_cc_c89=$ac_arg +fi +rm -f core conftest.err conftest.$ac_objext + test "x$ac_cv_prog_cc_c89" != "xno" && break +done +rm -f conftest.$ac_ext +CC=$ac_save_CC + +fi +# AC_CACHE_VAL +case "x$ac_cv_prog_cc_c89" in + x) + { $as_echo "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 +$as_echo "none needed" >&6; } ;; + xno) + { $as_echo "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 +$as_echo "unsupported" >&6; } ;; + *) + CC="$CC $ac_cv_prog_cc_c89" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c89" >&5 +$as_echo "$ac_cv_prog_cc_c89" >&6; } ;; +esac +if test "x$ac_cv_prog_cc_c89" != xno; then : + +fi + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to run the C preprocessor" >&5 +$as_echo_n "checking how to run the C preprocessor... " >&6; } +# On Suns, sometimes $CPP names a directory. +if test -n "$CPP" && test -d "$CPP"; then + CPP= +fi +if test -z "$CPP"; then + if ${ac_cv_prog_CPP+:} false; then : + $as_echo_n "(cached) " >&6 +else + # Double quotes because CPP needs to be expanded + for CPP in "$CC -E" "$CC -E -traditional-cpp" "/lib/cpp" + do + ac_preproc_ok=false +for ac_c_preproc_warn_flag in '' yes +do + # Use a header file that comes with gcc, so configuring glibc + # with a fresh cross-compiler works. + # Prefer to if __STDC__ is defined, since + # exists even on freestanding compilers. + # On the NeXT, cc -E runs the code through the compiler's parser, + # not just through cpp. "Syntax error" is here to catch this case. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#ifdef __STDC__ +# include +#else +# include +#endif + Syntax error +_ACEOF +if ac_fn_c_try_cpp "$LINENO"; then : + +else + # Broken: fails on valid input. +continue +fi +rm -f conftest.err conftest.i conftest.$ac_ext + + # OK, works on sane cases. Now check whether nonexistent headers + # can be detected and how. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +_ACEOF +if ac_fn_c_try_cpp "$LINENO"; then : + # Broken: success on invalid input. +continue +else + # Passes both tests. +ac_preproc_ok=: +break +fi +rm -f conftest.err conftest.i conftest.$ac_ext + +done +# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped. +rm -f conftest.i conftest.err conftest.$ac_ext +if $ac_preproc_ok; then : + break +fi + + done + ac_cv_prog_CPP=$CPP + +fi + CPP=$ac_cv_prog_CPP +else + ac_cv_prog_CPP=$CPP +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $CPP" >&5 +$as_echo "$CPP" >&6; } +ac_preproc_ok=false +for ac_c_preproc_warn_flag in '' yes +do + # Use a header file that comes with gcc, so configuring glibc + # with a fresh cross-compiler works. + # Prefer to if __STDC__ is defined, since + # exists even on freestanding compilers. + # On the NeXT, cc -E runs the code through the compiler's parser, + # not just through cpp. "Syntax error" is here to catch this case. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#ifdef __STDC__ +# include +#else +# include +#endif + Syntax error +_ACEOF +if ac_fn_c_try_cpp "$LINENO"; then : + +else + # Broken: fails on valid input. +continue +fi +rm -f conftest.err conftest.i conftest.$ac_ext + + # OK, works on sane cases. Now check whether nonexistent headers + # can be detected and how. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +_ACEOF +if ac_fn_c_try_cpp "$LINENO"; then : + # Broken: success on invalid input. +continue +else + # Passes both tests. +ac_preproc_ok=: +break +fi +rm -f conftest.err conftest.i conftest.$ac_ext + +done +# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped. +rm -f conftest.i conftest.err conftest.$ac_ext +if $ac_preproc_ok; then : + +else + { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "C preprocessor \"$CPP\" fails sanity check +See \`config.log' for more details" "$LINENO" 5; } +fi + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for grep that handles long lines and -e" >&5 +$as_echo_n "checking for grep that handles long lines and -e... " >&6; } +if ${ac_cv_path_GREP+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -z "$GREP"; then + ac_path_GREP_found=false + # Loop through the user's path and test for each of PROGNAME-LIST + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_prog in grep ggrep; do + for ac_exec_ext in '' $ac_executable_extensions; do + ac_path_GREP="$as_dir/$ac_prog$ac_exec_ext" + as_fn_executable_p "$ac_path_GREP" || continue +# Check for GNU ac_path_GREP and select it if it is found. + # Check for GNU $ac_path_GREP +case `"$ac_path_GREP" --version 2>&1` in +*GNU*) + ac_cv_path_GREP="$ac_path_GREP" ac_path_GREP_found=:;; +*) + ac_count=0 + $as_echo_n 0123456789 >"conftest.in" + while : + do + cat "conftest.in" "conftest.in" >"conftest.tmp" + mv "conftest.tmp" "conftest.in" + cp "conftest.in" "conftest.nl" + $as_echo 'GREP' >> "conftest.nl" + "$ac_path_GREP" -e 'GREP$' -e '-(cannot match)-' < "conftest.nl" >"conftest.out" 2>/dev/null || break + diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break + as_fn_arith $ac_count + 1 && ac_count=$as_val + if test $ac_count -gt ${ac_path_GREP_max-0}; then + # Best one so far, save it but keep looking for a better one + ac_cv_path_GREP="$ac_path_GREP" + ac_path_GREP_max=$ac_count + fi + # 10*(2^10) chars as input seems more than enough + test $ac_count -gt 10 && break + done + rm -f conftest.in conftest.tmp conftest.nl conftest.out;; +esac + + $ac_path_GREP_found && break 3 + done + done + done +IFS=$as_save_IFS + if test -z "$ac_cv_path_GREP"; then + as_fn_error $? "no acceptable grep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5 + fi +else + ac_cv_path_GREP=$GREP +fi + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_GREP" >&5 +$as_echo "$ac_cv_path_GREP" >&6; } + GREP="$ac_cv_path_GREP" + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for egrep" >&5 +$as_echo_n "checking for egrep... " >&6; } +if ${ac_cv_path_EGREP+:} false; then : + $as_echo_n "(cached) " >&6 +else + if echo a | $GREP -E '(a|b)' >/dev/null 2>&1 + then ac_cv_path_EGREP="$GREP -E" + else + if test -z "$EGREP"; then + ac_path_EGREP_found=false + # Loop through the user's path and test for each of PROGNAME-LIST + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_prog in egrep; do + for ac_exec_ext in '' $ac_executable_extensions; do + ac_path_EGREP="$as_dir/$ac_prog$ac_exec_ext" + as_fn_executable_p "$ac_path_EGREP" || continue +# Check for GNU ac_path_EGREP and select it if it is found. + # Check for GNU $ac_path_EGREP +case `"$ac_path_EGREP" --version 2>&1` in +*GNU*) + ac_cv_path_EGREP="$ac_path_EGREP" ac_path_EGREP_found=:;; +*) + ac_count=0 + $as_echo_n 0123456789 >"conftest.in" + while : + do + cat "conftest.in" "conftest.in" >"conftest.tmp" + mv "conftest.tmp" "conftest.in" + cp "conftest.in" "conftest.nl" + $as_echo 'EGREP' >> "conftest.nl" + "$ac_path_EGREP" 'EGREP$' < "conftest.nl" >"conftest.out" 2>/dev/null || break + diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break + as_fn_arith $ac_count + 1 && ac_count=$as_val + if test $ac_count -gt ${ac_path_EGREP_max-0}; then + # Best one so far, save it but keep looking for a better one + ac_cv_path_EGREP="$ac_path_EGREP" + ac_path_EGREP_max=$ac_count + fi + # 10*(2^10) chars as input seems more than enough + test $ac_count -gt 10 && break + done + rm -f conftest.in conftest.tmp conftest.nl conftest.out;; +esac + + $ac_path_EGREP_found && break 3 + done + done + done +IFS=$as_save_IFS + if test -z "$ac_cv_path_EGREP"; then + as_fn_error $? "no acceptable egrep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5 + fi +else + ac_cv_path_EGREP=$EGREP +fi + + fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_EGREP" >&5 +$as_echo "$ac_cv_path_EGREP" >&6; } + EGREP="$ac_cv_path_EGREP" + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for ANSI C header files" >&5 +$as_echo_n "checking for ANSI C header files... " >&6; } +if ${ac_cv_header_stdc+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +#include +#include +#include + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_header_stdc=yes +else + ac_cv_header_stdc=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + +if test $ac_cv_header_stdc = yes; then + # SunOS 4.x string.h does not declare mem*, contrary to ANSI. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include + +_ACEOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + $EGREP "memchr" >/dev/null 2>&1; then : + +else + ac_cv_header_stdc=no +fi +rm -f conftest* + +fi + +if test $ac_cv_header_stdc = yes; then + # ISC 2.0.2 stdlib.h does not declare free, contrary to ANSI. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include + +_ACEOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + $EGREP "free" >/dev/null 2>&1; then : + +else + ac_cv_header_stdc=no +fi +rm -f conftest* + +fi + +if test $ac_cv_header_stdc = yes; then + # /bin/cc in Irix-4.0.5 gets non-ANSI ctype macros unless using -ansi. + if test "$cross_compiling" = yes; then : + : +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +#include +#if ((' ' & 0x0FF) == 0x020) +# define ISLOWER(c) ('a' <= (c) && (c) <= 'z') +# define TOUPPER(c) (ISLOWER(c) ? 'A' + ((c) - 'a') : (c)) +#else +# define ISLOWER(c) \ + (('a' <= (c) && (c) <= 'i') \ + || ('j' <= (c) && (c) <= 'r') \ + || ('s' <= (c) && (c) <= 'z')) +# define TOUPPER(c) (ISLOWER(c) ? ((c) | 0x40) : (c)) +#endif + +#define XOR(e, f) (((e) && !(f)) || (!(e) && (f))) +int +main () +{ + int i; + for (i = 0; i < 256; i++) + if (XOR (islower (i), ISLOWER (i)) + || toupper (i) != TOUPPER (i)) + return 2; + return 0; +} +_ACEOF +if ac_fn_c_try_run "$LINENO"; then : + +else + ac_cv_header_stdc=no +fi +rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \ + conftest.$ac_objext conftest.beam conftest.$ac_ext +fi + +fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_header_stdc" >&5 +$as_echo "$ac_cv_header_stdc" >&6; } +if test $ac_cv_header_stdc = yes; then + +$as_echo "#define STDC_HEADERS 1" >>confdefs.h + +fi + +# On IRIX 5.3, sys/types and inttypes.h are conflicting. +for ac_header in sys/types.h sys/stat.h stdlib.h string.h memory.h strings.h \ + inttypes.h stdint.h unistd.h +do : + as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh` +ac_fn_c_check_header_compile "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default +" +if eval test \"x\$"$as_ac_Header"\" = x"yes"; then : + cat >>confdefs.h <<_ACEOF +#define `$as_echo "HAVE_$ac_header" | $as_tr_cpp` 1 +_ACEOF + +fi + +done + + +for ac_header in sys/ioctl.h unistd.h +do : + as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh` +ac_fn_c_check_header_mongrel "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default" +if eval test \"x\$"$as_ac_Header"\" = x"yes"; then : + cat >>confdefs.h <<_ACEOF +#define `$as_echo "HAVE_$ac_header" | $as_tr_cpp` 1 +_ACEOF + +fi + +done + + +# Check for debug +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to compile debug version" >&5 +$as_echo_n "checking whether to compile debug version... " >&6; } +# Check whether --enable-debug was given. +if test "${enable_debug+set}" = set; then : + enableval=$enable_debug; cppflag=-D__RTAUDIO_DEBUG__ + cxxflag=-g + object_path=Debug + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } +else + cppflag= + cxxflag=-O2 + object_path=Release + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + +# Checks for functions +ac_fn_c_check_func "$LINENO" "gettimeofday" "ac_cv_func_gettimeofday" +if test "x$ac_cv_func_gettimeofday" = xyes; then : + cppflag="$cppflag -DHAVE_GETTIMEOFDAY" +fi + + +# Set paths if prefix is defined +if test "x$prefix" != "x" && test "x$prefix" != "xNONE"; then + LIBS="$LIBS -L$prefix/lib" + CPPFLAGS="$CPPFLAGS -I$prefix/include" +fi + +# For -I and -D flags +CPPFLAGS="$CPPFLAGS $cppflag" + +# For debugging and optimization ... overwrite default because it has both -g and -O2 +#CXXFLAGS="$CXXFLAGS $cxxflag" +CXXFLAGS="$cxxflag" + +# Check compiler and use -Wall if gnu. +if test $GXX = "yes" ; then + cxxflag="-Wall -Wextra" + +fi + +CXXFLAGS="$CXXFLAGS $cxxflag" + +# Make sure we can run config.sub. +$SHELL "$ac_aux_dir/config.sub" sun4 >/dev/null 2>&1 || + as_fn_error $? "cannot run $SHELL $ac_aux_dir/config.sub" "$LINENO" 5 + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking build system type" >&5 +$as_echo_n "checking build system type... " >&6; } +if ${ac_cv_build+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_build_alias=$build_alias +test "x$ac_build_alias" = x && + ac_build_alias=`$SHELL "$ac_aux_dir/config.guess"` +test "x$ac_build_alias" = x && + as_fn_error $? "cannot guess build type; you must specify one" "$LINENO" 5 +ac_cv_build=`$SHELL "$ac_aux_dir/config.sub" $ac_build_alias` || + as_fn_error $? "$SHELL $ac_aux_dir/config.sub $ac_build_alias failed" "$LINENO" 5 + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_build" >&5 +$as_echo "$ac_cv_build" >&6; } +case $ac_cv_build in +*-*-*) ;; +*) as_fn_error $? "invalid value of canonical build" "$LINENO" 5;; +esac +build=$ac_cv_build +ac_save_IFS=$IFS; IFS='-' +set x $ac_cv_build +shift +build_cpu=$1 +build_vendor=$2 +shift; shift +# Remember, the first character of IFS is used to create $*, +# except with old shells: +build_os=$* +IFS=$ac_save_IFS +case $build_os in *\ *) build_os=`echo "$build_os" | sed 's/ /-/g'`;; esac + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking host system type" >&5 +$as_echo_n "checking host system type... " >&6; } +if ${ac_cv_host+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test "x$host_alias" = x; then + ac_cv_host=$ac_cv_build +else + ac_cv_host=`$SHELL "$ac_aux_dir/config.sub" $host_alias` || + as_fn_error $? "$SHELL $ac_aux_dir/config.sub $host_alias failed" "$LINENO" 5 +fi + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_host" >&5 +$as_echo "$ac_cv_host" >&6; } +case $ac_cv_host in +*-*-*) ;; +*) as_fn_error $? "invalid value of canonical host" "$LINENO" 5;; +esac +host=$ac_cv_host +ac_save_IFS=$IFS; IFS='-' +set x $ac_cv_host +shift +host_cpu=$1 +host_vendor=$2 +shift; shift +# Remember, the first character of IFS is used to create $*, +# except with old shells: +host_os=$* +IFS=$ac_save_IFS +case $host_os in *\ *) host_os=`echo "$host_os" | sed 's/ /-/g'`;; esac + + + +sharedlib="librtaudio.so" + +sharedname="librtaudio.so.\$(RELEASE)" + +libflags="-shared -Wl,-soname,\$(SHARED).\$(MAJOR) -o \$(SHARED).\$(RELEASE)" + +case $host in + *-apple*) + sharedlib="librtaudio.dylib" + + sharedname="librtaudio.\$(RELEASE).dylib" + + libflags="-dynamiclib -o librtaudio.\$(RELEASE).dylib" + +esac + +# Checks for package options and external software +api="" + +req="" + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for audio API" >&5 +$as_echo_n "checking for audio API... " >&6; } +case $host in + *-*-netbsd*) + { $as_echo "$as_me:${as_lineno-$LINENO}: result: using OSS" >&5 +$as_echo "using OSS" >&6; } + api="$api -D__LINUX_OSS__" + LIBS="$LIBS -lossaudio" + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for pthread_create in -lpthread" >&5 +$as_echo_n "checking for pthread_create in -lpthread... " >&6; } +if ${ac_cv_lib_pthread_pthread_create+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lpthread $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char pthread_create (); +int +main () +{ +return pthread_create (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_pthread_pthread_create=yes +else + ac_cv_lib_pthread_pthread_create=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_pthread_pthread_create" >&5 +$as_echo "$ac_cv_lib_pthread_pthread_create" >&6; } +if test "x$ac_cv_lib_pthread_pthread_create" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_LIBPTHREAD 1 +_ACEOF + + LIBS="-lpthread $LIBS" + +else + as_fn_error $? "RtAudio requires the pthread library!" "$LINENO" 5 +fi + + ;; + + *-*-linux*) + +# Check whether --with-jack was given. +if test "${with_jack+set}" = set; then : + withval=$with_jack; + api="$api -D__UNIX_JACK__" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: using JACK" >&5 +$as_echo "using JACK" >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for jack_client_open in -ljack" >&5 +$as_echo_n "checking for jack_client_open in -ljack... " >&6; } +if ${ac_cv_lib_jack_jack_client_open+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-ljack $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char jack_client_open (); +int +main () +{ +return jack_client_open (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_jack_jack_client_open=yes +else + ac_cv_lib_jack_jack_client_open=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_jack_jack_client_open" >&5 +$as_echo "$ac_cv_lib_jack_jack_client_open" >&6; } +if test "x$ac_cv_lib_jack_jack_client_open" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_LIBJACK 1 +_ACEOF + + LIBS="-ljack $LIBS" + +else + as_fn_error $? "JACK support requires the jack library!" "$LINENO" 5 +fi + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for snd_pcm_open in -lasound" >&5 +$as_echo_n "checking for snd_pcm_open in -lasound... " >&6; } +if ${ac_cv_lib_asound_snd_pcm_open+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lasound $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char snd_pcm_open (); +int +main () +{ +return snd_pcm_open (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_asound_snd_pcm_open=yes +else + ac_cv_lib_asound_snd_pcm_open=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_asound_snd_pcm_open" >&5 +$as_echo "$ac_cv_lib_asound_snd_pcm_open" >&6; } +if test "x$ac_cv_lib_asound_snd_pcm_open" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_LIBASOUND 1 +_ACEOF + + LIBS="-lasound $LIBS" + +else + as_fn_error $? "Jack support also requires the asound library!" "$LINENO" 5 +fi + +fi + + + # Look for ALSA flag + +# Check whether --with-alsa was given. +if test "${with_alsa+set}" = set; then : + withval=$with_alsa; + api="$api -D__LINUX_ALSA__" + req="$req alsa" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: using ALSA" >&5 +$as_echo "using ALSA" >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for snd_pcm_open in -lasound" >&5 +$as_echo_n "checking for snd_pcm_open in -lasound... " >&6; } +if ${ac_cv_lib_asound_snd_pcm_open+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lasound $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char snd_pcm_open (); +int +main () +{ +return snd_pcm_open (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_asound_snd_pcm_open=yes +else + ac_cv_lib_asound_snd_pcm_open=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_asound_snd_pcm_open" >&5 +$as_echo "$ac_cv_lib_asound_snd_pcm_open" >&6; } +if test "x$ac_cv_lib_asound_snd_pcm_open" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_LIBASOUND 1 +_ACEOF + + LIBS="-lasound $LIBS" + +else + as_fn_error $? "ALSA support requires the asound library!" "$LINENO" 5 +fi + +fi + + + # Look for PULSE flag + +# Check whether --with-pulse was given. +if test "${with_pulse+set}" = set; then : + withval=$with_pulse; + api="$api -D__LINUX_PULSE__" + req="$req libpulse-simple" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: using PulseAudio" >&5 +$as_echo "using PulseAudio" >&6; } + +pkg_failed=no +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for PULSE" >&5 +$as_echo_n "checking for PULSE... " >&6; } + +if test -n "$PULSE_CFLAGS"; then + pkg_cv_PULSE_CFLAGS="$PULSE_CFLAGS" + elif test -n "$PKG_CONFIG"; then + if test -n "$PKG_CONFIG" && \ + { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libpulse-simple\""; } >&5 + ($PKG_CONFIG --exists --print-errors "libpulse-simple") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + pkg_cv_PULSE_CFLAGS=`$PKG_CONFIG --cflags "libpulse-simple" 2>/dev/null` + test "x$?" != "x0" && pkg_failed=yes +else + pkg_failed=yes +fi + else + pkg_failed=untried +fi +if test -n "$PULSE_LIBS"; then + pkg_cv_PULSE_LIBS="$PULSE_LIBS" + elif test -n "$PKG_CONFIG"; then + if test -n "$PKG_CONFIG" && \ + { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libpulse-simple\""; } >&5 + ($PKG_CONFIG --exists --print-errors "libpulse-simple") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + pkg_cv_PULSE_LIBS=`$PKG_CONFIG --libs "libpulse-simple" 2>/dev/null` + test "x$?" != "x0" && pkg_failed=yes +else + pkg_failed=yes +fi + else + pkg_failed=untried +fi + + + +if test $pkg_failed = yes; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + +if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then + _pkg_short_errors_supported=yes +else + _pkg_short_errors_supported=no +fi + if test $_pkg_short_errors_supported = yes; then + PULSE_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libpulse-simple" 2>&1` + else + PULSE_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libpulse-simple" 2>&1` + fi + # Put the nasty error message in config.log where it belongs + echo "$PULSE_PKG_ERRORS" >&5 + + as_fn_error $? "PulseAudio support requires the pulse-simple library!" "$LINENO" 5 +elif test $pkg_failed = untried; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + as_fn_error $? "PulseAudio support requires the pulse-simple library!" "$LINENO" 5 +else + PULSE_CFLAGS=$pkg_cv_PULSE_CFLAGS + PULSE_LIBS=$pkg_cv_PULSE_LIBS + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + +fi + LIBS="$LIBS `pkg-config --libs libpulse-simple`" +fi + + + # Look for OSS flag + +# Check whether --with-oss was given. +if test "${with_oss+set}" = set; then : + withval=$with_oss; + api="$api -D__LINUX_OSS__" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: using OSS" >&5 +$as_echo "using OSS" >&6; } +fi + + + # If no audio api flags specified, use ALSA + if test "$api" == ""; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: using ALSA" >&5 +$as_echo "using ALSA" >&6; } + api=-D__LINUX_ALSA__ + + req="$req alsa" + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for snd_pcm_open in -lasound" >&5 +$as_echo_n "checking for snd_pcm_open in -lasound... " >&6; } +if ${ac_cv_lib_asound_snd_pcm_open+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lasound $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char snd_pcm_open (); +int +main () +{ +return snd_pcm_open (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_asound_snd_pcm_open=yes +else + ac_cv_lib_asound_snd_pcm_open=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_asound_snd_pcm_open" >&5 +$as_echo "$ac_cv_lib_asound_snd_pcm_open" >&6; } +if test "x$ac_cv_lib_asound_snd_pcm_open" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_LIBASOUND 1 +_ACEOF + + LIBS="-lasound $LIBS" + +else + as_fn_error $? "ALSA support requires the asound library!" "$LINENO" 5 +fi + + fi + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for pthread_create in -lpthread" >&5 +$as_echo_n "checking for pthread_create in -lpthread... " >&6; } +if ${ac_cv_lib_pthread_pthread_create+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lpthread $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char pthread_create (); +int +main () +{ +return pthread_create (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_pthread_pthread_create=yes +else + ac_cv_lib_pthread_pthread_create=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_pthread_pthread_create" >&5 +$as_echo "$ac_cv_lib_pthread_pthread_create" >&6; } +if test "x$ac_cv_lib_pthread_pthread_create" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_LIBPTHREAD 1 +_ACEOF + + LIBS="-lpthread $LIBS" + +else + as_fn_error $? "RtAudio requires the pthread library!" "$LINENO" 5 +fi + + ;; + + *-apple*) + +# Check whether --with-jack was given. +if test "${with_jack+set}" = set; then : + withval=$with_jack; + api="$api -D__UNIX_JACK__" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: using JACK" >&5 +$as_echo "using JACK" >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for jack_client_open in -ljack" >&5 +$as_echo_n "checking for jack_client_open in -ljack... " >&6; } +if ${ac_cv_lib_jack_jack_client_open+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-ljack $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char jack_client_open (); +int +main () +{ +return jack_client_open (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_jack_jack_client_open=yes +else + ac_cv_lib_jack_jack_client_open=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_jack_jack_client_open" >&5 +$as_echo "$ac_cv_lib_jack_jack_client_open" >&6; } +if test "x$ac_cv_lib_jack_jack_client_open" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_LIBJACK 1 +_ACEOF + + LIBS="-ljack $LIBS" + +else + as_fn_error $? "JACK support requires the jack library!" "$LINENO" 5 +fi + +fi + + +# AC_CHECK_HEADER(jack/jack.h, [], [AC_MSG_ERROR(Jack header file not found!)] ) +# LIBS="$LIBS -framework jackmp" ], ) + + + # Look for Core flag + +# Check whether --with-core was given. +if test "${with_core+set}" = set; then : + withval=$with_core; + api="$api -D__MACOSX_CORE__" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: using CoreAudio" >&5 +$as_echo "using CoreAudio" >&6; } + ac_fn_c_check_header_mongrel "$LINENO" "CoreAudio/CoreAudio.h" "ac_cv_header_CoreAudio_CoreAudio_h" "$ac_includes_default" +if test "x$ac_cv_header_CoreAudio_CoreAudio_h" = xyes; then : + +else + as_fn_error $? "CoreAudio header files not found!" "$LINENO" 5 +fi + + + LIBS="$LIBS -framework CoreAudio -framework CoreFoundation" +fi + + + # If no audio api flags specified, use CoreAudio + if test "$api" == ""; then + api=-D__MACOSX_CORE__ + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: using CoreAudio" >&5 +$as_echo "using CoreAudio" >&6; } + ac_fn_c_check_header_mongrel "$LINENO" "CoreAudio/CoreAudio.h" "ac_cv_header_CoreAudio_CoreAudio_h" "$ac_includes_default" +if test "x$ac_cv_header_CoreAudio_CoreAudio_h" = xyes; then : + +else + as_fn_error $? "CoreAudio header files not found!" "$LINENO" 5 +fi + + + LIBS="-framework CoreAudio -framework CoreFoundation" + + fi + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for pthread_create in -lpthread" >&5 +$as_echo_n "checking for pthread_create in -lpthread... " >&6; } +if ${ac_cv_lib_pthread_pthread_create+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lpthread $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char pthread_create (); +int +main () +{ +return pthread_create (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_pthread_pthread_create=yes +else + ac_cv_lib_pthread_pthread_create=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_pthread_pthread_create" >&5 +$as_echo "$ac_cv_lib_pthread_pthread_create" >&6; } +if test "x$ac_cv_lib_pthread_pthread_create" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_LIBPTHREAD 1 +_ACEOF + + LIBS="-lpthread $LIBS" + +else + as_fn_error $? "RtAudio requires the pthread library!" "$LINENO" 5 +fi + + ;; + + *-mingw32*) + +# Check whether --with-asio was given. +if test "${with_asio+set}" = set; then : + withval=$with_asio; + api="$api -D__WINDOWS_ASIO__" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: using ASIO" >&5 +$as_echo "using ASIO" >&6; } + objects="asio.o asiodrivers.o asiolist.o iasiothiscallresolver.o" + +fi + + + # Look for DirectSound flag + +# Check whether --with-ds was given. +if test "${with_ds+set}" = set; then : + withval=$with_ds; + api="$api -D__WINDOWS_DS__" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: using DirectSound" >&5 +$as_echo "using DirectSound" >&6; } + LIBS="-ldsound -lwinmm $LIBS" +fi + + + # Look for WASAPI flag + +# Check whether --with-wasapi was given. +if test "${with_wasapi+set}" = set; then : + withval=$with_wasapi; + api="$api -D__WINDOWS_WASAPI__" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: using WASAPI" >&5 +$as_echo "using WASAPI" >&6; } + LIBS="-lwinmm -luuid -lksuser $LIBS" +fi + + + # If no audio api flags specified, use DS + if test "$api" == ""; then + api=-D__WINDOWS_DS__ + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: using DirectSound" >&5 +$as_echo "using DirectSound" >&6; } + LIBS="-ldsound -lwinmm $LIBS" + fi + + LIBS="-lole32 $LIBS" + ;; + + *) + # Default case for unknown realtime systems. + as_fn_error $? "Unknown system type for realtime support!" "$LINENO" 5 + ;; +esac + +CPPFLAGS="$CPPFLAGS $api" + +cat >confcache <<\_ACEOF +# This file is a shell script that caches the results of configure +# tests run on this system so they can be shared between configure +# scripts and configure runs, see configure's option --config-cache. +# It is not useful on other systems. If it contains results you don't +# want to keep, you may remove or edit it. +# +# config.status only pays attention to the cache file if you give it +# the --recheck option to rerun configure. +# +# `ac_cv_env_foo' variables (set or unset) will be overridden when +# loading this file, other *unset* `ac_cv_foo' will be assigned the +# following values. + +_ACEOF + +# The following way of writing the cache mishandles newlines in values, +# but we know of no workaround that is simple, portable, and efficient. +# So, we kill variables containing newlines. +# Ultrix sh set writes to stderr and can't be redirected directly, +# and sets the high bit in the cache file unless we assign to the vars. +( + for ac_var in `(set) 2>&1 | sed -n 's/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'`; do + eval ac_val=\$$ac_var + case $ac_val in #( + *${as_nl}*) + case $ac_var in #( + *_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 +$as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; + esac + case $ac_var in #( + _ | IFS | as_nl) ;; #( + BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #( + *) { eval $ac_var=; unset $ac_var;} ;; + esac ;; + esac + done + + (set) 2>&1 | + case $as_nl`(ac_space=' '; set) 2>&1` in #( + *${as_nl}ac_space=\ *) + # `set' does not quote correctly, so add quotes: double-quote + # substitution turns \\\\ into \\, and sed turns \\ into \. + sed -n \ + "s/'/'\\\\''/g; + s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\\2'/p" + ;; #( + *) + # `set' quotes correctly as required by POSIX, so do not add quotes. + sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p" + ;; + esac | + sort +) | + sed ' + /^ac_cv_env_/b end + t clear + :clear + s/^\([^=]*\)=\(.*[{}].*\)$/test "${\1+set}" = set || &/ + t end + s/^\([^=]*\)=\(.*\)$/\1=${\1=\2}/ + :end' >>confcache +if diff "$cache_file" confcache >/dev/null 2>&1; then :; else + if test -w "$cache_file"; then + if test "x$cache_file" != "x/dev/null"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: updating cache $cache_file" >&5 +$as_echo "$as_me: updating cache $cache_file" >&6;} + if test ! -f "$cache_file" || test -h "$cache_file"; then + cat confcache >"$cache_file" + else + case $cache_file in #( + */* | ?:*) + mv -f confcache "$cache_file"$$ && + mv -f "$cache_file"$$ "$cache_file" ;; #( + *) + mv -f confcache "$cache_file" ;; + esac + fi + fi + else + { $as_echo "$as_me:${as_lineno-$LINENO}: not updating unwritable cache $cache_file" >&5 +$as_echo "$as_me: not updating unwritable cache $cache_file" >&6;} + fi +fi +rm -f confcache + +test "x$prefix" = xNONE && prefix=$ac_default_prefix +# Let make expand exec_prefix. +test "x$exec_prefix" = xNONE && exec_prefix='${prefix}' + +# Transform confdefs.h into DEFS. +# Protect against shell expansion while executing Makefile rules. +# Protect against Makefile macro expansion. +# +# If the first sed substitution is executed (which looks for macros that +# take arguments), then branch to the quote section. Otherwise, +# look for a macro that doesn't take arguments. +ac_script=' +:mline +/\\$/{ + N + s,\\\n,, + b mline +} +t clear +:clear +s/^[ ]*#[ ]*define[ ][ ]*\([^ (][^ (]*([^)]*)\)[ ]*\(.*\)/-D\1=\2/g +t quote +s/^[ ]*#[ ]*define[ ][ ]*\([^ ][^ ]*\)[ ]*\(.*\)/-D\1=\2/g +t quote +b any +:quote +s/[ `~#$^&*(){}\\|;'\''"<>?]/\\&/g +s/\[/\\&/g +s/\]/\\&/g +s/\$/$$/g +H +:any +${ + g + s/^\n// + s/\n/ /g + p +} +' +DEFS=`sed -n "$ac_script" confdefs.h` + + +ac_libobjs= +ac_ltlibobjs= +U= +for ac_i in : $LIBOBJS; do test "x$ac_i" = x: && continue + # 1. Remove the extension, and $U if already installed. + ac_script='s/\$U\././;s/\.o$//;s/\.obj$//' + ac_i=`$as_echo "$ac_i" | sed "$ac_script"` + # 2. Prepend LIBOBJDIR. When used with automake>=1.10 LIBOBJDIR + # will be set to the directory where LIBOBJS objects are built. + as_fn_append ac_libobjs " \${LIBOBJDIR}$ac_i\$U.$ac_objext" + as_fn_append ac_ltlibobjs " \${LIBOBJDIR}$ac_i"'$U.lo' +done +LIBOBJS=$ac_libobjs + +LTLIBOBJS=$ac_ltlibobjs + + + +: "${CONFIG_STATUS=./config.status}" +ac_write_fail=0 +ac_clean_files_save=$ac_clean_files +ac_clean_files="$ac_clean_files $CONFIG_STATUS" +{ $as_echo "$as_me:${as_lineno-$LINENO}: creating $CONFIG_STATUS" >&5 +$as_echo "$as_me: creating $CONFIG_STATUS" >&6;} +as_write_fail=0 +cat >$CONFIG_STATUS <<_ASEOF || as_write_fail=1 +#! $SHELL +# Generated by $as_me. +# Run this file to recreate the current configuration. +# Compiler output produced by configure, useful for debugging +# configure, is in config.log if it exists. + +debug=false +ac_cs_recheck=false +ac_cs_silent=false + +SHELL=\${CONFIG_SHELL-$SHELL} +export SHELL +_ASEOF +cat >>$CONFIG_STATUS <<\_ASEOF || as_write_fail=1 +## -------------------- ## +## M4sh Initialization. ## +## -------------------- ## + +# Be more Bourne compatible +DUALCASE=1; export DUALCASE # for MKS sh +if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then : + emulate sh + NULLCMD=: + # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which + # is contrary to our usage. Disable this feature. + alias -g '${1+"$@"}'='"$@"' + setopt NO_GLOB_SUBST +else + case `(set -o) 2>/dev/null` in #( + *posix*) : + set -o posix ;; #( + *) : + ;; +esac +fi + + +as_nl=' +' +export as_nl +# Printing a long string crashes Solaris 7 /usr/bin/printf. +as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' +as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo +as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo +# Prefer a ksh shell builtin over an external printf program on Solaris, +# but without wasting forks for bash or zsh. +if test -z "$BASH_VERSION$ZSH_VERSION" \ + && (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then + as_echo='print -r --' + as_echo_n='print -rn --' +elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then + as_echo='printf %s\n' + as_echo_n='printf %s' +else + if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then + as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"' + as_echo_n='/usr/ucb/echo -n' + else + as_echo_body='eval expr "X$1" : "X\\(.*\\)"' + as_echo_n_body='eval + arg=$1; + case $arg in #( + *"$as_nl"*) + expr "X$arg" : "X\\(.*\\)$as_nl"; + arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;; + esac; + expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl" + ' + export as_echo_n_body + as_echo_n='sh -c $as_echo_n_body as_echo' + fi + export as_echo_body + as_echo='sh -c $as_echo_body as_echo' +fi + +# The user is always right. +if test "${PATH_SEPARATOR+set}" != set; then + PATH_SEPARATOR=: + (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { + (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || + PATH_SEPARATOR=';' + } +fi + + +# IFS +# We need space, tab and new line, in precisely that order. Quoting is +# there to prevent editors from complaining about space-tab. +# (If _AS_PATH_WALK were called with IFS unset, it would disable word +# splitting by setting IFS to empty value.) +IFS=" "" $as_nl" + +# Find who we are. Look in the path if we contain no directory separator. +as_myself= +case $0 in #(( + *[\\/]* ) as_myself=$0 ;; + *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break + done +IFS=$as_save_IFS + + ;; +esac +# We did not find ourselves, most probably we were run as `sh COMMAND' +# in which case we are not to be found in the path. +if test "x$as_myself" = x; then + as_myself=$0 +fi +if test ! -f "$as_myself"; then + $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 + exit 1 +fi + +# Unset variables that we do not need and which cause bugs (e.g. in +# pre-3.0 UWIN ksh). But do not cause bugs in bash 2.01; the "|| exit 1" +# suppresses any "Segmentation fault" message there. '((' could +# trigger a bug in pdksh 5.2.14. +for as_var in BASH_ENV ENV MAIL MAILPATH +do eval test x\${$as_var+set} = xset \ + && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : +done +PS1='$ ' +PS2='> ' +PS4='+ ' + +# NLS nuisances. +LC_ALL=C +export LC_ALL +LANGUAGE=C +export LANGUAGE + +# CDPATH. +(unset CDPATH) >/dev/null 2>&1 && unset CDPATH + + +# as_fn_error STATUS ERROR [LINENO LOG_FD] +# ---------------------------------------- +# Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are +# provided, also output the error to LOG_FD, referencing LINENO. Then exit the +# script with STATUS, using 1 if that was 0. +as_fn_error () +{ + as_status=$1; test $as_status -eq 0 && as_status=1 + if test "$4"; then + as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + $as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 + fi + $as_echo "$as_me: error: $2" >&2 + as_fn_exit $as_status +} # as_fn_error + + +# as_fn_set_status STATUS +# ----------------------- +# Set $? to STATUS, without forking. +as_fn_set_status () +{ + return $1 +} # as_fn_set_status + +# as_fn_exit STATUS +# ----------------- +# Exit the shell with STATUS, even in a "trap 0" or "set -e" context. +as_fn_exit () +{ + set +e + as_fn_set_status $1 + exit $1 +} # as_fn_exit + +# as_fn_unset VAR +# --------------- +# Portably unset VAR. +as_fn_unset () +{ + { eval $1=; unset $1;} +} +as_unset=as_fn_unset +# as_fn_append VAR VALUE +# ---------------------- +# Append the text in VALUE to the end of the definition contained in VAR. Take +# advantage of any shell optimizations that allow amortized linear growth over +# repeated appends, instead of the typical quadratic growth present in naive +# implementations. +if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then : + eval 'as_fn_append () + { + eval $1+=\$2 + }' +else + as_fn_append () + { + eval $1=\$$1\$2 + } +fi # as_fn_append + +# as_fn_arith ARG... +# ------------------ +# Perform arithmetic evaluation on the ARGs, and store the result in the +# global $as_val. Take advantage of shells that can avoid forks. The arguments +# must be portable across $(()) and expr. +if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then : + eval 'as_fn_arith () + { + as_val=$(( $* )) + }' +else + as_fn_arith () + { + as_val=`expr "$@" || test $? -eq 1` + } +fi # as_fn_arith + + +if expr a : '\(a\)' >/dev/null 2>&1 && + test "X`expr 00001 : '.*\(...\)'`" = X001; then + as_expr=expr +else + as_expr=false +fi + +if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then + as_basename=basename +else + as_basename=false +fi + +if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then + as_dirname=dirname +else + as_dirname=false +fi + +as_me=`$as_basename -- "$0" || +$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ + X"$0" : 'X\(//\)$' \| \ + X"$0" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X/"$0" | + sed '/^.*\/\([^/][^/]*\)\/*$/{ + s//\1/ + q + } + /^X\/\(\/\/\)$/{ + s//\1/ + q + } + /^X\/\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + +# Avoid depending upon Character Ranges. +as_cr_letters='abcdefghijklmnopqrstuvwxyz' +as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' +as_cr_Letters=$as_cr_letters$as_cr_LETTERS +as_cr_digits='0123456789' +as_cr_alnum=$as_cr_Letters$as_cr_digits + +ECHO_C= ECHO_N= ECHO_T= +case `echo -n x` in #((((( +-n*) + case `echo 'xy\c'` in + *c*) ECHO_T=' ';; # ECHO_T is single tab character. + xy) ECHO_C='\c';; + *) echo `echo ksh88 bug on AIX 6.1` > /dev/null + ECHO_T=' ';; + esac;; +*) + ECHO_N='-n';; +esac + +rm -f conf$$ conf$$.exe conf$$.file +if test -d conf$$.dir; then + rm -f conf$$.dir/conf$$.file +else + rm -f conf$$.dir + mkdir conf$$.dir 2>/dev/null +fi +if (echo >conf$$.file) 2>/dev/null; then + if ln -s conf$$.file conf$$ 2>/dev/null; then + as_ln_s='ln -s' + # ... but there are two gotchas: + # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail. + # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable. + # In both cases, we have to default to `cp -pR'. + ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || + as_ln_s='cp -pR' + elif ln conf$$.file conf$$ 2>/dev/null; then + as_ln_s=ln + else + as_ln_s='cp -pR' + fi +else + as_ln_s='cp -pR' +fi +rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file +rmdir conf$$.dir 2>/dev/null + + +# as_fn_mkdir_p +# ------------- +# Create "$as_dir" as a directory, including parents if necessary. +as_fn_mkdir_p () +{ + + case $as_dir in #( + -*) as_dir=./$as_dir;; + esac + test -d "$as_dir" || eval $as_mkdir_p || { + as_dirs= + while :; do + case $as_dir in #( + *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( + *) as_qdir=$as_dir;; + esac + as_dirs="'$as_qdir' $as_dirs" + as_dir=`$as_dirname -- "$as_dir" || +$as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$as_dir" : 'X\(//\)[^/]' \| \ + X"$as_dir" : 'X\(//\)$' \| \ + X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X"$as_dir" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + test -d "$as_dir" && break + done + test -z "$as_dirs" || eval "mkdir $as_dirs" + } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir" + + +} # as_fn_mkdir_p +if mkdir -p . 2>/dev/null; then + as_mkdir_p='mkdir -p "$as_dir"' +else + test -d ./-p && rmdir ./-p + as_mkdir_p=false +fi + + +# as_fn_executable_p FILE +# ----------------------- +# Test if FILE is an executable regular file. +as_fn_executable_p () +{ + test -f "$1" && test -x "$1" +} # as_fn_executable_p +as_test_x='test -x' +as_executable_p=as_fn_executable_p + +# Sed expression to map a string onto a valid CPP name. +as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'" + +# Sed expression to map a string onto a valid variable name. +as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'" + + +exec 6>&1 +## ----------------------------------- ## +## Main body of $CONFIG_STATUS script. ## +## ----------------------------------- ## +_ASEOF +test $as_write_fail = 0 && chmod +x $CONFIG_STATUS || ac_write_fail=1 + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +# Save the log message, to keep $0 and so on meaningful, and to +# report actual input values of CONFIG_FILES etc. instead of their +# values after options handling. +ac_log=" +This file was extended by RtAudio $as_me 4.1, which was +generated by GNU Autoconf 2.69. Invocation command line was + + CONFIG_FILES = $CONFIG_FILES + CONFIG_HEADERS = $CONFIG_HEADERS + CONFIG_LINKS = $CONFIG_LINKS + CONFIG_COMMANDS = $CONFIG_COMMANDS + $ $0 $@ + +on `(hostname || uname -n) 2>/dev/null | sed 1q` +" + +_ACEOF + +case $ac_config_files in *" +"*) set x $ac_config_files; shift; ac_config_files=$*;; +esac + + + +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +# Files that config.status was made for. +config_files="$ac_config_files" + +_ACEOF + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +ac_cs_usage="\ +\`$as_me' instantiates files and other configuration actions +from templates according to the current configuration. Unless the files +and actions are specified as TAGs, all are instantiated by default. + +Usage: $0 [OPTION]... [TAG]... + + -h, --help print this help, then exit + -V, --version print version number and configuration settings, then exit + --config print configuration, then exit + -q, --quiet, --silent + do not print progress messages + -d, --debug don't remove temporary files + --recheck update $as_me by reconfiguring in the same conditions + --file=FILE[:TEMPLATE] + instantiate the configuration file FILE + +Configuration files: +$config_files + +Report bugs to ." + +_ACEOF +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" +ac_cs_version="\\ +RtAudio config.status 4.1 +configured by $0, generated by GNU Autoconf 2.69, + with options \\"\$ac_cs_config\\" + +Copyright (C) 2012 Free Software Foundation, Inc. +This config.status script is free software; the Free Software Foundation +gives unlimited permission to copy, distribute and modify it." + +ac_pwd='$ac_pwd' +srcdir='$srcdir' +test -n "\$AWK" || AWK=awk +_ACEOF + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +# The default lists apply if the user does not specify any file. +ac_need_defaults=: +while test $# != 0 +do + case $1 in + --*=?*) + ac_option=`expr "X$1" : 'X\([^=]*\)='` + ac_optarg=`expr "X$1" : 'X[^=]*=\(.*\)'` + ac_shift=: + ;; + --*=) + ac_option=`expr "X$1" : 'X\([^=]*\)='` + ac_optarg= + ac_shift=: + ;; + *) + ac_option=$1 + ac_optarg=$2 + ac_shift=shift + ;; + esac + + case $ac_option in + # Handling of the options. + -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r) + ac_cs_recheck=: ;; + --version | --versio | --versi | --vers | --ver | --ve | --v | -V ) + $as_echo "$ac_cs_version"; exit ;; + --config | --confi | --conf | --con | --co | --c ) + $as_echo "$ac_cs_config"; exit ;; + --debug | --debu | --deb | --de | --d | -d ) + debug=: ;; + --file | --fil | --fi | --f ) + $ac_shift + case $ac_optarg in + *\'*) ac_optarg=`$as_echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;; + '') as_fn_error $? "missing file argument" ;; + esac + as_fn_append CONFIG_FILES " '$ac_optarg'" + ac_need_defaults=false;; + --he | --h | --help | --hel | -h ) + $as_echo "$ac_cs_usage"; exit ;; + -q | -quiet | --quiet | --quie | --qui | --qu | --q \ + | -silent | --silent | --silen | --sile | --sil | --si | --s) + ac_cs_silent=: ;; + + # This is an error. + -*) as_fn_error $? "unrecognized option: \`$1' +Try \`$0 --help' for more information." ;; + + *) as_fn_append ac_config_targets " $1" + ac_need_defaults=false ;; + + esac + shift +done + +ac_configure_extra_args= + +if $ac_cs_silent; then + exec 6>/dev/null + ac_configure_extra_args="$ac_configure_extra_args --silent" +fi + +_ACEOF +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +if \$ac_cs_recheck; then + set X $SHELL '$0' $ac_configure_args \$ac_configure_extra_args --no-create --no-recursion + shift + \$as_echo "running CONFIG_SHELL=$SHELL \$*" >&6 + CONFIG_SHELL='$SHELL' + export CONFIG_SHELL + exec "\$@" +fi + +_ACEOF +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +exec 5>>config.log +{ + echo + sed 'h;s/./-/g;s/^.../## /;s/...$/ ##/;p;x;p;x' <<_ASBOX +## Running $as_me. ## +_ASBOX + $as_echo "$ac_log" +} >&5 + +_ACEOF +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +_ACEOF + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 + +# Handling of arguments. +for ac_config_target in $ac_config_targets +do + case $ac_config_target in + "rtaudio-config") CONFIG_FILES="$CONFIG_FILES rtaudio-config" ;; + "librtaudio.pc") CONFIG_FILES="$CONFIG_FILES librtaudio.pc" ;; + "Makefile") CONFIG_FILES="$CONFIG_FILES Makefile" ;; + "tests/Makefile") CONFIG_FILES="$CONFIG_FILES tests/Makefile" ;; + + *) as_fn_error $? "invalid argument: \`$ac_config_target'" "$LINENO" 5;; + esac +done + + +# If the user did not use the arguments to specify the items to instantiate, +# then the envvar interface is used. Set only those that are not. +# We use the long form for the default assignment because of an extremely +# bizarre bug on SunOS 4.1.3. +if $ac_need_defaults; then + test "${CONFIG_FILES+set}" = set || CONFIG_FILES=$config_files +fi + +# Have a temporary directory for convenience. Make it in the build tree +# simply because there is no reason against having it here, and in addition, +# creating and moving files from /tmp can sometimes cause problems. +# Hook for its removal unless debugging. +# Note that there is a small window in which the directory will not be cleaned: +# after its creation but before its name has been assigned to `$tmp'. +$debug || +{ + tmp= ac_tmp= + trap 'exit_status=$? + : "${ac_tmp:=$tmp}" + { test ! -d "$ac_tmp" || rm -fr "$ac_tmp"; } && exit $exit_status +' 0 + trap 'as_fn_exit 1' 1 2 13 15 +} +# Create a (secure) tmp directory for tmp files. + +{ + tmp=`(umask 077 && mktemp -d "./confXXXXXX") 2>/dev/null` && + test -d "$tmp" +} || +{ + tmp=./conf$$-$RANDOM + (umask 077 && mkdir "$tmp") +} || as_fn_error $? "cannot create a temporary directory in ." "$LINENO" 5 +ac_tmp=$tmp + +# Set up the scripts for CONFIG_FILES section. +# No need to generate them if there are no CONFIG_FILES. +# This happens for instance with `./config.status config.h'. +if test -n "$CONFIG_FILES"; then + + +ac_cr=`echo X | tr X '\015'` +# On cygwin, bash can eat \r inside `` if the user requested igncr. +# But we know of no other shell where ac_cr would be empty at this +# point, so we can use a bashism as a fallback. +if test "x$ac_cr" = x; then + eval ac_cr=\$\'\\r\' +fi +ac_cs_awk_cr=`$AWK 'BEGIN { print "a\rb" }' /dev/null` +if test "$ac_cs_awk_cr" = "a${ac_cr}b"; then + ac_cs_awk_cr='\\r' +else + ac_cs_awk_cr=$ac_cr +fi + +echo 'BEGIN {' >"$ac_tmp/subs1.awk" && +_ACEOF + + +{ + echo "cat >conf$$subs.awk <<_ACEOF" && + echo "$ac_subst_vars" | sed 's/.*/&!$&$ac_delim/' && + echo "_ACEOF" +} >conf$$subs.sh || + as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 +ac_delim_num=`echo "$ac_subst_vars" | grep -c '^'` +ac_delim='%!_!# ' +for ac_last_try in false false false false false :; do + . ./conf$$subs.sh || + as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 + + ac_delim_n=`sed -n "s/.*$ac_delim\$/X/p" conf$$subs.awk | grep -c X` + if test $ac_delim_n = $ac_delim_num; then + break + elif $ac_last_try; then + as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 + else + ac_delim="$ac_delim!$ac_delim _$ac_delim!! " + fi +done +rm -f conf$$subs.sh + +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +cat >>"\$ac_tmp/subs1.awk" <<\\_ACAWK && +_ACEOF +sed -n ' +h +s/^/S["/; s/!.*/"]=/ +p +g +s/^[^!]*!// +:repl +t repl +s/'"$ac_delim"'$// +t delim +:nl +h +s/\(.\{148\}\)..*/\1/ +t more1 +s/["\\]/\\&/g; s/^/"/; s/$/\\n"\\/ +p +n +b repl +:more1 +s/["\\]/\\&/g; s/^/"/; s/$/"\\/ +p +g +s/.\{148\}// +t nl +:delim +h +s/\(.\{148\}\)..*/\1/ +t more2 +s/["\\]/\\&/g; s/^/"/; s/$/"/ +p +b +:more2 +s/["\\]/\\&/g; s/^/"/; s/$/"\\/ +p +g +s/.\{148\}// +t delim +' >$CONFIG_STATUS || ac_write_fail=1 +rm -f conf$$subs.awk +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +_ACAWK +cat >>"\$ac_tmp/subs1.awk" <<_ACAWK && + for (key in S) S_is_set[key] = 1 + FS = "" + +} +{ + line = $ 0 + nfields = split(line, field, "@") + substed = 0 + len = length(field[1]) + for (i = 2; i < nfields; i++) { + key = field[i] + keylen = length(key) + if (S_is_set[key]) { + value = S[key] + line = substr(line, 1, len) "" value "" substr(line, len + keylen + 3) + len += length(value) + length(field[++i]) + substed = 1 + } else + len += 1 + keylen + } + + print line +} + +_ACAWK +_ACEOF +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +if sed "s/$ac_cr//" < /dev/null > /dev/null 2>&1; then + sed "s/$ac_cr\$//; s/$ac_cr/$ac_cs_awk_cr/g" +else + cat +fi < "$ac_tmp/subs1.awk" > "$ac_tmp/subs.awk" \ + || as_fn_error $? "could not setup config files machinery" "$LINENO" 5 +_ACEOF + +# VPATH may cause trouble with some makes, so we remove sole $(srcdir), +# ${srcdir} and @srcdir@ entries from VPATH if srcdir is ".", strip leading and +# trailing colons and then remove the whole line if VPATH becomes empty +# (actually we leave an empty line to preserve line numbers). +if test "x$srcdir" = x.; then + ac_vpsub='/^[ ]*VPATH[ ]*=[ ]*/{ +h +s/// +s/^/:/ +s/[ ]*$/:/ +s/:\$(srcdir):/:/g +s/:\${srcdir}:/:/g +s/:@srcdir@:/:/g +s/^:*// +s/:*$// +x +s/\(=[ ]*\).*/\1/ +G +s/\n// +s/^[^=]*=[ ]*$// +}' +fi + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +fi # test -n "$CONFIG_FILES" + + +eval set X " :F $CONFIG_FILES " +shift +for ac_tag +do + case $ac_tag in + :[FHLC]) ac_mode=$ac_tag; continue;; + esac + case $ac_mode$ac_tag in + :[FHL]*:*);; + :L* | :C*:*) as_fn_error $? "invalid tag \`$ac_tag'" "$LINENO" 5;; + :[FH]-) ac_tag=-:-;; + :[FH]*) ac_tag=$ac_tag:$ac_tag.in;; + esac + ac_save_IFS=$IFS + IFS=: + set x $ac_tag + IFS=$ac_save_IFS + shift + ac_file=$1 + shift + + case $ac_mode in + :L) ac_source=$1;; + :[FH]) + ac_file_inputs= + for ac_f + do + case $ac_f in + -) ac_f="$ac_tmp/stdin";; + *) # Look for the file first in the build tree, then in the source tree + # (if the path is not absolute). The absolute path cannot be DOS-style, + # because $ac_f cannot contain `:'. + test -f "$ac_f" || + case $ac_f in + [\\/$]*) false;; + *) test -f "$srcdir/$ac_f" && ac_f="$srcdir/$ac_f";; + esac || + as_fn_error 1 "cannot find input file: \`$ac_f'" "$LINENO" 5;; + esac + case $ac_f in *\'*) ac_f=`$as_echo "$ac_f" | sed "s/'/'\\\\\\\\''/g"`;; esac + as_fn_append ac_file_inputs " '$ac_f'" + done + + # Let's still pretend it is `configure' which instantiates (i.e., don't + # use $as_me), people would be surprised to read: + # /* config.h. Generated by config.status. */ + configure_input='Generated from '` + $as_echo "$*" | sed 's|^[^:]*/||;s|:[^:]*/|, |g' + `' by configure.' + if test x"$ac_file" != x-; then + configure_input="$ac_file. $configure_input" + { $as_echo "$as_me:${as_lineno-$LINENO}: creating $ac_file" >&5 +$as_echo "$as_me: creating $ac_file" >&6;} + fi + # Neutralize special characters interpreted by sed in replacement strings. + case $configure_input in #( + *\&* | *\|* | *\\* ) + ac_sed_conf_input=`$as_echo "$configure_input" | + sed 's/[\\\\&|]/\\\\&/g'`;; #( + *) ac_sed_conf_input=$configure_input;; + esac + + case $ac_tag in + *:-:* | *:-) cat >"$ac_tmp/stdin" \ + || as_fn_error $? "could not create $ac_file" "$LINENO" 5 ;; + esac + ;; + esac + + ac_dir=`$as_dirname -- "$ac_file" || +$as_expr X"$ac_file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$ac_file" : 'X\(//\)[^/]' \| \ + X"$ac_file" : 'X\(//\)$' \| \ + X"$ac_file" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X"$ac_file" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + as_dir="$ac_dir"; as_fn_mkdir_p + ac_builddir=. + +case "$ac_dir" in +.) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; +*) + ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'` + # A ".." for each directory in $ac_dir_suffix. + ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` + case $ac_top_builddir_sub in + "") ac_top_builddir_sub=. ac_top_build_prefix= ;; + *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; + esac ;; +esac +ac_abs_top_builddir=$ac_pwd +ac_abs_builddir=$ac_pwd$ac_dir_suffix +# for backward compatibility: +ac_top_builddir=$ac_top_build_prefix + +case $srcdir in + .) # We are building in place. + ac_srcdir=. + ac_top_srcdir=$ac_top_builddir_sub + ac_abs_top_srcdir=$ac_pwd ;; + [\\/]* | ?:[\\/]* ) # Absolute name. + ac_srcdir=$srcdir$ac_dir_suffix; + ac_top_srcdir=$srcdir + ac_abs_top_srcdir=$srcdir ;; + *) # Relative name. + ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix + ac_top_srcdir=$ac_top_build_prefix$srcdir + ac_abs_top_srcdir=$ac_pwd/$srcdir ;; +esac +ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix + + + case $ac_mode in + :F) + # + # CONFIG_FILE + # + +_ACEOF + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +# If the template does not know about datarootdir, expand it. +# FIXME: This hack should be removed a few years after 2.60. +ac_datarootdir_hack=; ac_datarootdir_seen= +ac_sed_dataroot=' +/datarootdir/ { + p + q +} +/@datadir@/p +/@docdir@/p +/@infodir@/p +/@localedir@/p +/@mandir@/p' +case `eval "sed -n \"\$ac_sed_dataroot\" $ac_file_inputs"` in +*datarootdir*) ac_datarootdir_seen=yes;; +*@datadir@*|*@docdir@*|*@infodir@*|*@localedir@*|*@mandir@*) + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&5 +$as_echo "$as_me: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&2;} +_ACEOF +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 + ac_datarootdir_hack=' + s&@datadir@&$datadir&g + s&@docdir@&$docdir&g + s&@infodir@&$infodir&g + s&@localedir@&$localedir&g + s&@mandir@&$mandir&g + s&\\\${datarootdir}&$datarootdir&g' ;; +esac +_ACEOF + +# Neutralize VPATH when `$srcdir' = `.'. +# Shell code in configure.ac might set extrasub. +# FIXME: do we really want to maintain this feature? +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +ac_sed_extra="$ac_vpsub +$extrasub +_ACEOF +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +:t +/@[a-zA-Z_][a-zA-Z_0-9]*@/!b +s|@configure_input@|$ac_sed_conf_input|;t t +s&@top_builddir@&$ac_top_builddir_sub&;t t +s&@top_build_prefix@&$ac_top_build_prefix&;t t +s&@srcdir@&$ac_srcdir&;t t +s&@abs_srcdir@&$ac_abs_srcdir&;t t +s&@top_srcdir@&$ac_top_srcdir&;t t +s&@abs_top_srcdir@&$ac_abs_top_srcdir&;t t +s&@builddir@&$ac_builddir&;t t +s&@abs_builddir@&$ac_abs_builddir&;t t +s&@abs_top_builddir@&$ac_abs_top_builddir&;t t +$ac_datarootdir_hack +" +eval sed \"\$ac_sed_extra\" "$ac_file_inputs" | $AWK -f "$ac_tmp/subs.awk" \ + >$ac_tmp/out || as_fn_error $? "could not create $ac_file" "$LINENO" 5 + +test -z "$ac_datarootdir_hack$ac_datarootdir_seen" && + { ac_out=`sed -n '/\${datarootdir}/p' "$ac_tmp/out"`; test -n "$ac_out"; } && + { ac_out=`sed -n '/^[ ]*datarootdir[ ]*:*=/p' \ + "$ac_tmp/out"`; test -z "$ac_out"; } && + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file contains a reference to the variable \`datarootdir' +which seems to be undefined. Please make sure it is defined" >&5 +$as_echo "$as_me: WARNING: $ac_file contains a reference to the variable \`datarootdir' +which seems to be undefined. Please make sure it is defined" >&2;} + + rm -f "$ac_tmp/stdin" + case $ac_file in + -) cat "$ac_tmp/out" && rm -f "$ac_tmp/out";; + *) rm -f "$ac_file" && mv "$ac_tmp/out" "$ac_file";; + esac \ + || as_fn_error $? "could not create $ac_file" "$LINENO" 5 + ;; + + + + esac + +done # for ac_tag + + +as_fn_exit 0 +_ACEOF +ac_clean_files=$ac_clean_files_save + +test $ac_write_fail = 0 || + as_fn_error $? "write failure creating $CONFIG_STATUS" "$LINENO" 5 + + +# configure is writing to config.log, and then calls config.status. +# config.status does its own redirection, appending to config.log. +# Unfortunately, on DOS this fails, as config.log is still kept open +# by configure, so config.status won't be able to write to it; its +# output is simply discarded. So we exec the FD to /dev/null, +# effectively closing config.log, so it can be properly (re)opened and +# appended to by config.status. When coming back to configure, we +# need to make the FD available again. +if test "$no_create" != yes; then + ac_cs_success=: + ac_config_status_args= + test "$silent" = yes && + ac_config_status_args="$ac_config_status_args --quiet" + exec 5>/dev/null + $SHELL $CONFIG_STATUS $ac_config_status_args || ac_cs_success=false + exec 5>>config.log + # Use ||, not &&, to avoid exiting from the if with $? = 1, which + # would make configure fail if this is the last instruction. + $ac_cs_success || as_fn_exit 1 +fi +if test -n "$ac_unrecognized_opts" && test "$enable_option_checking" != no; then + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: unrecognized options: $ac_unrecognized_opts" >&5 +$as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2;} +fi + + +chmod oug+x rtaudio-config diff --git a/src/rtaudio-mod/configure.ac b/src/rtaudio-mod/configure.ac new file mode 100644 index 0000000..fa45967 --- /dev/null +++ b/src/rtaudio-mod/configure.ac @@ -0,0 +1,197 @@ +# Process this file with autoconf to produce a configure script. +AC_INIT(RtAudio, 4.1, gary@music.mcgill.ca, rtaudio) +AC_CONFIG_AUX_DIR(config) +AC_CONFIG_SRCDIR(RtAudio.cpp) +AC_CONFIG_FILES([rtaudio-config librtaudio.pc Makefile tests/Makefile]) + +# Fill GXX with something before test. +AC_SUBST( GXX, ["no"] ) + +dnl Check for pkg-config program, used for configuring some libraries. +m4_define_default([PKG_PROG_PKG_CONFIG], +[AC_MSG_CHECKING([pkg-config]) +AC_MSG_RESULT([no])]) + +PKG_PROG_PKG_CONFIG + +dnl If the pkg-config autoconf support isn't installed, define its +dnl autoconf macro to disable any packages depending on it. +m4_define_default([PKG_CHECK_MODULES], +[AC_MSG_CHECKING([$1]) +AC_MSG_RESULT([no]) +$4]) + +# Checks for programs. +AC_PROG_CXX(g++ CC c++ cxx) +AC_PROG_RANLIB +AC_PATH_PROG(AR, ar, no) +if [[ $AR = "no" ]] ; then + AC_MSG_ERROR("Could not find ar - needed to create a library"); +fi + +# Checks for header files. +AC_HEADER_STDC +AC_CHECK_HEADERS(sys/ioctl.h unistd.h) + +# Check for debug +AC_MSG_CHECKING(whether to compile debug version) +AC_ARG_ENABLE(debug, + [ --enable-debug = enable various debug output], + [AC_SUBST( cppflag, [-D__RTAUDIO_DEBUG__] ) AC_SUBST( cxxflag, [-g] ) AC_SUBST( object_path, [Debug] ) AC_MSG_RESULT(yes)], + [AC_SUBST( cppflag, [] ) AC_SUBST( cxxflag, [-O2] ) AC_SUBST( object_path, [Release] ) AC_MSG_RESULT(no)]) + + +# Checks for functions +AC_CHECK_FUNC(gettimeofday, [cppflag="$cppflag -DHAVE_GETTIMEOFDAY"], ) + +# Set paths if prefix is defined +if test "x$prefix" != "x" && test "x$prefix" != "xNONE"; then + LIBS="$LIBS -L$prefix/lib" + CPPFLAGS="$CPPFLAGS -I$prefix/include" +fi + +# For -I and -D flags +CPPFLAGS="$CPPFLAGS $cppflag" + +# For debugging and optimization ... overwrite default because it has both -g and -O2 +#CXXFLAGS="$CXXFLAGS $cxxflag" +CXXFLAGS="$cxxflag" + +# Check compiler and use -Wall if gnu. +if [test $GXX = "yes" ;] then + AC_SUBST( cxxflag, ["-Wall -Wextra"] ) +fi + +CXXFLAGS="$CXXFLAGS $cxxflag" + +AC_CANONICAL_HOST + +AC_SUBST( sharedlib, ["librtaudio.so"] ) +AC_SUBST( sharedname, ["librtaudio.so.\$(RELEASE)"] ) +AC_SUBST( libflags, ["-shared -Wl,-soname,\$(SHARED).\$(MAJOR) -o \$(SHARED).\$(RELEASE)"] ) +case $host in + *-apple*) + AC_SUBST( sharedlib, ["librtaudio.dylib"] ) + AC_SUBST( sharedname, ["librtaudio.\$(RELEASE).dylib"] ) + AC_SUBST( libflags, ["-dynamiclib -o librtaudio.\$(RELEASE).dylib"] ) +esac + +# Checks for package options and external software +AC_SUBST( api, [""] ) +AC_SUBST( req, [""] ) +AC_MSG_CHECKING(for audio API) +case $host in + *-*-netbsd*) + AC_MSG_RESULT(using OSS) + api="$api -D__LINUX_OSS__" + LIBS="$LIBS -lossaudio" + AC_CHECK_LIB(pthread, pthread_create, , AC_MSG_ERROR(RtAudio requires the pthread library!)) + ;; + + *-*-linux*) + AC_ARG_WITH(jack, [ --with-jack = choose JACK server support (mac and linux only)], [ + api="$api -D__UNIX_JACK__" + AC_MSG_RESULT(using JACK) + AC_CHECK_LIB(jack, jack_client_open, , AC_MSG_ERROR(JACK support requires the jack library!)) + AC_CHECK_LIB(asound, snd_pcm_open, , AC_MSG_ERROR(Jack support also requires the asound library!))], ) + + # Look for ALSA flag + AC_ARG_WITH(alsa, [ --with-alsa = choose native ALSA API support (linux only)], [ + api="$api -D__LINUX_ALSA__" + req="$req alsa" + AC_MSG_RESULT(using ALSA) + AC_CHECK_LIB(asound, snd_pcm_open, , AC_MSG_ERROR(ALSA support requires the asound library!))], ) + + # Look for PULSE flag + AC_ARG_WITH(pulse, [ --with-pulse = choose PulseAudio API support (linux only)], [ + api="$api -D__LINUX_PULSE__" + req="$req libpulse-simple" + AC_MSG_RESULT(using PulseAudio) + PKG_CHECK_MODULES([PULSE], [libpulse-simple], , AC_MSG_ERROR(PulseAudio support requires the pulse-simple library!)) + LIBS="$LIBS `pkg-config --libs libpulse-simple`" ], ) + + # Look for OSS flag + AC_ARG_WITH(oss, [ --with-oss = choose OSS API support (linux only)], [ + api="$api -D__LINUX_OSS__" + AC_MSG_RESULT(using OSS)], ) + + # If no audio api flags specified, use ALSA + if [test "$api" == "";] then + AC_MSG_RESULT(using ALSA) + AC_SUBST( api, [-D__LINUX_ALSA__] ) + req="$req alsa" + AC_CHECK_LIB(asound, snd_pcm_open, , AC_MSG_ERROR(ALSA support requires the asound library!)) + fi + + AC_CHECK_LIB(pthread, pthread_create, , AC_MSG_ERROR(RtAudio requires the pthread library!)) + ;; + + *-apple*) + AC_ARG_WITH(jack, [ --with-jack = choose JACK server support (unix only)], [ + api="$api -D__UNIX_JACK__" + AC_MSG_RESULT(using JACK) + AC_CHECK_LIB(jack, jack_client_open, , AC_MSG_ERROR(JACK support requires the jack library!))], ) + +# AC_CHECK_HEADER(jack/jack.h, [], [AC_MSG_ERROR(Jack header file not found!)] ) +# LIBS="$LIBS -framework jackmp" ], ) + + + # Look for Core flag + AC_ARG_WITH(core, [ --with-core = choose CoreAudio API support (mac only)], [ + api="$api -D__MACOSX_CORE__" + AC_MSG_RESULT(using CoreAudio) + AC_CHECK_HEADER(CoreAudio/CoreAudio.h, [], [AC_MSG_ERROR(CoreAudio header files not found!)] ) + LIBS="$LIBS -framework CoreAudio -framework CoreFoundation" ], ) + + # If no audio api flags specified, use CoreAudio + if [test "$api" == ""; ] then + AC_SUBST( api, [-D__MACOSX_CORE__] ) + AC_MSG_RESULT(using CoreAudio) + AC_CHECK_HEADER(CoreAudio/CoreAudio.h, + [], + [AC_MSG_ERROR(CoreAudio header files not found!)] ) + AC_SUBST( LIBS, ["-framework CoreAudio -framework CoreFoundation"] ) + fi + + AC_CHECK_LIB(pthread, pthread_create, , AC_MSG_ERROR(RtAudio requires the pthread library!)) + ;; + + *-mingw32*) + AC_ARG_WITH(asio, [ --with-asio = choose ASIO API support (windoze only)], [ + api="$api -D__WINDOWS_ASIO__" + AC_MSG_RESULT(using ASIO) + AC_SUBST( objects, ["asio.o asiodrivers.o asiolist.o iasiothiscallresolver.o"] ) ], ) + + # Look for DirectSound flag + AC_ARG_WITH(ds, [ --with-ds = choose DirectSound API support (windoze only)], [ + api="$api -D__WINDOWS_DS__" + AC_MSG_RESULT(using DirectSound) + LIBS="-ldsound -lwinmm $LIBS" ], ) + + # Look for WASAPI flag + AC_ARG_WITH(wasapi, [ --with-wasapi = choose Windows Audio Session API support (windoze only)], [ + api="$api -D__WINDOWS_WASAPI__" + AC_MSG_RESULT(using WASAPI) + LIBS="-lwinmm -luuid -lksuser $LIBS" ], ) + + # If no audio api flags specified, use DS + if [test "$api" == "";] then + AC_SUBST( api, [-D__WINDOWS_DS__] ) + AC_MSG_RESULT(using DirectSound) + LIBS="-ldsound -lwinmm $LIBS" + fi + + LIBS="-lole32 $LIBS" + ;; + + *) + # Default case for unknown realtime systems. + AC_MSG_ERROR(Unknown system type for realtime support!) + ;; +esac + +CPPFLAGS="$CPPFLAGS $api" + +AC_OUTPUT + +chmod oug+x rtaudio-config diff --git a/src/rtaudio-mod/librtaudio.pc.in b/src/rtaudio-mod/librtaudio.pc.in new file mode 100644 index 0000000..45c3f4e --- /dev/null +++ b/src/rtaudio-mod/librtaudio.pc.in @@ -0,0 +1,12 @@ +prefix=@prefix@ +exec_prefix=${prefix} +libdir=${exec_prefix}/lib +includedir=${prefix}/include + +Name: librtaudio +Description: RtAudio - a set of C++ classes that provide a common API for realtime audio input/output +Version: 4.1.1 +Requires: @req@ +Libs: -L${libdir} -lrtaudio +Libs.private: -lpthread +Cflags: -pthread -I${includedir} @CPPFLAGS@ \ No newline at end of file diff --git a/src/rtaudio-mod/rtaudio-config.in b/src/rtaudio-mod/rtaudio-config.in new file mode 100644 index 0000000..5f83d51 --- /dev/null +++ b/src/rtaudio-mod/rtaudio-config.in @@ -0,0 +1,19 @@ +#! /bin/sh +if (test "x$#" != "x1") ; then + echo "Usage: $0 [--libs | --cxxflags | --cppflags]" + exit; +fi + +LIBRARY="@LIBS@" +CXXFLAGS="@CXXFLAGS@" +CPPFLAGS="@CPPFLAGS@" + +if (test "x$1" = "x--libs") ; then + echo "$LIBRARY -lrtaudio" +elif (test "x$1" = "x--cxxflags") ; then + echo "$CXXFLAGS" +elif (test "x$1" = "x--cppflags") ; then + echo "$CPPFLAGS" +else + echo "Unknown option: $1" +fi diff --git a/src/sampleChannel.cpp b/src/sampleChannel.cpp new file mode 100644 index 0000000..300db28 --- /dev/null +++ b/src/sampleChannel.cpp @@ -0,0 +1,993 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * channel + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + + +#include +#include "sampleChannel.h" +#include "patch.h" +#include "conf.h" +#include "wave.h" +#include "pluginHost.h" +#include "waveFx.h" +#include "mixerHandler.h" +#include "log.h" + + +extern Patch G_Patch; +extern Mixer G_Mixer; +extern Conf G_Conf; +#ifdef WITH_VST +extern PluginHost G_PluginHost; +#endif + + +SampleChannel::SampleChannel(int bufferSize) + : Channel (CHANNEL_SAMPLE, STATUS_EMPTY, bufferSize), + frameRewind (-1), + wave (NULL), + tracker (0), + begin (0), + end (0), + pitch (gDEFAULT_PITCH), + boost (1.0f), + mode (DEFAULT_CHANMODE), + qWait (false), + fadeinOn (false), + fadeinVol (1.0f), + fadeoutOn (false), + fadeoutVol (1.0f), + fadeoutTracker (0), + fadeoutStep (DEFAULT_FADEOUT_STEP), + readActions (true), + midiInReadActions(0x0), + midiInPitch (0x0) +{ + rsmp_state = src_new(SRC_LINEAR, 2, NULL); + pChan = (float *) malloc(kernelAudio::realBufsize * 2 * sizeof(float)); +} + + +/* ------------------------------------------------------------------ */ + + +SampleChannel::~SampleChannel() { + if (wave) + delete wave; + src_delete(rsmp_state); + free(pChan); +} + + +/* ------------------------------------------------------------------ */ + + +void SampleChannel::clear() { + + /** TODO - these memsets can be done only if status PLAY (if below), + * but it would require extra clearPChan calls when samples stop */ + + memset(vChan, 0, sizeof(float) * bufferSize); + memset(pChan, 0, sizeof(float) * bufferSize); + + if (status & (STATUS_PLAY | STATUS_ENDING)) { + tracker = fillChan(vChan, tracker, 0); + if (fadeoutOn && fadeoutType == XFADE) { + gLog("[clear] filling pChan fadeoutTracker=%d\n", fadeoutTracker); + fadeoutTracker = fillChan(pChan, fadeoutTracker, 0); + } + } +} + + +/* ------------------------------------------------------------------ */ + + +void SampleChannel::calcVolumeEnv(int frame) { + + /* method: check this frame && next frame, then calculate delta */ + + recorder::action *a0 = NULL; + recorder::action *a1 = NULL; + int res; + + /* get this action on frame 'frame'. It's unlikely that the action + * is not found. */ + + res = recorder::getAction(index, ACTION_VOLUME, frame, &a0); + if (res == 0) + return; + + /* get the action next to this one. + * res == -1: a1 not found, this is the last one. Rewind the search + * and use action at frame number 0 (actions[0]). + * res == -2 ACTION_VOLUME not found. This should never happen */ + + res = recorder::getNextAction(index, ACTION_VOLUME, frame, &a1); + + if (res == -1) + res = recorder::getAction(index, ACTION_VOLUME, 0, &a1); + + volume_i = a0->fValue; + volume_d = ((a1->fValue - a0->fValue) / ((a1->frame - a0->frame) / 2)) * 1.003f; +} + + +/* ------------------------------------------------------------------ */ + + +void SampleChannel::hardStop(int frame) { + if (frame != 0) // clear data in range [frame, bufferSize-1] + clearChan(vChan, frame); + status = STATUS_OFF; + reset(frame); +} + + +/* ------------------------------------------------------------------ */ + + +void SampleChannel::onBar(int frame) { + ///if (mode == LOOP_REPEAT && status == STATUS_PLAY) + /// //setXFade(frame); + /// reset(frame); + + if (mode == LOOP_REPEAT) { + if (status == STATUS_PLAY) + //setXFade(frame); + reset(frame); + } + else + if (mode == LOOP_ONCE_BAR) { + if (status == STATUS_WAIT) { + status = STATUS_PLAY; + tracker = fillChan(vChan, tracker, frame); + } + } +} + + +/* ------------------------------------------------------------------ */ + + +int SampleChannel::save(const char *path) { + return wave->writeData(path); +} + + +/* ------------------------------------------------------------------ */ + + +void SampleChannel::setBegin(unsigned v) { + begin = v; + tracker = begin; +} + + +/* ------------------------------------------------------------------ */ + + +void SampleChannel::setEnd(unsigned v) { + end = v; +} + + +/* ------------------------------------------------------------------ */ + + +void SampleChannel::setPitch(float v) { + + pitch = v; + rsmp_data.src_ratio = 1/pitch; + + /* if status is off don't slide between frequencies */ + + if (status & (STATUS_OFF | STATUS_WAIT)) + src_set_ratio(rsmp_state, 1/pitch); +} + + +/* ------------------------------------------------------------------ */ + + +void SampleChannel::rewind() { + + /* rewind LOOP_ANY or SINGLE_ANY only if it's in read-record-mode */ + + if (wave != NULL) { + if ((mode & LOOP_ANY) || (recStatus == REC_READING && (mode & SINGLE_ANY))) + reset(0); // rewind is user-generated events, always on frame 0 + } +} + + +/* ------------------------------------------------------------------ */ + + +void SampleChannel::parseAction(recorder::action *a, int localFrame, int globalFrame) { + + if (readActions == false) + return; + + switch (a->type) { + case ACTION_KEYPRESS: + if (mode & SINGLE_ANY) + start(localFrame, false); + break; + case ACTION_KEYREL: + if (mode & SINGLE_ANY) + stop(); + break; + case ACTION_KILLCHAN: + if (mode & SINGLE_ANY) + kill(localFrame); + break; + case ACTION_MUTEON: + setMute(true); // internal mute + break; + case ACTION_MUTEOFF: + unsetMute(true); // internal mute + break; + case ACTION_VOLUME: + calcVolumeEnv(globalFrame); + break; + } +} + + +/* ------------------------------------------------------------------ */ + + +void SampleChannel::sum(int frame, bool running) { + + if (wave == NULL || status & ~(STATUS_PLAY | STATUS_ENDING)) + return; + + if (frame != frameRewind) { + + /* volume envelope, only if seq is running */ + + if (running) { + volume_i += volume_d; + if (volume_i < 0.0f) + volume_i = 0.0f; + else + if (volume_i > 1.0f) + volume_i = 1.0f; + } + + /* fadein or fadeout processes. If mute, delete any signal. */ + + /** TODO - big issue: fade[in/out]Vol * internal_volume might be a + * bad choice: it causes glitches when muting on and off during a + * volume envelope. */ + + if (mute || mute_i) { + vChan[frame] = 0.0f; + vChan[frame+1] = 0.0f; + } + else + if (fadeinOn) { + if (fadeinVol < 1.0f) { + vChan[frame] *= fadeinVol * volume_i; + vChan[frame+1] *= fadeinVol * volume_i; + fadeinVol += 0.01f; + } + else { + fadeinOn = false; + fadeinVol = 0.0f; + } + } + else + if (fadeoutOn) { + if (fadeoutVol > 0.0f) { // fadeout ongoing + if (fadeoutType == XFADE) { + vChan[frame] *= volume_i; + vChan[frame+1] *= volume_i; + vChan[frame] = pChan[frame] * fadeoutVol * volume_i; + vChan[frame+1] = pChan[frame+1] * fadeoutVol * volume_i; + } + else { + vChan[frame] *= fadeoutVol * volume_i; + vChan[frame+1] *= fadeoutVol * volume_i; + } + fadeoutVol -= fadeoutStep; + } + else { // fadeout end + fadeoutOn = false; + fadeoutVol = 1.0f; + + /* QWait ends with the end of the xfade */ + + if (fadeoutType == XFADE) { + qWait = false; + } + else { + if (fadeoutEnd == DO_MUTE) + mute = true; + else + if (fadeoutEnd == DO_MUTE_I) + mute_i = true; + else // DO_STOP + hardStop(frame); + } + } + } + else { + vChan[frame] *= volume_i; + vChan[frame+1] *= volume_i; + } + } + else { + + if (mode & (SINGLE_BASIC | SINGLE_PRESS | SINGLE_RETRIG) || + (mode == SINGLE_ENDLESS && status == STATUS_ENDING) || + (mode & LOOP_ANY && !running)) // stop loops when the seq is off + { + status = STATUS_OFF; + } + + /* temporary stop LOOP_ONCE not in ENDING status, otherwise they + * would return in wait, losing the ENDING status */ + + //if (mode == LOOP_ONCE && status != STATUS_ENDING) + if ((mode & (LOOP_ONCE | LOOP_ONCE_BAR)) && status != STATUS_ENDING) + status = STATUS_WAIT; + + /* check for end of samples. SINGLE_ENDLESS runs forever unless + * it's in ENDING mode. */ + + reset(frame); + } +} + + +/* ------------------------------------------------------------------ */ + + +void SampleChannel::onZero(int frame) { + + if (wave == NULL) + return; + + if (mode & LOOP_ANY) { + + /* do a crossfade if the sample is playing. Regular chanReset + * instead if it's muted, otherwise a click occurs */ + + if (status == STATUS_PLAY) { + /* + if (mute || mute_i) + reset(frame); + else + setXFade(frame); + */ + reset(frame); + } + else + if (status == STATUS_ENDING) + hardStop(frame); + } + + if (status == STATUS_WAIT) { /// FIXME - should be inside previous if! + status = STATUS_PLAY; + tracker = fillChan(vChan, tracker, frame); + } + + if (recStatus == REC_ENDING) { + recStatus = REC_STOPPED; + setReadActions(false); // rec stop + } + else + if (recStatus == REC_WAITING) { + recStatus = REC_READING; + setReadActions(true); // rec start + } +} + + +/* ------------------------------------------------------------------ */ + + +void SampleChannel::quantize(int index, int localFrame, int globalFrame) { + + /* skip if LOOP_ANY or not in quantizer-wait mode */ + + if ((mode & LOOP_ANY) || !qWait) + return; + + /* no fadeout if the sample starts for the first time (from a + * STATUS_OFF), it would be meaningless. */ + + if (status == STATUS_OFF) { + status = STATUS_PLAY; + qWait = false; + tracker = fillChan(vChan, tracker, localFrame); /// FIXME: ??? + } + else + //setXFade(localFrame); + reset(localFrame); + + /* this is the moment in which we record the keypress, if the + * quantizer is on. SINGLE_PRESS needs overdub */ + + if (recorder::canRec(this)) { + if (mode == SINGLE_PRESS) + recorder::startOverdub(index, ACTION_KEYS, globalFrame); + else + recorder::rec(index, ACTION_KEYPRESS, globalFrame); + } +} + + +/* ------------------------------------------------------------------ */ + + +int SampleChannel::getPosition() { + if (status & ~(STATUS_EMPTY | STATUS_MISSING | STATUS_OFF)) // if is not (...) + return tracker - begin; + else + return -1; +} + + +/* ------------------------------------------------------------------ */ + + +void SampleChannel::setMute(bool internal) { + + if (internal) { + + /* global mute is on? don't waste time with fadeout, just mute it + * internally */ + + if (mute) + mute_i = true; + else { + if (isPlaying()) + setFadeOut(DO_MUTE_I); + else + mute_i = true; + } + } + else { + + /* internal mute is on? don't waste time with fadeout, just mute it + * globally */ + + if (mute_i) + mute = true; + else { + + /* sample in play? fadeout needed. Else, just mute it globally */ + + if (isPlaying()) + setFadeOut(DO_MUTE); + else + mute = true; + } + } +} + + +/* ------------------------------------------------------------------ */ + + +void SampleChannel::unsetMute(bool internal) { + if (internal) { + if (mute) + mute_i = false; + else { + if (isPlaying()) + setFadeIn(internal); + else + mute_i = false; + } + } + else { + if (mute_i) + mute = false; + else { + if (isPlaying()) + setFadeIn(internal); + else + mute = false; + } + } +} + + +/* ------------------------------------------------------------------ */ + + +void SampleChannel::calcFadeoutStep() { + if (end - tracker < (1 / DEFAULT_FADEOUT_STEP) * 2) + fadeoutStep = ceil((end - tracker) / volume) * 2; /// or volume_i ??? + else + fadeoutStep = DEFAULT_FADEOUT_STEP; +} + + +/* ------------------------------------------------------------------ */ + + +void SampleChannel::setReadActions(bool v) { + if (v) + readActions = true; + else { + readActions = false; + if (G_Conf.recsStopOnChanHalt) + kill(0); /// FIXME - wrong frame value + } +} + + +/* ------------------------------------------------------------------ */ + + +void SampleChannel::setFadeIn(bool internal) { + if (internal) mute_i = false; // remove mute before fading in + else mute = false; + fadeinOn = true; + fadeinVol = 0.0f; +} + + +/* ------------------------------------------------------------------ */ + + +void SampleChannel::setFadeOut(int actionPostFadeout) { + calcFadeoutStep(); + fadeoutOn = true; + fadeoutVol = 1.0f; + fadeoutType = FADEOUT; + fadeoutEnd = actionPostFadeout; +} + + +/* ------------------------------------------------------------------ */ + + +void SampleChannel::setXFade(int frame) { + + gLog("[xFade] frame=%d tracker=%d\n", frame, tracker); + + calcFadeoutStep(); + fadeoutOn = true; + fadeoutVol = 1.0f; + fadeoutType = XFADE; + fadeoutTracker = fillChan(pChan, tracker, 0, false); + reset(frame); +} + + +/* ------------------------------------------------------------------ */ + + +/* on reset, if frame > 0 and in play, fill again pChan to create + * something like this: + * + * |abcdefabcdefab*abcdefabcde| + * [old data-----]*[new data--] + * + * */ + +void SampleChannel::reset(int frame) { + //fadeoutTracker = tracker; // store old frame number for xfade + tracker = begin; + mute_i = false; + if (frame > 0 && status & (STATUS_PLAY | STATUS_ENDING)) + tracker = fillChan(vChan, tracker, frame); +} + + +/* ------------------------------------------------------------------ */ + + +void SampleChannel::empty() { + status = STATUS_OFF; + if (wave) { + delete wave; + wave = NULL; + } + status = STATUS_EMPTY; +} + + +/* ------------------------------------------------------------------ */ + + +void SampleChannel::pushWave(Wave *w) { + wave = w; + status = STATUS_OFF; + begin = 0; + end = wave->size; +} + + +/* ------------------------------------------------------------------ */ + + +bool SampleChannel::allocEmpty(int frames, int takeId) { + + Wave *w = new Wave(); + if (!w->allocEmpty(frames)) + return false; + + char wname[32]; + sprintf(wname, "TAKE-%d", takeId); + + w->pathfile = gGetCurrentPath()+"/"+wname; // FIXME - use gGetSlash() in utils.h + w->name = wname; + wave = w; + status = STATUS_OFF; + begin = 0; + end = wave->size; + + return true; +} + + +/* ------------------------------------------------------------------ */ + + +void SampleChannel::process(float *buffer) { + +#ifdef WITH_VST + G_PluginHost.processStack(vChan, PluginHost::CHANNEL, this); +#endif + + for (int j=0; j FILENAME_MAX) + return SAMPLE_PATH_TOO_LONG; + + Wave *w = new Wave(); + + if (!w->open(file)) { + gLog("[SampleChannel] %s: read error\n", file); + delete w; + return SAMPLE_READ_ERROR; + } + + if (w->channels() > 2) { + gLog("[SampleChannel] %s: unsupported multichannel wave\n", file); + delete w; + return SAMPLE_MULTICHANNEL; + } + + if (!w->readData()) { + delete w; + return SAMPLE_READ_ERROR; + } + + if (w->channels() == 1) /** FIXME: error checking */ + wfx_monoToStereo(w); + + if (w->rate() != G_Conf.samplerate) { + gLog("[SampleChannel] input rate (%d) != system rate (%d), conversion needed\n", + w->rate(), G_Conf.samplerate); + w->resample(G_Conf.rsmpQuality, G_Conf.samplerate); + } + + pushWave(w); + + /* sample name must be unique. Start from k = 1, zero is too nerdy */ + + std::string oldName = wave->name; + int k = 1; + while (!mh_uniqueSamplename(this, wave->name.c_str())) { + wave->updateName((oldName + "-" + gItoa(k)).c_str()); + k++; + } + + gLog("[SampleChannel] %s loaded in channel %d\n", file, index); + return SAMPLE_LOADED_OK; +} + + +/* ------------------------------------------------------------------ */ + + +int SampleChannel::loadByPatch(const char *f, int i) { + + int res = load(f); + + volume = G_Patch.getVol(i); + key = G_Patch.getKey(i); + index = G_Patch.getIndex(i); + mode = G_Patch.getMode(i); + mute = G_Patch.getMute(i); + mute_s = G_Patch.getMute_s(i); + solo = G_Patch.getSolo(i); + boost = G_Patch.getBoost(i); + panLeft = G_Patch.getPanLeft(i); + panRight = G_Patch.getPanRight(i); + readActions = G_Patch.getRecActive(i); + recStatus = readActions ? REC_READING : REC_STOPPED; + + readPatchMidiIn(i); + midiInReadActions = G_Patch.getMidiValue(i, "InReadActions"); + midiInPitch = G_Patch.getMidiValue(i, "InPitch"); + + if (res == SAMPLE_LOADED_OK) { + setBegin(G_Patch.getBegin(i)); + setEnd (G_Patch.getEnd(i, wave->size)); + setPitch(G_Patch.getPitch(i)); + } + else { + // volume = DEFAULT_VOL; + // mode = DEFAULT_CHANMODE; + // status = STATUS_WRONG; + // key = 0; + + if (res == SAMPLE_LEFT_EMPTY) + status = STATUS_EMPTY; + else + if (res == SAMPLE_READ_ERROR) + status = STATUS_MISSING; + } + + return res; +} + + +/* ------------------------------------------------------------------ */ + + +bool SampleChannel::canInputRec() { + return wave == NULL; +} + + +/* ------------------------------------------------------------------ */ + + +void SampleChannel::start(int frame, bool doQuantize) { + + switch (status) { + case STATUS_EMPTY: + case STATUS_MISSING: + case STATUS_WRONG: + { + return; + } + + case STATUS_OFF: + { + if (mode & LOOP_ANY) { + status = STATUS_WAIT; + } + else { + if (G_Mixer.quantize > 0 && G_Mixer.running && doQuantize) + qWait = true; + else { + + /* fillChan only if frame != 0. If you call fillChan on frame == 0 + * a duplicate call to fillChan occurs with loss of data. */ + + status = STATUS_PLAY; + if (frame != 0) + tracker = fillChan(vChan, tracker, frame); + } + } + break; + } + + case STATUS_PLAY: + { + if (mode == SINGLE_BASIC) + setFadeOut(DO_STOP); + else + if (mode == SINGLE_RETRIG) { + if (G_Mixer.quantize > 0 && G_Mixer.running && doQuantize) + qWait = true; + else + reset(frame); + } + else + if (mode & (LOOP_ANY | SINGLE_ENDLESS)) + status = STATUS_ENDING; + break; + } + + case STATUS_WAIT: + { + status = STATUS_OFF; + break; + } + + case STATUS_ENDING: + { + status = STATUS_PLAY; + break; + } + } +} + + +/* ------------------------------------------------------------------ */ + + +void SampleChannel::writePatch(FILE *fp, int i, bool isProject) { + + Channel::writePatch(fp, i, isProject); + + const char *path = ""; + if (wave != NULL) { + path = wave->pathfile.c_str(); + if (isProject) + path = gBasename(path).c_str(); // make it portable + } + + fprintf(fp, "samplepath%d=%s\n", i, path); + fprintf(fp, "chanKey%d=%d\n", i, key); + //fprintf(fp, "columnIndex%d=%d\n", i, index); + fprintf(fp, "chanmode%d=%d\n", i, mode); + fprintf(fp, "chanBegin%d=%d\n", i, begin); + fprintf(fp, "chanend%d=%d\n", i, end); + fprintf(fp, "chanBoost%d=%f\n", i, boost); + fprintf(fp, "chanRecActive%d=%d\n", i, readActions); + fprintf(fp, "chanPitch%d=%f\n", i, pitch); + + fprintf(fp, "chanMidiInReadActions%d=%u\n", i, midiInReadActions); + fprintf(fp, "chanMidiInPitch%d=%u\n", i, midiInPitch); +} + + +/* ------------------------------------------------------------------ */ + + +void SampleChannel::clearChan(float *dest, int start) { + memset(dest+start, 0, sizeof(float)*(bufferSize-start)); +} + + +/* ------------------------------------------------------------------ */ + + +int SampleChannel::fillChan(float *dest, int start, int offset, bool rewind) { + + int position; // return value: the new position + + if (pitch == 1.0f) { + + /* case 1: 'dest' lies within the original sample boundaries (start- + * end) */ + + if (start+bufferSize-offset <= end) { + memcpy(dest+offset, wave->data+start, (bufferSize-offset)*sizeof(float)); + position = start+bufferSize-offset; + if (rewind) + frameRewind = -1; + } + + /* case2: 'dest' lies outside the end of the sample, OR the sample + * is smaller than 'dest' */ + + else { + memcpy(dest+offset, wave->data+start, (end-start)*sizeof(float)); + position = end; + if (rewind) + frameRewind = end-start+offset; + } + } + else { + + rsmp_data.data_in = wave->data+start; // source data + rsmp_data.input_frames = (end-start)/2; // how many readable bytes + rsmp_data.data_out = dest+offset; // destination (processed data) + rsmp_data.output_frames = (bufferSize-offset)/2; // how many bytes to process + rsmp_data.end_of_input = false; + + src_process(rsmp_state, &rsmp_data); + int gen = rsmp_data.output_frames_gen*2; // frames generated by this call + + position = start + rsmp_data.input_frames_used*2; // position goes forward of frames_used (i.e. read from wave) + + if (rewind) { + if (gen == bufferSize-offset) + frameRewind = -1; + else + frameRewind = gen+offset; + } + } + return position; +} diff --git a/src/sampleChannel.h b/src/sampleChannel.h new file mode 100644 index 0000000..736063a --- /dev/null +++ b/src/sampleChannel.h @@ -0,0 +1,210 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * sampleChannel + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + + +#ifndef SAMPLE_CHANNEL_H +#define SAMPLE_CHANNEL_H + + +#include +#include "channel.h" + + +class SampleChannel : public Channel { + +private: + + /* rsmp_state, rsmp_data + * structs from libsamplerate */ + + SRC_STATE *rsmp_state; + SRC_DATA rsmp_data; + + /* pChan + * extra virtual channel for processing resampled data. */ + + float *pChan; + + /* frameRewind + * exact frame in which a rewind occurs */ + + int frameRewind; + + /* fillChan + * copy from wave to *dest and resample data from wave, if necessary. + * Start to fill pChan from byte 'offset'. If rewind=false don't + * rewind internal tracker. Returns new sample position, in frames */ + + int fillChan(float *dest, int start, int offset, bool rewind=true); + + /* clearChan + * set data to zero from start to bufferSize-1. */ + + void clearChan(float *dest, int start); + + /* calcFadeoutStep + * how many frames are left before the end of the sample? Is there + * enough room for a complete fadeout? Should we shorten it? */ + + void calcFadeoutStep(); + + /* calcVolumeEnv + * compute any changes in volume done via envelope tool */ + + void calcVolumeEnv(int frame); + +public: + + SampleChannel(int bufferSize); + ~SampleChannel(); + + void clear (); + void process (float *buffer); + void start (int frame, bool doQuantize); + void kill (int frame); + void empty (); + void stopBySeq (); + void stop (); + void rewind (); + void setMute (bool internal); + void unsetMute (bool internal); + void reset (int frame); + int load (const char *file); + int loadByPatch(const char *file, int i); + void writePatch (FILE *fp, int i, bool isProject); + void quantize (int index, int localFrame, int globalFrame); + void onZero (int frame); + void onBar (int frame); + void parseAction(recorder::action *a, int localFrame, int globalFrame); + + /* fade methods + * prepare channel for fade, mixer will take care of the process + * during master play. */ + + void setFadeIn (bool internal); + void setFadeOut (int actionPostFadeout); + void setXFade (int frame); + + /* pushWave + * add a new wave to an existing channel. */ + + void pushWave(class Wave *w); + + /* getPosition + * returns the position of an active sample. If EMPTY o MISSING + * returns -1. */ + + int getPosition(); + + /* sum + * add sample frames to virtual channel. Frame = processed frame in + * Mixer. Running = is Mixer in play? */ + + void sum(int frame, bool running); + + /* setPitch + * updates the pitch value and chanStart+chanEnd accordingly. */ + + void setPitch(float v); + + /* setStart/end + * change begin/end read points in sample. */ + + void setBegin(unsigned v); + void setEnd (unsigned v); + + /* save + * save sample to file. */ + + int save(const char *path); + + /* hardStop + * stop the channel immediately, no further checks. */ + + void hardStop(int frame); + + /* allocEmpty + * alloc an empty wave used in input recordings. */ + + bool allocEmpty(int frames, int takeId); + + /* canInputRec + * true if channel can host a new wave from input recording. */ + + bool canInputRec(); + + /* setReadActions + * if enabled, recorder will read actions from this channel */ + + void setReadActions(bool v); + + /* ---------------------------------------------------------------- */ + + class Wave *wave; + int tracker; // chan position + int begin; + int end; + float pitch; + float boost; + int mode; // mode: see const.h + bool qWait; // quantizer wait + bool fadeinOn; + float fadeinVol; + bool fadeoutOn; + float fadeoutVol; // fadeout volume + int fadeoutTracker; // tracker fadeout, xfade only + float fadeoutStep; // fadeout decrease + int fadeoutType; // xfade or fadeout + int fadeoutEnd; // what to do when fadeout ends + + /* recorder:: stuff */ + + bool readActions; // read actions or not + + /* midi stuff */ + + uint32_t midiInReadActions; + uint32_t midiInPitch; + + /* const - what to do when a fadeout ends */ + + enum { + DO_STOP = 0x01, + DO_MUTE = 0x02, + DO_MUTE_I = 0x04 + }; + + /* const - fade types */ + + enum { + FADEOUT = 0x01, + XFADE = 0x02 + }; +}; + +#endif diff --git a/src/utils.cpp b/src/utils.cpp new file mode 100644 index 0000000..e7b2e8c --- /dev/null +++ b/src/utils.cpp @@ -0,0 +1,379 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * utils + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include "utils.h" +#if defined(_WIN32) // getcwd (unix) or __getcwd (win) + #include + #include +#else + #include +#endif + +#include +#include // stat (gDirExists) +#include +#include +#include +#include +#include +#include +#include +#if defined(__APPLE__) + #include // basename unix + #include // getpwuid +#endif + + + +bool gFileExists(const char *filename) { + FILE *fh = fopen(filename, "rb"); + if (!fh) { + return 0; + } + else { + fclose(fh); + return 1; + } +} + + +/* -------------------------------------------------------------------------- */ + + +bool gIsDir(const char *path) +{ + bool ret; + +#if defined(__linux__) + + struct stat s1; + stat(path, &s1); + ret = S_ISDIR(s1.st_mode); + +#elif defined(__APPLE__) + + if (strcmp(path, "")==0) + ret = false; + else { + struct stat s1; + stat(path, &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) { + std::string tmp = path; + tmp += "/Contents/Info.plist"; + if (gFileExists(tmp.c_str())) + ret = false; + } + } + +#elif defined(__WIN32) + + unsigned dwAttrib = GetFileAttributes(path); + ret = (dwAttrib != INVALID_FILE_ATTRIBUTES && + (dwAttrib & FILE_ATTRIBUTE_DIRECTORY)); +#endif + + return ret & !gIsProject(path); +} + + +/* -------------------------------------------------------------------------- */ + + +bool gDirExists(const char *path) +{ + struct stat st; + if (stat(path, &st) != 0 && errno == ENOENT) + return false; + return true; +} + + +/* -------------------------------------------------------------------------- */ + + +bool gMkdir(const char *path) +{ +#if defined(__linux__) || defined(__APPLE__) + if (mkdir(path, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) == 0) +#else + if (_mkdir(path) == 0) +#endif + return true; + return false; +} + + +/* -------------------------------------------------------------------------- */ + + +std::string gBasename(const char *path) +{ + std::string out = path; + out.erase(0, out.find_last_of(gGetSlash().c_str())+1); + return out; +} + + +/* -------------------------------------------------------------------------- */ + + +std::string gDirname(const char *path) +{ + std::string out = path; + out.erase(out.find_last_of(gGetSlash().c_str())); + return out; +} + + +/* -------------------------------------------------------------------------- */ + + +std::string gGetCurrentPath() +{ + char buf[PATH_MAX]; +#if defined(__WIN32) + if (_getcwd(buf, PATH_MAX) != NULL) +#else + if (getcwd(buf, PATH_MAX) != NULL) +#endif + return buf; + else + return ""; +} + + +/* -------------------------------------------------------------------------- */ + + +std::string gGetExt(const char *file) +{ + int len = strlen(file); + int pos = len; + while (pos>0) { + if (file[pos] == '.') + break; + pos--; + } + if (pos==0) + return ""; + std::string out = file; + return out.substr(pos+1, len); +} + + +/* -------------------------------------------------------------------------- */ + + +std::string gStripExt(const char *file) +{ + int len = strlen(file); + int pos = -1; + for (int i=0; i=0) { /// TODO - use gGetSlash() +#if defined(__linux__) || defined(__APPLE__) + if (out[i] == '/') +#elif defined(_WIN32) + if (out[i] == '\\') +#endif + break; + i--; + } + + out.erase(0, i+1); // includes the '/' (or '\' on windows) + return out; +} + + +/* -------------------------------------------------------------------------- */ + + +std::string gGetSlash() // TODO - create SLASH macro or constant +{ +#if defined(_WIN32) + return "\\"; +#else + return "/"; +#endif +} + + +/* -------------------------------------------------------------------------- */ + + +std::string gItoa(int i) +{ + std::stringstream out; + out << i; + return out.str(); +} + + +/* -------------------------------------------------------------------------- */ + + +std::string gTrim(const char *f) +{ + std::string out = f; + return gTrim(out); +} + + +std::string gTrim(const std::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); +} + + +/* -------------------------------------------------------------------------- */ + + +std::string gReplace(std::string in, const std::string& search, const std::string& replace) +{ + size_t pos = 0; + while ((pos = in.find(search, pos)) != std::string::npos) { + in.replace(pos, search.length(), replace); + pos += replace.length(); + } + return in; +} + + +/* -------------------------------------------------------------------------- */ + + +std::string gStripFileUrl(const char *f) +{ + std::string out = f; + out = gReplace(out, "file://", ""); + out = gReplace(out, "%20", " "); + return out; +} + + +/* -------------------------------------------------------------------------- */ + + +std::string gGetHomePath() +{ + 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 == NULL) { + gLog("[gGetHomePath] 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 std::string(path); +} + + +/* -------------------------------------------------------------------------- */ + + +void gSplit(std::string in, std::string sep, gVector *v) +{ + std::string full = in; + std::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->add(token); + } + while (next != std::string::npos); +} diff --git a/src/utils.h b/src/utils.h new file mode 100644 index 0000000..0c1a2d0 --- /dev/null +++ b/src/utils.h @@ -0,0 +1,190 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * utils + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#ifndef UTILS_H +#define UTILS_H + + +#include +#include +#include "log.h" + + +/* gVector + * lightweight template class. */ + +template class gVector +{ +public: + + /* gVector() + * default constructor, no parameters */ + + gVector() : size(0), s(NULL) {} + + /* gVector(const &) + * copy-constructor, when gVector a = b (where b is gVector). + * Default constructor doesn't copy referenced ojbects, so we need + * to re-allocate the internal stack for the copied object */ + + gVector(const gVector &other) + { + s = new T[other.size]; + for (unsigned i=0; i size-1) gLog("[vector] del() outside! requested=%d, size=%d\n", p, size); + T *tmp = new T[size-1]; + unsigned i=0; + unsigned j=0; + while (i 0) { + delete [] s; + s = NULL; + size = 0; + } + } + + + void swap(unsigned x, unsigned y) + { + T tmp = s[x]; + s[x] = s[y]; + s[y] = tmp; + } + + + T &at(unsigned p) + { + if (p > size-1) gLog("[vector] at() outside! requested=%d, size=%d\n", p, size); + return s[p]; + } + + + T &last() + { + return s[size-1]; + } + + + unsigned size; + T *s; // stack (array of T) +}; + + +/* ------------------------------------------------------------------ */ + + +bool gFileExists(const char *path); + +bool gDirExists(const char *path); + +bool gIsDir(const char *path); + +bool gIsProject(const char *path); + +bool gIsPatch(const char *path); + +bool gMkdir(const char *path); + +std::string gBasename(const char *path); + +std::string gReplace(std::string in, const std::string& search, const std::string& replace); + +std::string gDirname(const char *path); + +std::string gTrim(const char *path); +std::string gTrim(const std::string &s); + +std::string gGetCurrentPath(); + +std::string gGetHomePath(); + +std::string gStripFileUrl(const char *path); + +std::string gGetExt(const char *path); + +std::string gStripExt(const char *path); + +std::string gGetProjectName(const char *path); + +std::string gGetSlash(); + +std::string gItoa(int i); + +void gSplit(std::string in, std::string sep, gVector *v); + +#endif diff --git a/src/wave.cpp b/src/wave.cpp new file mode 100644 index 0000000..edaef7b --- /dev/null +++ b/src/wave.cpp @@ -0,0 +1,245 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * wave + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + + +#include +#include +#include // memcpy +#include +#include "wave.h" +#include "utils.h" +#include "conf.h" +#include "init.h" +#include "log.h" + + +extern Conf G_Conf; + + +Wave::Wave() + : data (NULL), + size (0), + isLogical(0), + isEdited (0) {} + + +/* ------------------------------------------------------------------ */ + + +Wave::~Wave() { + clear(); +} + + +/* ------------------------------------------------------------------ */ + + +int Wave::open(const char *f) { + + pathfile = f; + name = gStripExt(gBasename(f).c_str()); + fileIn = sf_open(f, SFM_READ, &inHeader); + + if (fileIn == NULL) { + gLog("[wave] unable to read %s. %s\n", f, sf_strerror(fileIn)); + pathfile = ""; + name = ""; + return 0; + } + + isLogical = false; + isEdited = false; + + return 1; +} + + +/* ------------------------------------------------------------------ */ + +/* how to read and write with libsndfile: + * + * a frame consists of all items (samples) that belong to the same + * point in time. So in each frame there are as many items as there + * are channels. + * + * Quindi: + * frame = [item, item, ...] + * In pratica: + * frame1 = [itemLeft, itemRight] + * frame2 = [itemLeft, itemRight] + * ... + */ + +int Wave::readData() { + size = inHeader.frames * inHeader.channels; + data = (float *) malloc(size * sizeof(float)); + if (data == NULL) { + gLog("[wave] unable to allocate memory\n"); + return 0; + } + + if (sf_read_float(fileIn, data, size) != size) + gLog("[wave] warning: incomplete read!\n"); + + sf_close(fileIn); + return 1; +} + + +/* ------------------------------------------------------------------ */ + + +int Wave::writeData(const char *f) { + + /* prepare the header for output file */ + + outHeader.samplerate = inHeader.samplerate; + outHeader.channels = inHeader.channels; + outHeader.format = inHeader.format; + + fileOut = sf_open(f, SFM_WRITE, &outHeader); + if (fileOut == NULL) { + gLog("[wave] unable to open %s for exporting\n", f); + return 0; + } + + int out = sf_write_float(fileOut, data, size); + if (out != (int) size) { + gLog("[wave] error while exporting %s! %s\n", f, sf_strerror(fileOut)); + return 0; + } + + isLogical = false; + isEdited = false; + sf_close(fileOut); + return 1; +} + + +/* ------------------------------------------------------------------ */ + + +void Wave::clear() { + if (data != NULL) { + free(data); + data = NULL; + pathfile = ""; + size = 0; + } +} + + +/* ------------------------------------------------------------------ */ + + +int Wave::allocEmpty(unsigned __size) { + + /* the caller must pass a __size for stereo values */ + + /// FIXME - this way if malloc fails size becomes wrong + size = __size; + data = (float *) malloc(size * sizeof(float)); + if (data == NULL) { + gLog("[wave] unable to allocate memory\n"); + return 0; + } + + memset(data, 0, sizeof(float) * size); /// FIXME - is it useful? + + inHeader.samplerate = G_Conf.samplerate; + inHeader.channels = 2; + inHeader.format = SF_FORMAT_WAV | SF_FORMAT_FLOAT; // wave only + + isLogical = true; + return 1; +} + + +/* ------------------------------------------------------------------ */ + + +int Wave::resample(int quality, int newRate) { + + float ratio = newRate / (float) inHeader.samplerate; + int newSize = ceil(size * ratio); + if (newSize % 2 != 0) // libsndfile goes crazy with odd size in case of saving + newSize++; + + float *tmp = (float *) malloc(newSize * sizeof(float)); + if (!tmp) { + gLog("[wave] unable to allocate memory for resampling\n"); + return -1; + } + + SRC_DATA src_data; + src_data.data_in = data; + src_data.input_frames = size/2; // in frames, i.e. /2 (stereo) + src_data.data_out = tmp; + src_data.output_frames = newSize/2; // in frames, i.e. /2 (stereo) + src_data.src_ratio = ratio; + + gLog("[wave] resampling: new size=%d (%d frames)\n", newSize, newSize/2); + + int ret = src_simple(&src_data, quality, 2); + if (ret != 0) { + gLog("[wave] resampling error: %s\n", src_strerror(ret)); + return 0; + } + + free(data); + data = tmp; + size = newSize; + inHeader.samplerate = newRate; + return 1; +} + + +/* ------------------------------------------------------------------ */ + + +std::string Wave::basename() { + return gStripExt(gBasename(pathfile.c_str()).c_str()); +} + +std::string Wave::extension() { + return gGetExt(pathfile.c_str()); +} + + +/* ------------------------------------------------------------------ */ + + +void Wave::updateName(const char *n) { + std::string ext = gGetExt(pathfile.c_str()); + name = gStripExt(gBasename(n).c_str()); + pathfile = gDirname(pathfile.c_str()) + gGetSlash() + name + "." + ext; + isLogical = true; + + /* a wave with updated name must become logical, since the underlying + * file does not exist yet. */ +} diff --git a/src/wave.h b/src/wave.h new file mode 100644 index 0000000..9ce81df --- /dev/null +++ b/src/wave.h @@ -0,0 +1,88 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * wave + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + + +#ifndef WAVE_H +#define WAVE_H + + +#include +#include +#include + + +class Wave { + +private: + + SNDFILE *fileIn; + SNDFILE *fileOut; + SF_INFO inHeader; + SF_INFO outHeader; + +public: + + Wave(); + ~Wave(); + + std::string pathfile; // full path + sample name + std::string name; // sample name (changeable) + + float *data; + int size; // wave size (size in stereo: size / 2) + bool isLogical; // memory only (a take) + bool isEdited; // edited via editor + + inline int rate () { return inHeader.samplerate; } + inline int channels() { return inHeader.channels; } + inline int frames () { return inHeader.frames; } + inline void rate (int v) { inHeader.samplerate = v; } + inline void channels(int v) { inHeader.channels = v; } + inline void frames (int v) { inHeader.frames = v; } + + std::string basename (); + std::string extension(); + + void updateName(const char *n); + int open (const char *f); + int readData (); + int writeData (const char *f); + void clear (); + + /* allocEmpty + * alloc an empty waveform. */ + + int allocEmpty(unsigned size); + + /* resample + * simple algorithm for one-shot resampling. */ + + int resample(int quality, int newRate); +}; + +#endif diff --git a/src/waveFx.cpp b/src/waveFx.cpp new file mode 100644 index 0000000..25e0812 --- /dev/null +++ b/src/waveFx.cpp @@ -0,0 +1,224 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * waveFx + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + + +#include +#include "waveFx.h" +#include "channel.h" +#include "mixer.h" +#include "wave.h" +#include "log.h" + + +extern Mixer G_Mixer; + + +float wfx_normalizeSoft(Wave *w) { + float peak = 0.0f; + float abs = 0.0f; + for (int i=0; isize; i++) { // i++: both L and R samples + abs = fabs(w->data[i]); + if (abs > peak) + peak = abs; + } + + /* 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; +} + + +/* ------------------------------------------------------------------ */ + + +bool wfx_monoToStereo(Wave *w) { + + unsigned newSize = w->size * 2; + float *dataNew = (float *) malloc(newSize * sizeof(float)); + if (dataNew == NULL) { + gLog("[wfx] unable to allocate memory for mono>stereo conversion\n"); + return 0; + } + + for (int i=0, j=0; isize; i++) { + dataNew[j] = w->data[i]; + dataNew[j+1] = w->data[i]; + j+=2; + } + + free(w->data); + w->data = dataNew; + w->size = newSize; + w->frames(w->frames()*2); + w->channels(2); + + return 1; +} + + +/* ------------------------------------------------------------------ */ + + +void wfx_silence(Wave *w, int a, int b) { + + /* stereo values */ + a = a * 2; + b = b * 2; + + gLog("[wfx] silencing from %d to %d\n", a, b); + + for (int i=a; idata[i] = 0.0f; + w->data[i+1] = 0.0f; + } + + w->isEdited = true; + + return; +} + + +/* ------------------------------------------------------------------ */ + + +int wfx_cut(Wave *w, int a, int b) { + a = a * 2; + b = b * 2; + + if (a < 0) a = 0; + if (b > w->size) b = w->size; + + /* create a new temp wave and copy there the original one, skipping + * the a-b range */ + + unsigned newSize = w->size-(b-a); + float *temp = (float *) malloc(newSize * sizeof(float)); + if (temp == NULL) { + gLog("[wfx] unable to allocate memory for cutting\n"); + return 0; + } + + gLog("[wfx] cutting from %d to %d, new size=%d (video=%d)\n", a, b, newSize, newSize/2); + + for (int i=0, k=0; isize; i++) { + if (i < a || i >= b) { // left margin always included, in order to keep + temp[k] = w->data[i]; // the stereo pair + k++; + } + } + + free(w->data); + w->data = temp; + w->size = newSize; + //w->inHeader.frames -= b-a; + w->frames(w->frames() - b - a); + w->isEdited = true; + + gLog("[wfx] cutting done\n"); + + return 1; +} + + +/* ------------------------------------------------------------------ */ + + +int wfx_trim(Wave *w, int a, int b) { + a = a * 2; + b = b * 2; + + if (a < 0) a = 0; + if (b > w->size) b = w->size; + + int newSize = b - a; + float *temp = (float *) malloc(newSize * sizeof(float)); + if (temp == NULL) { + gLog("[wfx] unable to allocate memory for trimming\n"); + return 0; + } + + gLog("[wfx] trimming from %d to %d (area = %d)\n", a, b, b-a); + + for (int i=a, k=0; idata[i]; + + free(w->data); + w->data = temp; + w->size = newSize; + //w->inHeader.frames = b-a; + w->frames(b - a); + w->isEdited = true; + + return 1; +} + + +/* ------------------------------------------------------------------ */ + + +void wfx_fade(Wave *w, int a, int b, int type) { + + float m = type == 0 ? 0.0f : 1.0f; + float d = 1.0f/(float)(b-a); + if (type == 1) + d = -d; + + a *= 2; + b *= 2; + + for (int i=a; idata[i] *= m; + w->data[i+1] *= m; + m += d; + } +} + + +/* ------------------------------------------------------------------ */ + + +void wfx_smooth(Wave *w, int a, int b) { + + int d = 32; // 64 if stereo data + + /* do nothing if fade edges (both of 32 samples) are > than selected + * portion of wave. d*2 => count both edges, (b-a)*2 => stereo + * values. */ + + if (d*2 > (b-a)*2) { + gLog("[WFX] selection is too small, nothing to do\n"); + return; + } + + wfx_fade(w, a, a+d, 0); + wfx_fade(w, b-d, b, 1); +} diff --git a/src/waveFx.h b/src/waveFx.h new file mode 100644 index 0000000..ebdef4a --- /dev/null +++ b/src/waveFx.h @@ -0,0 +1,59 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * waveFx + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2015 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + + +#ifndef WAVEFX_H +#define WAVEFX_H + + +/* normalizeSoft + * normalize the wave by returning the dB value for the boost volume. It + * doesn't deal with data in memory. */ + +float wfx_normalizeSoft(class Wave *w); + +bool wfx_monoToStereo(class Wave *w); + +void wfx_silence(class Wave *w, int a, int b); + +int wfx_cut(class Wave *w, int a, int b); + +int wfx_trim(class Wave *w, int a, int b); + +/* fade + * fade in or fade out selection. Fade In = type 0, Fade Out = type 1 */ + +void wfx_fade(class Wave *w, int a, int b, int type); + +/* smooth + * smooth edges of selection. */ + +void wfx_smooth(class Wave *w, int a, int b); + + +#endif -- 2.30.2