From d8dbdc62d4263f26c6dbfbf07830b1fe206ac993 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Jarom=C3=ADr=20Mike=C5=A1?= Date: Sun, 15 Jan 2017 17:51:32 +0000 Subject: [PATCH] Import giada_0.13.2~dfsg1.orig.tar.gz [dgit import orig giada_0.13.2~dfsg1.orig.tar.gz] --- COPYING | 674 + ChangeLog | 953 ++ Makefile.am | 337 + README.md | 66 + autogen.sh | 1579 +++ configure.ac | 188 + src/core/.dirstamp | 0 src/core/channel.cpp | 425 + src/core/channel.h | 289 + src/core/conf.cpp | 449 + src/core/conf.h | 144 + src/core/const.h | 483 + src/core/dataStorageIni.cpp | 70 + src/core/dataStorageIni.h | 48 + src/core/dataStorageJson.cpp | 148 + src/core/dataStorageJson.h | 65 + src/core/graphics.cpp | 1794 +++ src/core/graphics.h | 111 + src/core/init.cpp | 226 + src/core/init.h | 50 + src/core/kernelAudio.cpp | 501 + src/core/kernelAudio.h | 92 + src/core/kernelMidi.cpp | 474 + src/core/kernelMidi.h | 127 + src/core/midiChannel.cpp | 374 + src/core/midiChannel.h | 97 + src/core/midiMapConf.cpp | 432 + src/core/midiMapConf.h | 177 + src/core/mixer.cpp | 797 ++ src/core/mixer.h | 268 + src/core/mixerHandler.cpp | 292 + src/core/mixerHandler.h | 78 + src/core/patch.cpp | 490 + src/core/patch.h | 186 + src/core/patch_DEPR_.cpp | 619 + src/core/patch_DEPR_.h | 94 + src/core/plugin.cpp | 263 + src/core/plugin.h | 104 + src/core/pluginHost.cpp | 494 + src/core/pluginHost.h | 222 + src/core/recorder.cpp | 629 + src/core/recorder.h | 191 + src/core/sampleChannel.cpp | 1132 ++ src/core/sampleChannel.h | 217 + src/core/wave.cpp | 269 + src/core/wave.h | 93 + src/core/waveFx.cpp | 224 + src/core/waveFx.h | 59 + src/deps/juce-config.h | 21 + src/deps/rtaudio-mod/RtAudio.cpp | 10237 ++++++++++++++++ src/deps/rtaudio-mod/RtAudio.h | 1171 ++ src/ext/giada.ico | Bin 0 -> 9662 bytes src/ext/resource.h | 1 + src/ext/resource.rc | 2 + src/glue/channel.cpp | 487 + src/glue/channel.h | 98 + src/glue/io.cpp | 336 + src/glue/io.h | 67 + src/glue/main.cpp | 434 + src/glue/main.h | 81 + src/glue/plugin.cpp | 73 + src/glue/plugin.h | 46 + src/glue/storage.cpp | 514 + src/glue/storage.h | 43 + src/gui/dialogs/gd_about.cpp | 148 + src/gui/dialogs/gd_about.h | 59 + src/gui/dialogs/gd_actionEditor.cpp | 473 + src/gui/dialogs/gd_actionEditor.h | 135 + src/gui/dialogs/gd_beatsInput.cpp | 103 + src/gui/dialogs/gd_beatsInput.h | 56 + src/gui/dialogs/gd_bpmInput.cpp | 106 + src/gui/dialogs/gd_bpmInput.h | 51 + src/gui/dialogs/gd_browser.cpp | 315 + src/gui/dialogs/gd_browser.h | 136 + src/gui/dialogs/gd_config.cpp | 1025 ++ src/gui/dialogs/gd_config.h | 208 + src/gui/dialogs/gd_devInfo.cpp | 87 + src/gui/dialogs/gd_devInfo.h | 47 + src/gui/dialogs/gd_editor.cpp | 486 + src/gui/dialogs/gd_editor.h | 116 + src/gui/dialogs/gd_keyGrabber.cpp | 145 + src/gui/dialogs/gd_keyGrabber.h | 63 + src/gui/dialogs/gd_mainWindow.cpp | 124 + src/gui/dialogs/gd_mainWindow.h | 56 + src/gui/dialogs/gd_pluginChooser.cpp | 138 + src/gui/dialogs/gd_pluginChooser.h | 68 + src/gui/dialogs/gd_pluginList.cpp | 377 + src/gui/dialogs/gd_pluginList.h | 104 + src/gui/dialogs/gd_pluginWindow.cpp | 127 + src/gui/dialogs/gd_pluginWindow.h | 77 + src/gui/dialogs/gd_pluginWindowGUI.cpp | 124 + src/gui/dialogs/gd_pluginWindowGUI.h | 91 + src/gui/dialogs/gd_warnings.cpp | 79 + src/gui/dialogs/gd_warnings.h | 43 + src/gui/dialogs/midiIO/midiInputBase.cpp | 103 + src/gui/dialogs/midiIO/midiInputBase.h | 61 + src/gui/dialogs/midiIO/midiInputChannel.cpp | 172 + src/gui/dialogs/midiIO/midiInputChannel.h | 68 + src/gui/dialogs/midiIO/midiInputMaster.cpp | 60 + src/gui/dialogs/midiIO/midiInputMaster.h | 45 + src/gui/dialogs/midiIO/midiOutputBase.cpp | 116 + src/gui/dialogs/midiIO/midiOutputBase.h | 86 + src/gui/dialogs/midiIO/midiOutputMidiCh.cpp | 119 + src/gui/dialogs/midiIO/midiOutputMidiCh.h | 61 + src/gui/dialogs/midiIO/midiOutputSampleCh.cpp | 72 + src/gui/dialogs/midiIO/midiOutputSampleCh.h | 54 + src/gui/elems/actionEditor.cpp | 689 ++ src/gui/elems/actionEditor.h | 140 + src/gui/elems/baseActionEditor.cpp | 102 + src/gui/elems/baseActionEditor.h | 55 + src/gui/elems/basics/boxtypes.cpp | 59 + src/gui/elems/basics/boxtypes.h | 47 + src/gui/elems/basics/scroll.cpp | 50 + src/gui/elems/basics/scroll.h | 46 + src/gui/elems/browser.cpp | 188 + src/gui/elems/browser.h | 77 + src/gui/elems/envelopeEditor.cpp | 413 + src/gui/elems/envelopeEditor.h | 122 + src/gui/elems/ge_mixed.cpp | 603 + src/gui/elems/ge_mixed.h | 332 + src/gui/elems/ge_pluginBrowser.cpp | 127 + src/gui/elems/ge_pluginBrowser.h | 61 + src/gui/elems/ge_waveTools.cpp | 104 + src/gui/elems/ge_waveTools.h | 49 + src/gui/elems/ge_waveform.cpp | 840 ++ src/gui/elems/ge_waveform.h | 199 + src/gui/elems/ge_window.cpp | 183 + src/gui/elems/ge_window.h | 77 + src/gui/elems/mainWindow/beatMeter.cpp | 72 + src/gui/elems/mainWindow/beatMeter.h | 46 + src/gui/elems/mainWindow/keyboard/channel.cpp | 237 + src/gui/elems/mainWindow/keyboard/channel.h | 139 + .../mainWindow/keyboard/channelButton.cpp | 136 + .../elems/mainWindow/keyboard/channelButton.h | 60 + .../elems/mainWindow/keyboard/channelMode.cpp | 111 + .../elems/mainWindow/keyboard/channelMode.h | 54 + .../mainWindow/keyboard/channelStatus.cpp | 84 + .../elems/mainWindow/keyboard/channelStatus.h | 47 + src/gui/elems/mainWindow/keyboard/column.cpp | 293 + src/gui/elems/mainWindow/keyboard/column.h | 102 + .../elems/mainWindow/keyboard/keyboard.cpp | 405 + src/gui/elems/mainWindow/keyboard/keyboard.h | 158 + .../elems/mainWindow/keyboard/midiChannel.cpp | 284 + .../elems/mainWindow/keyboard/midiChannel.h | 72 + .../mainWindow/keyboard/sampleChannel.cpp | 499 + .../elems/mainWindow/keyboard/sampleChannel.h | 81 + src/gui/elems/mainWindow/mainIO.cpp | 180 + src/gui/elems/mainWindow/mainIO.h | 81 + src/gui/elems/mainWindow/mainMenu.cpp | 229 + src/gui/elems/mainWindow/mainMenu.h | 62 + src/gui/elems/mainWindow/mainTimer.cpp | 180 + src/gui/elems/mainWindow/mainTimer.h | 74 + src/gui/elems/mainWindow/mainTransport.cpp | 160 + src/gui/elems/mainWindow/mainTransport.h | 69 + src/gui/elems/midiLearner.cpp | 110 + src/gui/elems/midiLearner.h | 84 + src/gui/elems/muteEditor.cpp | 415 + src/gui/elems/muteEditor.h | 106 + src/gui/elems/noteEditor.cpp | 90 + src/gui/elems/noteEditor.h | 54 + src/gui/elems/pianoItem.cpp | 351 + src/gui/elems/pianoItem.h | 119 + src/gui/elems/pianoRoll.cpp | 360 + src/gui/elems/pianoRoll.h | 84 + src/main.cpp | 113 + src/utils/cocoa.h | 38 + src/utils/cocoa.mm | 46 + src/utils/fs.cpp | 267 + src/utils/fs.h | 67 + src/utils/gui.cpp | 222 + src/utils/gui.h | 99 + src/utils/log.cpp | 90 + src/utils/log.h | 45 + src/utils/string.cpp | 113 + src/utils/string.h | 53 + tests/catch.hpp | 9416 ++++++++++++++ tests/conf.cpp | 161 + tests/main.cpp | 2 + tests/midiMapConf.cpp | 124 + tests/patch.cpp | 232 + tests/pluginHost.cpp | 34 + tests/resources/test.wav | Bin 0 -> 81316 bytes tests/utils.cpp | 36 + tests/wave.cpp | 45 + 184 files changed, 58343 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/core/.dirstamp create mode 100644 src/core/channel.cpp create mode 100644 src/core/channel.h create mode 100644 src/core/conf.cpp create mode 100644 src/core/conf.h create mode 100644 src/core/const.h create mode 100644 src/core/dataStorageIni.cpp create mode 100644 src/core/dataStorageIni.h create mode 100644 src/core/dataStorageJson.cpp create mode 100644 src/core/dataStorageJson.h create mode 100644 src/core/graphics.cpp create mode 100644 src/core/graphics.h create mode 100644 src/core/init.cpp create mode 100644 src/core/init.h create mode 100644 src/core/kernelAudio.cpp create mode 100644 src/core/kernelAudio.h create mode 100644 src/core/kernelMidi.cpp create mode 100644 src/core/kernelMidi.h create mode 100644 src/core/midiChannel.cpp create mode 100644 src/core/midiChannel.h create mode 100644 src/core/midiMapConf.cpp create mode 100644 src/core/midiMapConf.h create mode 100644 src/core/mixer.cpp create mode 100644 src/core/mixer.h create mode 100644 src/core/mixerHandler.cpp create mode 100644 src/core/mixerHandler.h create mode 100644 src/core/patch.cpp create mode 100644 src/core/patch.h create mode 100644 src/core/patch_DEPR_.cpp create mode 100644 src/core/patch_DEPR_.h create mode 100644 src/core/plugin.cpp create mode 100644 src/core/plugin.h create mode 100644 src/core/pluginHost.cpp create mode 100644 src/core/pluginHost.h create mode 100644 src/core/recorder.cpp create mode 100644 src/core/recorder.h create mode 100644 src/core/sampleChannel.cpp create mode 100644 src/core/sampleChannel.h create mode 100644 src/core/wave.cpp create mode 100644 src/core/wave.h create mode 100644 src/core/waveFx.cpp create mode 100644 src/core/waveFx.h create mode 100644 src/deps/juce-config.h create mode 100755 src/deps/rtaudio-mod/RtAudio.cpp create mode 100755 src/deps/rtaudio-mod/RtAudio.h create mode 100644 src/ext/giada.ico create mode 100644 src/ext/resource.h create mode 100644 src/ext/resource.rc create mode 100644 src/glue/channel.cpp create mode 100644 src/glue/channel.h create mode 100644 src/glue/io.cpp create mode 100644 src/glue/io.h create mode 100644 src/glue/main.cpp create mode 100644 src/glue/main.h create mode 100644 src/glue/plugin.cpp create mode 100644 src/glue/plugin.h create mode 100644 src/glue/storage.cpp create mode 100644 src/glue/storage.h create mode 100644 src/gui/dialogs/gd_about.cpp create mode 100644 src/gui/dialogs/gd_about.h create mode 100644 src/gui/dialogs/gd_actionEditor.cpp create mode 100644 src/gui/dialogs/gd_actionEditor.h create mode 100644 src/gui/dialogs/gd_beatsInput.cpp create mode 100644 src/gui/dialogs/gd_beatsInput.h create mode 100644 src/gui/dialogs/gd_bpmInput.cpp create mode 100644 src/gui/dialogs/gd_bpmInput.h create mode 100644 src/gui/dialogs/gd_browser.cpp create mode 100644 src/gui/dialogs/gd_browser.h create mode 100644 src/gui/dialogs/gd_config.cpp create mode 100644 src/gui/dialogs/gd_config.h create mode 100644 src/gui/dialogs/gd_devInfo.cpp create mode 100644 src/gui/dialogs/gd_devInfo.h create mode 100644 src/gui/dialogs/gd_editor.cpp create mode 100644 src/gui/dialogs/gd_editor.h create mode 100644 src/gui/dialogs/gd_keyGrabber.cpp create mode 100644 src/gui/dialogs/gd_keyGrabber.h create mode 100644 src/gui/dialogs/gd_mainWindow.cpp create mode 100644 src/gui/dialogs/gd_mainWindow.h create mode 100644 src/gui/dialogs/gd_pluginChooser.cpp create mode 100644 src/gui/dialogs/gd_pluginChooser.h create mode 100644 src/gui/dialogs/gd_pluginList.cpp create mode 100644 src/gui/dialogs/gd_pluginList.h create mode 100644 src/gui/dialogs/gd_pluginWindow.cpp create mode 100644 src/gui/dialogs/gd_pluginWindow.h create mode 100644 src/gui/dialogs/gd_pluginWindowGUI.cpp create mode 100644 src/gui/dialogs/gd_pluginWindowGUI.h create mode 100644 src/gui/dialogs/gd_warnings.cpp create mode 100644 src/gui/dialogs/gd_warnings.h create mode 100644 src/gui/dialogs/midiIO/midiInputBase.cpp create mode 100644 src/gui/dialogs/midiIO/midiInputBase.h create mode 100644 src/gui/dialogs/midiIO/midiInputChannel.cpp create mode 100644 src/gui/dialogs/midiIO/midiInputChannel.h create mode 100644 src/gui/dialogs/midiIO/midiInputMaster.cpp create mode 100644 src/gui/dialogs/midiIO/midiInputMaster.h create mode 100644 src/gui/dialogs/midiIO/midiOutputBase.cpp create mode 100644 src/gui/dialogs/midiIO/midiOutputBase.h create mode 100644 src/gui/dialogs/midiIO/midiOutputMidiCh.cpp create mode 100644 src/gui/dialogs/midiIO/midiOutputMidiCh.h create mode 100644 src/gui/dialogs/midiIO/midiOutputSampleCh.cpp create mode 100644 src/gui/dialogs/midiIO/midiOutputSampleCh.h create mode 100644 src/gui/elems/actionEditor.cpp create mode 100644 src/gui/elems/actionEditor.h create mode 100644 src/gui/elems/baseActionEditor.cpp create mode 100644 src/gui/elems/baseActionEditor.h create mode 100644 src/gui/elems/basics/boxtypes.cpp create mode 100644 src/gui/elems/basics/boxtypes.h create mode 100644 src/gui/elems/basics/scroll.cpp create mode 100644 src/gui/elems/basics/scroll.h create mode 100644 src/gui/elems/browser.cpp create mode 100644 src/gui/elems/browser.h create mode 100644 src/gui/elems/envelopeEditor.cpp create mode 100644 src/gui/elems/envelopeEditor.h create mode 100644 src/gui/elems/ge_mixed.cpp create mode 100644 src/gui/elems/ge_mixed.h create mode 100644 src/gui/elems/ge_pluginBrowser.cpp create mode 100644 src/gui/elems/ge_pluginBrowser.h create mode 100644 src/gui/elems/ge_waveTools.cpp create mode 100644 src/gui/elems/ge_waveTools.h create mode 100644 src/gui/elems/ge_waveform.cpp create mode 100644 src/gui/elems/ge_waveform.h create mode 100644 src/gui/elems/ge_window.cpp create mode 100644 src/gui/elems/ge_window.h create mode 100644 src/gui/elems/mainWindow/beatMeter.cpp create mode 100644 src/gui/elems/mainWindow/beatMeter.h create mode 100644 src/gui/elems/mainWindow/keyboard/channel.cpp create mode 100644 src/gui/elems/mainWindow/keyboard/channel.h create mode 100644 src/gui/elems/mainWindow/keyboard/channelButton.cpp create mode 100644 src/gui/elems/mainWindow/keyboard/channelButton.h create mode 100644 src/gui/elems/mainWindow/keyboard/channelMode.cpp create mode 100644 src/gui/elems/mainWindow/keyboard/channelMode.h create mode 100644 src/gui/elems/mainWindow/keyboard/channelStatus.cpp create mode 100644 src/gui/elems/mainWindow/keyboard/channelStatus.h create mode 100644 src/gui/elems/mainWindow/keyboard/column.cpp create mode 100644 src/gui/elems/mainWindow/keyboard/column.h create mode 100644 src/gui/elems/mainWindow/keyboard/keyboard.cpp create mode 100644 src/gui/elems/mainWindow/keyboard/keyboard.h create mode 100644 src/gui/elems/mainWindow/keyboard/midiChannel.cpp create mode 100644 src/gui/elems/mainWindow/keyboard/midiChannel.h create mode 100644 src/gui/elems/mainWindow/keyboard/sampleChannel.cpp create mode 100644 src/gui/elems/mainWindow/keyboard/sampleChannel.h create mode 100644 src/gui/elems/mainWindow/mainIO.cpp create mode 100644 src/gui/elems/mainWindow/mainIO.h create mode 100644 src/gui/elems/mainWindow/mainMenu.cpp create mode 100644 src/gui/elems/mainWindow/mainMenu.h create mode 100644 src/gui/elems/mainWindow/mainTimer.cpp create mode 100644 src/gui/elems/mainWindow/mainTimer.h create mode 100644 src/gui/elems/mainWindow/mainTransport.cpp create mode 100644 src/gui/elems/mainWindow/mainTransport.h create mode 100644 src/gui/elems/midiLearner.cpp create mode 100644 src/gui/elems/midiLearner.h create mode 100644 src/gui/elems/muteEditor.cpp create mode 100644 src/gui/elems/muteEditor.h create mode 100644 src/gui/elems/noteEditor.cpp create mode 100644 src/gui/elems/noteEditor.h create mode 100644 src/gui/elems/pianoItem.cpp create mode 100644 src/gui/elems/pianoItem.h create mode 100644 src/gui/elems/pianoRoll.cpp create mode 100644 src/gui/elems/pianoRoll.h create mode 100644 src/main.cpp create mode 100644 src/utils/cocoa.h create mode 100644 src/utils/cocoa.mm create mode 100644 src/utils/fs.cpp create mode 100644 src/utils/fs.h create mode 100644 src/utils/gui.cpp create mode 100644 src/utils/gui.h create mode 100644 src/utils/log.cpp create mode 100644 src/utils/log.h create mode 100644 src/utils/string.cpp create mode 100644 src/utils/string.h create mode 100644 tests/catch.hpp create mode 100644 tests/conf.cpp create mode 100644 tests/main.cpp create mode 100644 tests/midiMapConf.cpp create mode 100644 tests/patch.cpp create mode 100644 tests/pluginHost.cpp create mode 100644 tests/resources/test.wav create mode 100644 tests/utils.cpp create mode 100644 tests/wave.cpp 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..65e29a5 --- /dev/null +++ b/ChangeLog @@ -0,0 +1,953 @@ + +-------------------------------------------------------------------------------- + + + Giada - Your Hardcore Loopmachine. + + Developed by Monocasual Laboratories + + www.giadamusic.com + + CHANGELOG +-------------------------------------------------------------------------------- + + +0.13.2 --- 2017 . 01 . 14 +- MIDI learn for plugins parameters +- Toggle hidden files in File Browser +- Fix broken compilation when build without VST support +- Make sure PluginChooser window has a sane size +- Decouple Recorder from any global variable +- Better source code organization +- Make plugin creation more robust +- More source code reorganization +- Fix crash on clicking scrollbar arrows (GitHub #53) +- Fix crash when doubling/dividing length while recording (GitHub #110) + + +0.13.1 --- 2016 . 11 . 16 +- Input MIDI to MIDI channels/plugins +- Refinements to show/hide 'R' button's dynamics +- Increase piano roll items' height +- Set input volume to max by default +- Start live-recorded sample channels right away +- Avoid potential crashes when loading samples on running channels +- Generate metronome during output post-processing +- Better widgets' layout in Sample Editor +- Lots of source code optimizations and cleanups +- Fix inverted 'R' button's status (GitHub #94) +- Better handling of 'R' button's status when the sequencer is off (GitHub #95) +- Fix non-playing samples if live-recorded and 'R' button is on (GitHub #93) +- Reset button statuses once channels have been freed (GitHub #100) +- Fix missing ASIO and WASAPI APIs on Windows (GitHub #96) +- Missing RtMidi libs on Linux (GitHub #102) +- Fix fade-in/fade-out editing not triggering alert on save (GitHub #101) + + +0.13.0 --- 2016 . 09 . 20 +- Deep file browser refactoring +- Save browser's scroll position and last item selected on opening +- Load patches/projects/samples on double click +- 64 bit builds for Windows +- Prevent deprecated patch from crashing if a plugin is not found in the stack +- Force logger to flush to file on Windows +- Add more default values for windows' dimensions and positions +- Avoid crashes on Configuration panel if no midimaps were selected +- Fix missing keyRelease actions in action editor +- Update JUCE to version 4.2.3 +- Don't include JUCE on tests without VST support (GitHub #75) +- Fix compilation errors on GCC 6 (GitHub #82) +- Fix includes on OSX (GitHub #92) +- Fix wrong channel's actions count that prevented "R" button to be toggled + properly +- Fixed a bug that prevented actions on frame 0 to being properly reproduced +- Make Recorder a proper class +- Better naming convention for ActionEditor's children classes +- Source code reorganization + + +0.12.2 --- 2016 . 06 . 02 +- Update RtAudio to version 4.1.2 +- Add WASAPI support on Windows +- Sortable plugins list +- Simplify custom RtAudio build and inclusion on Linux +- Fix crashes on startup on OS X El Capitan +- Store position and size of Available Plugins window +- Untangle Channels' code from global variables + + +0.12.1 --- 2016 . 05 . 06 +- Show percentage progress for plugin scan +- Notify if plugins are missing +- Notify if unknown plugins are present +- Fix potential segfault on MasterIn/MasterOut plugins loading +- Proper cleanup of JUCE resources +- Internal refactoring on PluginHost's global variables + + +0.12.0 --- 2016 . 03 . 07 +- Port to JUCE Framework for audio plugin management +- Increase global font size +- Minor UI fixes and cleanups +- Add ability to run tests outside Travis CI +- Switch to C++11 +- 64 bit binaries for OS X +- Use new constant for global font size + + +0.11.2 --- 2016 . 01 . 16 +- New JSON-based midimap files +- Add new channel by right-clicking anywhere on a column +- Show warning if patch is using the deprecated file format +- Do not force 32 bit compilation on OS X +- Fix warnings and errors on GCC 5.3 +- Fix a bug that prevented MIDI Jack from being selected on Linux + + +0.11.1 --- 2015 . 12 . 22 +- Ability to clone channels +- New JSON-based configuration file +- Port all vectors from old gVector to std::vector +- Deactivate all other MIDI fields when changing MIDI system in Config window +- Minor optimizations in configuration panel, Audio tab +- Assume 'none' as default sound system +- Include Catch header file in source package +- Update Travis CI environment to Ubuntu Trusty +- Fix missing sanitization after reading configuration file +- Fix garbage text in device info window +- Fix wrong config value if no midimaps are available +- Fix garbage text while printing device and port names + + +0.11.0 --- 2015 . 12 . 02 +- New JSON-based patch system +- Properly store column width in patch +- Port all const char* strings to std::string in patch/project glue layer +- Switch to SemVer-like internal versioning system +- More source code reorganization +- Fix potential memory leaks in Mixer +- Fix missing static link of RtMidi on Linux +- Unable to store pitch values > 2.0 (fixed) +- Missing assigned key after opening patch (fixed) + + +0.10.2 --- 2015 . 10 . 21 +- Setup Travis CI automated builds +- Add base framework for unit testing (with Catch) +- Improve behavior of Loop Once family when the sequencer is halted +- Fix empty sample path in sample channels when saving a Project +- Fix disabled "edit actions" for sample channels +- Fix missing pthreadGC2.dll in Windows build + + +0.10.1 --- 2015 . 08 . 26 +- Massive source folders refactoring +- Improved usability of "play" buttons for channels +- Remove support for patches created with Giada < 0.6.x +- Fix check for configured soundsystem (would break compilation on g++5) +- Small fixes and cleanup in Makefile.am + + +0.10.0 --- 2015 . 07 . 05 +- MIDI lightning output +- Other minor fixes + + +0.9.6 --- 2015 . 05 . 11 +- Keyboard binding for MIDI channels +- Support for multiple files in drag-n-drop operations +- Different color for wait/end statuses +- Small improvements to Keyboard grabber widget +- Fix random crashes with Jack enabled +- Fix weird behavior with multiple drag and drop +- Code refactoring + + +0.9.5 --- 2015 . 03 . 28 +- Better column resize algorithm +- New patch loading system with permanent MIDI mapping +- Ability to clear assigned keys (keyboard mode) +- Improved zoom icons in editors +- Fix deprecation warning in configure.ac + + +0.9.4 --- 2015 . 02 . 24 +- Drag-n-drop now works also in existing channels +- Store 'resize recordings' flag in giada.conf +- Better management of duplicate samples +- Add more VST debug information +- Minor fixes and tweaks + + +0.9.3 --- 2015 . 02 . 01 +- New GUI improvement: responsive and resizable columns +- Upgrade to FLTK 1.3.3 +- More robust column handling mechanism +- Support for MIDI devices without note-off message (@blablack) +- Fix segfaults when saving a patch with missing plugins +- Fix many minor graphical bugs +- Fix wrong vector assignment in MIDI send event +- Fix reloaded patches with no right tempo/beats displayed +- Fix random odd frames when adding/moving events in Piano Roll +- Minor internal cleanup + + +0.9.2 --- 2014 . 11 . 29 +- New grid layout in Sample Editor +- Load samples via drag n drop +- Add new utility functions: gTrim and gStripFileUrl +- Fix "normalize" button position in Sample Editor +- Minor waveform drawing optimizations +- Add missing files for RtAudio-mod compilation +- All one-shot mode, if fired manually, get the first frame truncated (fixed) + + +0.9.1 --- 2014 . 09 . 24 +- Bring back custom version of rtAudio in source package +- Automatically turn up volume when adding new channel +- Updated 'misc' tab in configuration panel +- Fix startup crash on OS X +- Fix missing jack headers + + +0.9.0 --- 2014 . 08 . 18 +- New full-screen GUI +- Multi-column support +- Advanced logging system +- Upgrade to RtAudio 4.1.1 and RtMidi 2.1.0 +- Removed embedded RtAudio (thanks to Arty) +- Fix wrong processing of VST MIDI events on 64 bit version +- Fix stretched buttons when resizing sample editor window +- "Clear all samples" destroys channels (fixed) +- "Free channel" messes up loop / mute buttons (fixes) +- Fix potential recordings with odd frames + + +0.8.4 --- 2014 . 03 . 27 +- New mode 'Loop Bar Once' +- Several small improvements and cleanups to internal utils functions +- Fixed missing title in several subwindows +- (win) Fix runtime error when loading a new project +- Fix chan reset when clicking on waveform +- Properly close subwindows after a channel has been deleted +- Fix 'reload' button not working for samples with updated names + + +0.8.3 --- 2014 . 02 . 14 +- Experimental MIDI timing output with MTC and MIDI clock +- Expose Sequencer x2 and /2 via MIDI +- New pitch operators x2 and /2 +- Internal xfade process restored +- "set key..." becomes "setup keyboard input" for sample channels +- MIDI events are now saved as unsigned int in patch +- Same expression on both sides of '|' in recorder.cpp (fixed) +- Muted channels leak some glitches on 'kill' event (fixed) +- Piano roll can't be edited anymore if beats == 32 (fixed) +- Noise when adding new MIDI channel (fixed) +- Boost and Normalize not working (fixed) +- Multiple copies of every file used by the patch (fixed) +- Samples with -1, -2, ... -n suffix are not included in patch (fixed) +- Segfaults when quantizing samples (fixed) + + +0.8.2 --- 2014 . 01 . 13 +- Pitch control exposed via MIDI +- New tools in Sample Editor (linear fade in/out, smooth edges) +- Implemented vstEvent->deltaFrames, gaining more precision with vst + MIDI events +- Add Fl::lock/Fl::unlock dynamics to glue_ calls where needed +- Avoid pitch sliding when changing pitch of a sample in status OFF +- Update copyright info in source files +- Internal fade in and fade out restored +- Add 'Giada' keyword to desktop file +- Fix annoying glitches when playing very short samples +- Fix random crashes when controlling giada via MIDI +- Fix missing MIDI mapping for read-actions button + + +0.8.1 --- 2013 . 12 . 09 +- New, high-quality pitch control based on libsamplerate +- New set of functions 'spread sample to beat/song' +[known issues] +- Internal crossfades have been temporarily disabled. Some clicks may + occur + + +0.8.0 --- 2013 . 11 . 03 +- Initial MIDI input support +- Fix freeze when recording audio inputs on a second channel +- Fix 'R' button to show up even if the channel has no actions +- Fix weird drawings of keypress actions in action editor +- Free channel: delete 'R' button as well +- Shift+key does not kill loop mode channels in a wait status +- Fix issue with 'R' button and newly added actions +- Remove "left"/"right" labels from main buttons + + +0.7.3 --- 2013 . 09 . 14 +- Experimental 64 bit compilation (Linux only) +- Massive internal cleanup of channel/gui channel layers +- Set default mode to full volume on sample load +- Set default mode to oneshot basic +- Faster drawings in piano roll +- Visual aids in piano roll +- Scroll to pointer in piano roll +- Several minor improvements in piano roll's usability +- Revised VST Carbon window popup system +- Minor improvements in startInputRec/stopInputRec procedure +- Fix compile error using local type Plugin* in Channel's constructor +- Fix segfault in OSX when working with VST windows + + +0.7.2 --- 2013 . 07 . 27 +- Initial MIDI output support +- Mute now affects channels with VSTi signals +- Lots of deb package improvements +- Complete rewrite of VST GUI part on OS X +- Don't send MIDI mute on sample channels +- Send MIDI mute for MIDI channels in play mode +- Fix wrong looping due to VST processing in mixer::masterPlay +- Fix jack crashes when using Giada with ALSA +- Fix VST random crashes on OSX, bus error +- Fix input device set to -1 after a system change + + +0.7.1 --- 2013 . 06 . 27 +- Initial Jack Transport support +- Send global note off when sequencer is being stopped +- Send note off when deleting notes in Piano Roll +- Store position and size of Piano Roll in conf file +- Avoid overlap MIDI notes in Piano Roll +- MIDI channel refactoring +- MIDI channels now behave like loop-mode ones +- Fix graphical bugs in Action Editor, sample mode +- Fix refresh issue in Piano Roll when deleting items +- Lots of invisible cleanups and improvements + + +0.7.0 --- 2013 . 06 . 05 +- Initial MIDI output implementation +- Initial VSTi (instrument) support +- New piano roll widget in action editor +- New chan mode: MIDI vs SAMPLE +- Fix E-MU Tracker Pre not correctly listed in audio in/output + + +0.6.4 --- 2013 . 05 . 07 +- Resizable plugin parameter window +- New and standard package name format -. +- Implement RtAudio::getCompiledApi() to fetch compiled APIs +- Implement audioMasterGetSampleRate, audioMasterGetLanguage VST opcodes +- Add drop-down menu for buffer size values in config panel +- Enhance project portability between OSes +- Lots of fixes and improvements for VST strings and parameters +- Avoid segfault when loading recs from a patch with files not found +- Always remember selected program when shifting up/down plugins +- Fix wrong size of single_press displayed in action editor +- Fix volume actions resized with value set to zero +- Fix volume envelope always over the cover area +- Fix src package extracts to current dir +- Fix segfault in loadpatch process if plugin GUIs are open +- Fix segfault when closing patch with plugins in BAD status + + +0.6.3 --- 2013 . 04 . 23 +- New 'solo' button +- Portable project system +- New 'Single Endless' channel mode +- GUI enhancements for channels in WAIT or ENDING status +- Minor fixes & cleanups + + +0.6.2 --- 2013 . 04 . 05 +- New volume envelope widget +- Zoom with mouse wheel in the action editor +- Graphical enhancements & speedups for the action editor +- Loop-repeat doesn't stop when put in ending mode (fixed) +- Fix draw errors when zooming too much the action editor +- Set silence in wave editor messes up the waveform (fixed) +- Wrong slashes in file path when saving a patch in Windows (fixed) +- Many, many code improvements and bugs fixed + + +0.6.1 --- 2013 . 03 . 21 +- Unlimited number of channels +- Deep internal refactoring, mixer/GUI layers +- Fix random crashes on exit +- Fix crashes when closing Giada with VST windows opened +- Always free Master In plugin stack on exit +- Lots of other minor bugs fixed and small enhancements + + +0.6.0 --- 2013 . 03 . 02 +- New, full-screen, redesigned sample editor +- Zoom with mouse wheel in sample editor +- Use kernelAudio::defaultIn/defaultOut for DEFAULT_SOUNDDEV_OUT +- Volume knob in main window now updates the editor +- Sound system issues in OS X (fixed) +- Output device info dialog refers to wrong device (fixed) + + +0.5.8 --- 2013 . 02 . 07 +- Internal samplerate conversion (with libsamplerate) +- Bring channels automatically to full volume on sample load +- Ability to set the audio device frequency +- New "internal mute" feature +- fix for deprecated VST opcode 14 +- fix deb package issues on Ubuntu 12.10 / KXStudio + + +0.5.7 --- 2013 . 01 . 21 +- visual grid + snapping in the action editor +- implement more audioMasterCanDo's in pluginHost +- limit zoom in actionEditor +- revise zoom behavior in actionEditor, now more comfortable +- fix forward declaration & inclusion of several headers +- implemented VST opcode 32 +- implemented VST opcode 33 +- implemented VST opcode 34 +- update website link in tar files +- update copyright info for 2013 + + +0.5.6 --- 2013 . 01 . 03 +- New overdub mode for live recording +- Support for VST programs, aka presets +- Lots of VST opcodes implemented +- Fix crash when removing a plugin from the stack +- Fix pops when going to beat 0 +- Fix compilation issues without --enable-vst +- Many invisible optimizations and small bugs fixed + + +0.5.5 --- 2012 . 12 . 15 +- "Hear what you're playing" feature +- Fx processing on the input side +- Ability to add different action types (Action Editor) +- Desktop integration on Linux (via deb package) +- Upgrade to FLTK 1.3.2 +- Remove "the action might stop the channel" when loading new samples +- Fix wrong positioning of zoom tools (Action Editor) +- Fix unwanted interactions on the grey area (Action Editor) +- Fix wrong memory alloc during the VST processing +- VST don't show up in OS X (fixed) +- Minor internal refactoring + bugfixing + + +0.5.4 --- 2012 . 11 . 24 +- VST GUI support +- Better subwindow management +- Implemented many other VST opcodes +- Missing plugins are now shown in the list with a 'dead' state +- Refresh action editor when changing beats (via beat operator or + beat window) +- Graphical improvements in the action editor +- Resizable action editor doesn't work well (fixed) +- Fix auto fadeout for SINGLE_PRESS channels +- Fix compilation without --enable-vst +- Fix for a wrong prototype definition of the VST hostCallback + + +0.5.3 --- 2012 . 10 . 26 +- Live beat manipulators (x2)(/2) +- New sub-windows management, faster and more comfortable +- New optional hard limiter on the output side +- Action Editor window recalls x,y,w,h zoom and position +- Usability improvements while handling an action (action editor) +- Refresh actionEditor window when switching channel mode or delete + actions +- Unable to delete a killchan action (action editor) (fixed) +- Don't show ACTION_KILLCHAN in a singlepress channel (action editor) +- Libsndfile no longer statically linked in Linux +- Fixed a typo in config: "when the sequeCer is halted" +- redefinition of DEFAULT_PITCH in wingdi.h (windows) (fixed) +- Upgrade to FLTK 1.3.0 +- Other internal optimizations +- Other small bugs fixed + + +0.5.2 --- 2012 . 10 . 05 +- Add ability to handle actions for loop-mode channels +- Add ability to record live mute actions for loop-mode channels +- Lots of live action recording improvements +- Enhanced usability for the action editor +- More verbose output if kernel audio fails to start +- Several internal optimizations + + +0.5.1 --- 2012 . 09 . 13 +- First implementation of the Action Editor +- Added compatibility with Ubuntu >= 10.04 + + +0.5.0 --- 2012 . 07 . 23 +- New custom project folder (.gprj) +- Sample names are now made unique +- Fixed unwanted time stretching while exporting a mono sample +- Lots of minor internal improvements + + +0.4.12 --- 2012 . 07 . 01 +- VST parameters and stacks are now stored in patch file +- Upgrade to RtAudio 0.4.11 +- PulseAudio support in Linux (thanks to RtAudio 0.4.11) +- Revised .deb package +- Enhanced "normalize" function in wave editor +- Several memory issues fixed +- Internal enhancements and minor bugs fixed + + +0.4.11 --- 2012 . 06 . 10 +- VST stack for each channel +- Custom paths for plugins, samples and patches +- Crash in config panel if device is busy (fixed) +- Graphical bug in the input meter (fixed) +- ParamLabel added in the VST parameter list + + +0.4.10 --- 2012 . 05 . 30 +- Ability to shift up an down VST plugins +- Enhanced patch/conf architecture +- Ability to edit a sample while playing +- Mutex controls in VST processing +- Lots of security issues fixed while changing pitch dinamically +- Enhanced sub-window system +- Several minor bugs fixed + + +0.4.9 --- 2012 . 05 . 12 +- No more mandatory inputs +- Pitch value properly stored inside the patch +- Several small VST host improvements +- Enhanced window management +- Ability to browse files while playing with main GUI (non-modal browser) +- Improved error checking in KernelAudio +- Wrong style for lower scrollbar in Browser (fixed) +- Fixed compilation on 64 bit systems (thanks to Speps@Archlinux) +- Samplerate no longer hardcoded, auto-detected with JACK +- Minor internal improvements and bugfixing + + +0.4.8 --- 2012 . 04 . 21 +- Initial VST support (experimental) +- Pitch controller (experimental, no filtering) +- OSX bundles are now correctly handled by the file browser +- Fixed several memory leaks +- Minor internal improvements + + +0.4.7 --- 2012 . 03 . 31 +- Cut, trim & silence operations in sample editor +- New "Reload sample" button added +- Lots of optimizations in the waveform drawing routines +- The sample is no longer editable while in play mode +- Fixed potential startup crashes while using Giada with Jack Audio +- Other minor fixes applied to the configuration panel +- Fixed compilation on 64 bit systems (thanks to Speps@Archlinux) + + +0.4.6 --- 2012 . 03 . 11 +- New device information panel +- The device configuration now shows only active and available devices +- Channel panel no longer pops up during a recording process +- GUI beautifications and other minor graphical fixes +- Program icon added in all subwindows +- Action records no longer available during a take, and vice versa +- Fixed a serious bug that swapped input and output devices +- Fixed loop behavior in ending mode +- Fixed clicks when stopping a muted channel in loop + + +0.4.5 --- 2012 . 02 . 25 +- Complete GUI redesign +- New "start/stop action recs" button +- Lots of internal cleanups and micro refactorings +- Small drawing glithes in Editor and status box (fixed) +- An invalid patch puts Giada to init state (fixed) +- Fixed button repeat on start/stop, action rec, input rec +- Checks against takes with unique name +- Message "this action may stop the channel" always shown (fixed) +- Channel no longer freeable while a take is in progress + + +0.4.4 --- 2012 . 02 . 04 +- New input/output channel selector +- Rewind bypasses the quantizer if triggered via mouse (fixed) +- Fixed library paths in configure and makefile (thanks to Yann C.) +- Added AUTHORS and NEWS files to the source package (thanks to Yann C.) +- More robust sample export procedure +- Issues with mute buttons when opening a patch (fixed) +- Several usability improvements +- Minor code cleanups and optimizations + + +0.4.3 --- 2012 . 01 . 21 +- New "save project" feature +- Ability to export a single sample to disk +- More feedback when removing/clearing actions and samples +- Sequencer starts automatically when action-rec button is pressed +- Alert if patch name is empty while saving it +- Channels now store internally the name of the samples +- Missing "--no devices found--" in input devices menu (fixed) +- Alert added if there are no empty channels for recording +- "Edit->Clear all actions" no longer works (fixed) +- END button could be used as a channel trigger (fixed) +- Recorders are available even if device status is wrong (fixed) +- Missing sample rewind if channel is muted (fixed) +- Quantizer doesn't work if framesize is odd (fixed) +- Random segfault when closing Giada (fixed) +- Lots of code cleanups +- Other minor improvements and optimizations + + +0.4.2 --- 2012 . 01 . 09 +- Live sampling from external input with meter and delay compensation +- Check against uneven values and overflow in buffersize field +- Wrong normalized values if volume level is 0.0 (fixed) +- Boost dial goes crazy if normalized > 20.0 dB (fixed) +- Boost dial goes crazy if normalized < 0.0 dB (fixed) +- Unwanted noise click if a muted channel is being rewinded (fixed) +- Mute doesn't work well for single-shot samples (fixed) +- Wrong FLTK headers (fixed, thanks to Yann C.) +- Moving chanStart/chanEnd swaps stereo image (fixed) +- Reset to init state doesn't reset mute buttons (fixed) +- Wrong chanStart value if > 0 (fixed) + + +0.4.1 --- 2011 . 12 . 07 +- Complete mixer engine refactoring +- Faster audio buffer allocation +- Global beat system revisited +- Autocrossfade between samples is now enabled by default +- No more recorded actions on odd frames +- Unintentional channel swapping fixed +- Unable to list all sound systems and sound devs under OSX (fixed) +- Missing graceful stop of audio streaming under OSX (fixed) + + +0.4.0 --- 2011 . 11 . 16 +- Support for all major uncompressed file formats (with libsndfile) +- Enhanced mono > stereo conversion +- Fixed drawing issues for the start/stop labels inside the waveform +- Enhanced backward compatibility with old patches +- Support for compilation on OS X and Windows + + +0.3.6 --- 2011 . 11 . 02 +- Initial Mac OS X release +- (Windows) Ability to list and browse all active drives +- Change some internal routines plus minor optimizations +- Added -pedantic and -Werror flag to the compiler +- Crash if clicking on mute in an empty channel (fixed) +- Chan status changes if an empty channel is being muted (fixed) + + +0.3.5 --- 2011 . 10 . 22 +- Pan controller added +- New GNU-style source code packaging +- Revamped .deb package +- Program icon missing under Windows (fixed) +- Crash if a sample in patch is missing from the filesystem (fixed) +- Unable to rewind to beat 1 if quantizer is on and seq stopped (fixed) +- Several minor glitches fixed + + +0.3.4 --- 2011 . 10 . 10 +- Full source code released under GPL license +- Autosmooth is now toggleable via setup +- Faster loading process of patch files +- Various internal cleanups and optimizations +- Fixed incorrect reading of boost values from patch +- Fixed a potential bug that prevented the config panel to appear +- Fixed stereo swap bug +- Minor graphical revisions + + +0.3.3 --- 2011 . 09 . 28 +- New "normalize" function +- More editing tools added inside the sample editor +- Waveform beautifications +- Fixed interaction bugs for boost and volume controls + + +0.3.2 --- 2011 . 09 . 19 +- New "mute" button inside the main window +- Waveform is now updated when the boost value changes +- Zoomin/zoomout relative to the scrollbar position +- Fixed garbage output if the volume was "-inf" (windows version) +- Fixed several rendering issues for short waveforms + + +0.3.1 --- 2011 . 09 . 12 +- Boost volume + fine volume control in sample editor +- Start/End handles inside the editor are now draggable via mouse +- Fixed scrollbar issues in sample editor +- Start/end points are now always drawn in the foreground +- Waveform no longer overflow if a value is greater than the window +- (linux) giada.conf is saved inside the hidden folder /home/.giada +- patch loading process is now faster and cleaner +- Update to rtAudio 4.0.10 + + +0.3.0 --- 2011 . 09 . 01 +- New sample editor window +- Ability to set start/end points within a sample +- Update to rtAudio 4.0.9 +- Fixed an string overflow inside a patch +- Fixed a missing memory free if a sample is unreadable +- Several internal updates and optimizations + + +0.2.7 --- 2011 . 07. 22 +- New way to handle recorded channels as loops +- Fixed retrig for backspace key (rewind) +- Enhanced rewind with quantization support +- Main and alert windows now appear centered on screen +- Sanity check against old patches without metronome information +- Rewind now affects loops in rec-reading mode + + +0.2.6 --- 2011 . 07 . 11 +- Internal metronome +- Fixed some glitches in config panel +- Minor cleanups + + +0.2.5 --- 2011 . 06 . 20 +- Configuration panel redesign +- Several new control options +- Progress feedback when loading patches +- Internal optimizations +- Updated docs + + +0.2.4 --- 2011 . 06 . 08 +- New loop repeat mode +- Ability to save patches anywhere in the filesystem +- Sub-beat management +- Sound meter has been revisited and improved +- Several patch enhancements +- Core audio optimizations + + +0.2.3 --- 2011 . 05 . 18 +- ASIO support for Windows version +- Enhanced security when reading values from a patch +- Ability to disable the recordings when the sequencer is paused +- Master volume and rec status are now saved inside the patch +- Device selection fixed and improved +- Sequencer flickering in Windows has been fixed +- Feedback added if a sample from a patch is unreadable or corrupted +- Minor internal optimizations + + +0.2.2 --- 2011 . 05 . 04 +- New open-source patch system +- A patch can now be loaded from any location of the filesystem +- Enhanced file browser coords system +- Lots of minor improvements to the sample loading/unloading procedure +- (win) Init path of file browser now starts from %userProfile%/Desktop +- Wrong handling of "/" chars fixed in config menu +- Fixed potential hangs on quit +- Fixed clicks when stopping sequencer/sample +- Minor gui beautifications + + +0.2.1 --- 2011 . 04 . 26 +- Windows version + + +0.2.0 --- 2011 . 04 . 19 +- Full JACK and ALSA support with RtAudio +- New list of sound devices in menu window +- Enhanced shutdown procedure to prevent potential crashes +- Some GUI glitches fixed +- Fixed random locks when the screensaver is active + + +0.1.8 --- 2011 . 04 . 13 +- new functions: free al samples/recordings, reset to init patch +- main menu redesign +- the file browser is now resizable +- GUI feedback for samples in play mode +- some fixes when unloading a sample + + +0.1.7 --- 2011 . 04 . 07 +- Ability to remove only action recordings or mute recordings +- Shift+key now stops the sample if the master play is deactivated +- Frame 0 was always processed at the end of the sequencer +- Minor internal improvements + + +0.1.6 --- 2011 . 03 . 29 +- Autocrossfade to prevent clicks +- Internal improvements and bugfixing + + +0.1.5 --- 2011 . 03 . 10 +- decimal bpm adjustment +- ability to shrink/expand actions when changing the global beats +- improved GUI for beats and bpm controllers +- improved routines for action management +- actions are now updated when you change bpm + + +0.1.4 --- 2011 . 03 . 04 +- ability to save recorded actions +- status box now shows if a recorded chan is deactivated +- recorder is reset correctly when you load a new patch +- minor improvements + + +0.1.3 --- 2011 . 02 . 26 +- action recorder (first implementation) +- quantization procedure slightly optimized +- minor graphical adjustments +- expanded documentation + + +0.1.2 --- 2011 . 02 . 08 +- master volume controller +- improved sound meter with more accuracy +- improved verifications when reading or writing a patch +- beat counter is now always reset to 1 after a patch is loaded +- made loading wave files more robust, plus memory optimizations +- minor crashes fixed + + +0.1.1 --- 2011 . 01 . 26 +- expansion to 32 channels +- GUI restyling +- live quantizer +- fixed wrong handling of "mute" value when loading a patch +- minor internal improvements + + +0.1.0 --- 2011 . 01 . 18 +- ability to mute channels +- stop and rewind buttons now affect only channels in loop mode +- undo for ending loops +- internal patch improvements to provide backward compatibility +- better behaviour when exceeding the total amount of available memory +- fixed random reversals of stereo field at the end of the beat bar +- fixed a potential segmentation fault when freeing a sample + + +0.0.12 --- 2011 . 01 . 11 +- ability to free a channel +- "stop" button to suspend the general program +- new "stop-to-end" mode for looped channels +- new "full stop" key combination +- enhanced mouse interaction +- minor bugfixing + + +0.0.11 --- 2010 . 12 . 28 +- customizable keys +- GUI layer optimizations and improvements +- overwrite confirmation when saving a patch +- the browser always displays the patch folder when loading a new patch +- browser url is now read-only to prevent manipulations + + +0.0.10 --- 2010 . 12 . 16 +- new "single-mode retrig" mode added +- expansion to 16 channels +- new advanced file browser with the ability to navigate the filesystem +- audio configuration now uses the "default" device, if not changed +- graphical restyling for audio channels +- fixed a random crash on startup, due to a wrong thread synch + + +0.0.9 --- 2010 . 12 . 08 +- new loop once mode +- new graphical beat meter +- rewind-program button added +- heavy buttons and controls restyling +- reinforced header verification when a new patch is opened for reading +- some bugfixing for the loading procedure of a patch +- fixed a potential crash while a new sample is being loaded + + +0.0.8 --- 2010 . 11 . 28 +- fixed a critical crash while loading a sample +- GUI warning when loading a sample or a patch into an active channel +- little optimization during the search for data into waves +- all popup windows are now modal (always on top) +- fixed a potential crash in case of malformed wave files + + +0.0.7 --- 2010 . 11 . 18 +- new peak meter with clip warning and system status report +- any "ok" button is associated to the "return" key (for fast inputs) +- graphical improvements for checkboxes, buttons, smaller fonts in browsers +- graphical feedback for missing samples +- internal optimizations + + +0.0.6 --- 2010 . 11 . 01 +- new 32 bit floating point audio engine +- support for any wave bit-rate, from 8 bit pcm to 32 float +- Giada now prompts when a sound card error occurs +- removed the hard-limiting system, now useless +- the "save patch" panel now shows the actual patchname in use +- alphabetic sort into the file browser +- fixed an annoying gui flickering +- patch volume information are now handled correctly +- minor internal optimizations +- fixed a memory leak when loading a new patch +- other memory optimizations + + +0.0.5 --- 2010 . 10 . 21 +- Patch-based system: load/save your setup from/to a binary file +- New audio configuration panel +- New configuration file (giada.conf) where to store data +- Complete implementation of the double click startup +- Fixed a bug related to the confirm-on-quit window +- Minor GUI beautifications +- Extended documentation + + +0.0.4 --- 2010 . 10 . 11 +- New internal sample-accurate loop engine +- Ability to configure the period size through ini file +- First implementation of the double click startup +- Debug information are now properly tagged, reporting the interested layer + + +0.0.3 --- 2010 . 10 . 02 +- (giada) New official logo +- (giada) Ability to load single-channel samples +- (giada) Capital letter consistency between GUI buttons +- (giada) Added "cancel" button to the browser window +- (giada) Endianness verification +- (giada) Cleanup of the audio initialization procedure +- (giada) Several internal optimization for audio playback +- (giada) ALSA layer now tells if an underrun occurs +- (giada) Internal memory allocation improvements +- (giada) Fixed an unallocated hardware parameter into ALSA configuration +- (wa) Information about wave endianness +- Added a "Requirements" section to the readme file + + +0.0.2 --- 2010 . 09 . 17 +- (giada) More visual feedbacks if a key is pressed +- (giada) Added a graphical alert if a sample is in an incorrect format +- (giada) Confirm on exit +- (giada) Graphical improvements for the browser window +- (giada) Browser window doesn't close itself anymore if a sample format is incorrect +- (giada) Added "-- no sample --" for empty channels +- (giada) Startup no longer fails if a sample from the ini file is not found +- (giada) Internal optimization for the sample loading routine +- (giada) More graphical consistency between subwindows +- (giada) The sample name is now trucated to fit into its box, preventing overflow +- (giada) Other minor GUI tweaks +- (giada) Internal memory improvements to prevent a bad bug of allocation with malformed wave files +- (wa) More information about sample size +- (wa) Added calculations and comparison between data sizes + + +0.0.1 --- 2010 . 09 . 06 +(initial release) diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..edb15a4 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,337 @@ +AUTOMAKE_OPTIONS = foreign + +# make giada ------------------------------------------------------------------- + +bin_PROGRAMS = giada + +giada_SOURCES = \ +src/main.cpp \ +src/core/const.h \ +src/core/channel.h \ +src/core/channel.cpp \ +src/core/sampleChannel.h \ +src/core/sampleChannel.cpp \ +src/core/midiChannel.h \ +src/core/midiChannel.cpp \ +src/core/midiMapConf.h \ +src/core/midiMapConf.cpp \ +src/core/conf.h \ +src/core/conf.cpp \ +src/core/kernelAudio.h \ +src/core/kernelAudio.cpp \ +src/core/pluginHost.h \ +src/core/pluginHost.cpp \ +src/core/mixerHandler.h \ +src/core/mixerHandler.cpp \ +src/core/init.h \ +src/core/init.cpp \ +src/core/plugin.h \ +src/core/plugin.cpp \ +src/core/wave.h \ +src/core/wave.cpp \ +src/core/waveFx.h \ +src/core/waveFx.cpp \ +src/core/kernelMidi.h \ +src/core/kernelMidi.cpp \ +src/core/graphics.h \ +src/core/graphics.cpp \ +src/core/patch_DEPR_.h \ +src/core/patch_DEPR_.cpp \ +src/core/patch.h \ +src/core/patch.cpp \ +src/core/recorder.h \ +src/core/recorder.cpp \ +src/core/mixer.h \ +src/core/mixer.cpp \ +src/core/dataStorageIni.h \ +src/core/dataStorageIni.cpp \ +src/core/dataStorageJson.h \ +src/core/dataStorageJson.cpp \ +src/glue/main.h \ +src/glue/main.cpp \ +src/glue/io.h \ +src/glue/io.cpp \ +src/glue/storage.h \ +src/glue/storage.cpp \ +src/glue/channel.h \ +src/glue/channel.cpp \ +src/glue/plugin.h \ +src/glue/plugin.cpp \ +src/gui/dialogs/gd_keyGrabber.h \ +src/gui/dialogs/gd_keyGrabber.cpp \ +src/gui/dialogs/gd_about.h \ +src/gui/dialogs/gd_about.cpp \ +src/gui/dialogs/gd_mainWindow.h \ +src/gui/dialogs/gd_mainWindow.cpp \ +src/gui/dialogs/gd_beatsInput.h \ +src/gui/dialogs/gd_beatsInput.cpp \ +src/gui/dialogs/gd_warnings.h \ +src/gui/dialogs/gd_warnings.cpp \ +src/gui/dialogs/gd_bpmInput.h \ +src/gui/dialogs/gd_bpmInput.cpp \ +src/gui/dialogs/gd_browser.h \ +src/gui/dialogs/gd_browser.cpp \ +src/gui/dialogs/gd_config.h \ +src/gui/dialogs/gd_config.cpp \ +src/gui/dialogs/gd_devInfo.h \ +src/gui/dialogs/gd_devInfo.cpp \ +src/gui/dialogs/gd_pluginList.h \ +src/gui/dialogs/gd_pluginList.cpp \ +src/gui/dialogs/gd_pluginWindow.h \ +src/gui/dialogs/gd_pluginWindow.cpp \ +src/gui/dialogs/gd_editor.h \ +src/gui/dialogs/gd_editor.cpp \ +src/gui/dialogs/gd_pluginWindowGUI.h \ +src/gui/dialogs/gd_pluginWindowGUI.cpp \ +src/gui/dialogs/gd_actionEditor.h \ +src/gui/dialogs/gd_actionEditor.cpp \ +src/gui/dialogs/gd_pluginChooser.h \ +src/gui/dialogs/gd_pluginChooser.cpp \ +src/gui/dialogs/midiIO/midiOutputBase.h \ +src/gui/dialogs/midiIO/midiOutputBase.cpp \ +src/gui/dialogs/midiIO/midiOutputSampleCh.h \ +src/gui/dialogs/midiIO/midiOutputSampleCh.cpp \ +src/gui/dialogs/midiIO/midiOutputMidiCh.h \ +src/gui/dialogs/midiIO/midiOutputMidiCh.cpp \ +src/gui/dialogs/midiIO/midiInputBase.h \ +src/gui/dialogs/midiIO/midiInputBase.cpp \ +src/gui/dialogs/midiIO/midiInputChannel.h \ +src/gui/dialogs/midiIO/midiInputChannel.cpp \ +src/gui/dialogs/midiIO/midiInputMaster.h \ +src/gui/dialogs/midiIO/midiInputMaster.cpp \ +src/gui/elems/midiLearner.h \ +src/gui/elems/midiLearner.cpp \ +src/gui/elems/ge_mixed.h \ +src/gui/elems/ge_mixed.cpp \ +src/gui/elems/ge_waveform.h \ +src/gui/elems/ge_waveform.cpp \ +src/gui/elems/browser.h \ +src/gui/elems/browser.cpp \ +src/gui/elems/baseActionEditor.h \ +src/gui/elems/baseActionEditor.cpp \ +src/gui/elems/envelopeEditor.h \ +src/gui/elems/envelopeEditor.cpp \ +src/gui/elems/pianoRoll.h \ +src/gui/elems/pianoRoll.cpp \ +src/gui/elems/noteEditor.h \ +src/gui/elems/noteEditor.cpp \ +src/gui/elems/pianoItem.h \ +src/gui/elems/pianoItem.cpp \ +src/gui/elems/muteEditor.h \ +src/gui/elems/muteEditor.cpp \ +src/gui/elems/actionEditor.h \ +src/gui/elems/actionEditor.cpp \ +src/gui/elems/ge_window.h \ +src/gui/elems/ge_window.cpp \ +src/gui/elems/ge_waveTools.h \ +src/gui/elems/ge_waveTools.cpp \ +src/gui/elems/ge_pluginBrowser.h \ +src/gui/elems/ge_pluginBrowser.cpp \ +src/gui/elems/mainWindow/mainIO.h \ +src/gui/elems/mainWindow/mainIO.cpp \ +src/gui/elems/mainWindow/mainMenu.h \ +src/gui/elems/mainWindow/mainMenu.cpp \ +src/gui/elems/mainWindow/mainTimer.h \ +src/gui/elems/mainWindow/mainTimer.cpp \ +src/gui/elems/mainWindow/mainTransport.h \ +src/gui/elems/mainWindow/mainTransport.cpp \ +src/gui/elems/mainWindow/beatMeter.h \ +src/gui/elems/mainWindow/beatMeter.cpp \ +src/gui/elems/mainWindow/keyboard/channelMode.h \ +src/gui/elems/mainWindow/keyboard/channelMode.cpp \ +src/gui/elems/mainWindow/keyboard/channelButton.h \ +src/gui/elems/mainWindow/keyboard/channelButton.cpp \ +src/gui/elems/mainWindow/keyboard/channelStatus.h \ +src/gui/elems/mainWindow/keyboard/channelStatus.cpp \ +src/gui/elems/mainWindow/keyboard/keyboard.h \ +src/gui/elems/mainWindow/keyboard/keyboard.cpp \ +src/gui/elems/mainWindow/keyboard/column.h \ +src/gui/elems/mainWindow/keyboard/column.cpp \ +src/gui/elems/mainWindow/keyboard/sampleChannel.h \ +src/gui/elems/mainWindow/keyboard/sampleChannel.cpp \ +src/gui/elems/mainWindow/keyboard/midiChannel.h \ +src/gui/elems/mainWindow/keyboard/midiChannel.cpp \ +src/gui/elems/mainWindow/keyboard/channel.h \ +src/gui/elems/mainWindow/keyboard/channel.cpp \ +src/gui/elems/basics/scroll.h \ +src/gui/elems/basics/scroll.cpp \ +src/gui/elems/basics/boxtypes.h \ +src/gui/elems/basics/boxtypes.cpp \ +src/utils/log.h \ +src/utils/log.cpp \ +src/utils/gui.h \ +src/utils/gui.cpp \ +src/utils/gvector.h \ +src/utils/fs.h \ +src/utils/fs.cpp \ +src/utils/string.h \ +src/utils/string.cpp + +if WITH_VST +giada_SOURCES += \ +src/deps/juce/modules/juce_audio_basics/juce_audio_basics.cpp \ +src/deps/juce/modules/juce_audio_processors/juce_audio_processors.cpp \ +src/deps/juce/modules/juce_core/juce_core.cpp \ +src/deps/juce/modules/juce_data_structures/juce_data_structures.cpp \ +src/deps/juce/modules/juce_events/juce_events.cpp \ +src/deps/juce/modules/juce_graphics/juce_graphics.cpp \ +src/deps/juce/modules/juce_gui_basics/juce_gui_basics.cpp \ +src/deps/juce/modules/juce_gui_extra/juce_gui_extra.cpp +endif + +# Check for environment: WITH_VST, LINUX, WINDOWS and OSX are varibles defined +# via AM_CONDITIONAL inside configure.ac. +# Note: CPPFLAGS = C preprocessor flags, CXXFLAGS = C++ compiler flags. + +giada_CXXFLAGS = -std=c++11 -Wall -Werror +giada_CPPFLAGS = + +# TODO - these are flags for Linux only! +# Also, JUCE makes GCC complain if compiled with optimization set to -O2. +# Call configure script as follows: +# +# ./configure CXXFLAGS='-g -O1 -pedantic' --target=linux --enable-vst +# + +if WITH_VST +giada_CPPFLAGS += \ + -I./src/deps/juce/modules \ + -I./src/deps/vst \ + -I/usr/include \ + -I/usr/include/freetype2 \ + -DJUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1 \ + -DJUCE_STANDALONE_APPLICATION=1 \ + -DJUCE_PLUGINHOST_VST=1 \ + -DJUCE_PLUGINHOST_VST3=0 \ + -DJUCE_PLUGINHOST_AU=0 +endif + +if LINUX +giada_SOURCES += src/deps/rtaudio-mod/RtAudio.h src/deps/rtaudio-mod/RtAudio.cpp +# -Wno-error=vla: mute rtAudio error on variable length array +# -Wno-error=misleading-indentation: mute JUCE warnings on GCC6 +giada_CXXFLAGS += -Wno-error=vla -Wno-error=misleading-indentation +giada_CPPFLAGS += -D__LINUX_ALSA__ -D__LINUX_PULSE__ -D__UNIX_JACK__ +giada_LDADD = -lsndfile -lfltk -lXext -lX11 -lXft -lXpm -lm -ljack -lasound \ + -lpthread -ldl -lpulse-simple -lpulse -lsamplerate -lrtmidi -ljansson \ + -lfreetype +endif + +if WINDOWS +giada_SOURCES += \ +src/deps/rtaudio-mod/RtAudio.h \ +src/deps/rtaudio-mod/RtAudio.cpp \ +src/deps/rtaudio-mod/include/asio.h \ +src/deps/rtaudio-mod/include/asio.cpp \ +src/deps/rtaudio-mod/include/asiolist.h \ +src/deps/rtaudio-mod/include/asiolist.cpp \ +src/deps/rtaudio-mod/include/asiodrivers.h \ +src/deps/rtaudio-mod/include/asiodrivers.cpp \ +src/deps/rtaudio-mod/include/iasiothiscallresolver.h \ +src/deps/rtaudio-mod/include/iasiothiscallresolver.cpp +# -Wno-error=misleading-indentation: mute JUCE warnings on GCC6 +# -Wno-error=unused-but-set-variable: silence ASIO errors +giada_CXXFLAGS += \ +-Wno-error=misleading-indentation \ +-Wno-error=unused-but-set-variable +giada_CPPFLAGS += \ +-I./src/deps/rtaudio-mod/include \ +-D__WINDOWS_ASIO__ \ +-D__WINDOWS_WASAPI__ \ +-D__WINDOWS_DS__ +giada_LDADD = -ldsound -lwsock32 -lm -lfltk -lwininet -lgdi32 \ + -lshell32 -lvfw32 -lrpcrt4 -luuid -lcomctl32 -lole32 -lws2_32 -lsndfile \ + -lsamplerate -lrtmidi -lwinmm -lsetupapi -lksuser -lpthreadGC2 -ljansson \ + -limm32 -lglu32 -lshell32 -lversion -lopengl32 -loleaut32 -lshlwapi -lcomdlg32 +giada_LDFLAGS = -mwindows -static +giada_SOURCES += resource.rc +endif + +if OSX +# for 32 bit compilation: +# export CXXFLAGS="-m32" +# export LDFLAGS="-m32" +# -ObjC++: Juce requires to build some Objective C code +# -Wno-unknown-pragmas: shut up Juce even more +giada_SOURCES += src/utils/cocoa.mm src/utils/cocoa.h +giada_CXXFLAGS += -ObjC++ -Wno-unknown-pragmas +giada_LDADD = -lsndfile -lm -lpthread -lfltk -lrtmidi -lrtaudio \ + -lsamplerate -ljansson +giada_LDFLAGS = -framework CoreAudio -framework Cocoa -framework Carbon \ + -framework CoreMIDI -framework CoreFoundation -framework Accelerate \ + -framework WebKit -framework QuartzCore -framework IOKit +endif + +# used only under MinGW to compile the resource.rc file (program icon) + +resource.o: + windres src/ext/resource.rc -o resource.o + +# make test -------------------------------------------------------------------- + +TESTS = giada_tests +check_PROGRAMS = giada_tests +giada_tests_SOURCES = \ +tests/main.cpp \ +tests/conf.cpp \ +tests/wave.cpp \ +tests/patch.cpp \ +tests/midiMapConf.cpp \ +tests/pluginHost.cpp \ +tests/utils.cpp \ +src/core/conf.cpp \ +src/core/wave.cpp \ +src/core/midiMapConf.cpp \ +src/core/patch.cpp \ +src/core/plugin.cpp \ +src/core/dataStorageIni.cpp \ +src/core/dataStorageJson.cpp \ +src/utils/fs.cpp \ +src/utils/string.cpp \ +src/utils/log.cpp + +if WITH_VST +giada_tests_SOURCES += \ +src/deps/juce/modules/juce_audio_basics/juce_audio_basics.cpp \ +src/deps/juce/modules/juce_audio_processors/juce_audio_processors.cpp \ +src/deps/juce/modules/juce_core/juce_core.cpp \ +src/deps/juce/modules/juce_data_structures/juce_data_structures.cpp \ +src/deps/juce/modules/juce_events/juce_events.cpp \ +src/deps/juce/modules/juce_graphics/juce_graphics.cpp \ +src/deps/juce/modules/juce_gui_basics/juce_gui_basics.cpp \ +src/deps/juce/modules/juce_gui_extra/juce_gui_extra.cpp +endif + +giada_tests_LDADD = -ljansson -lsndfile -lsamplerate -lfltk -lXext -lX11 -lXft \ + -lXpm -lm -ljack -lasound -lpthread -ldl -lpulse-simple -lpulse -lrtmidi \ + -lfreetype + +giada_tests_CXXFLAGS = -std=c++11 + +if WITH_VST +giada_tests_CPPFLAGS = \ + -I./src/deps/juce/modules \ + -I./src/deps/vst \ + -I/usr/include \ + -I/usr/include/freetype2 \ + -DJUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1 \ + -DJUCE_STANDALONE_APPLICATION=1 \ + -DJUCE_PLUGINHOST_VST=1 \ + -DJUCE_PLUGINHOST_VST3=0 \ + -DJUCE_PLUGINHOST_AU=0 +endif + +# make rename ------------------------------------------------------------------ + +if LINUX +rename: + mv giada giada_lin +endif +if WINDOWS +rename: + mv giada giada_win.exe +endif +if OSX +rename: + mv giada giada_osx +endif diff --git a/README.md b/README.md new file mode 100644 index 0000000..a35a25d --- /dev/null +++ b/README.md @@ -0,0 +1,66 @@ +# Giada - Your Hardcore Loopmachine + +Official website: http://www.giadamusic.com | Travis CI status: [![Build Status](https://travis-ci.org/monocasual/giada.svg?branch=master)](https://travis-ci.org/monocasual/giada) + +## What is Giada? + +Giada is a free, minimal, hardcore audio tool for DJs, live performers and electronic musicians. How does it work? Just pick up your channel, fill it with samples or MIDI events and start the show by using this tiny piece of software as a loop machine, drum machine, sequencer, live sampler or yet as a plugin/effect host. Giada aims to be a compact and portable virtual device for Linux, Mac OS X and Windows for production use and live sets. + +➔➔➔ [See Giada in action!](http://www.youtube.com/user/GiadaLoopMachine) + +![Giada Loop Machine screenshot](http://giadamusic.com/public/img/screenshots/giada-loop-machine-screenshot-14-carousel.jpg) + +## Main features + +* Ultra-lightweight internal design; +* multi-thread/multi-core support; +* 32-bit floating point audio engine; +* ALSA, JACK + Transport, CoreAudio, ASIO and DirectSound full support; +* high quality internal resampler; +* unlimited number of channels (controllable via computer keyboard); +* several playback modes and combinations; +* BPM and beat sync with sample-accurate loop engine; +* VST and VSTi (instrument) plugin support; +* MIDI input and output support, featuring custom [MIDI lightning messages](https://github.com/monocasual/giada-midimaps); +* super-sleek, built-in wave editor; +* live sampler from external inputs; +* live action recorder with automatic quantizer; +* piano Roll editor; +* portable patch storage system, based on super-hackable JSON files; +* support for all major uncompressed file formats; +* test-driven development style supported by [Travis CI](https://travis-ci.org/monocasual/giada) and [Catch](https://github.com/philsquared/Catch) +* under a constant stage of development; +* 100% open-source GPL v3. + +## License + +Giada is available under the terms of the GNU General Public License. +Take a look at the COPYING file for further informations. + +## Documentation + +Docs are available online on the official website: + +http://www.giadamusic.com/documentation + +Found a typo or a terrible mistake? Feel free to clone the [website repository](https://github.com/monocasual/giada-www) and send us your pull requests. + +## Build Giada from source + +We do our best to make the compilation process as simple as possible. You can find all the information in the [official docs page](http://giadamusic.com/documentation/show/compiling-from-source). + +## Bugs, requests and questions for non-developers + +Feel free to ask anything on our end-user forum: + +http://www.giadamusic.com/forum + +## Copyright + +Giada is Copyright (C) 2010-2016 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, if present, will be passed to gcc as -DWITH_VST +# +# AC_ARG_ENABLE ( +# feature, [--enable-] + [feature], eg --enable-vst +# help-string, +# [action-if-given], == gcc ... -DWITH_VST +# [action-if-not-given]) not used here + +AC_ARG_ENABLE( + [vst], + AS_HELP_STRING([--enable-vst], [enable vst support]), + [AC_DEFINE(WITH_VST) AM_CONDITIONAL(WITH_VST, true)], + [AM_CONDITIONAL(WITH_VST, false)] +) + +# ------------------------------------------------------------------------------ + +# test if files needed for Travis CI are present. If so, define a new macro +# RUN_TESTS_WITH_LOCAL_FILES used during the test suite + +if test -f "giada-midimaps-master.zip" && test -f "dexed.tar.xz" ; then + AC_DEFINE(RUN_TESTS_WITH_LOCAL_FILES) +fi + +# ------------------------------------------------------------------------------ + +# Check for C++ compiler + +AC_PROG_CXX + +# Check for Objective-C++ compiler + +AC_PROG_OBJCXX + +# Check for C compiler (TODO - is that really needed?) + +AC_PROG_CC + +# Check for make + +AC_PROG_MAKE_SET + +# ------------------------------------------------------------------------------ + +# Check for libraries. + +AC_CHECK_LIB( + [pthread], + [pthread_exit], + [], + [AC_MSG_ERROR([error: library 'pthread' not found!])] +) + +# ------------------------------------------------------------------------------ + +# Check for generic headers (fltk, rtaudio and libsndfile are static, +# we ask if headers are available) + +AC_LANG_PUSH([C++]) +AC_CHECK_HEADER( + [FL/Fl.H], + [], + [AC_MSG_ERROR([library 'fltk' not found!])] +) +AC_LANG_POP + +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( + [jansson.h], + [], + [AC_MSG_ERROR([library 'Jansson' not found!])] +) +AC_LANG_POP + +AC_LANG_PUSH([C++]) +AC_CHECK_HEADER( + [sndfile.h], + [], + [AC_MSG_ERROR([library 'libsndfile' not found!])] +) +AC_LANG_POP + +#~ AC_LANG_PUSH([C++]) +#~ AC_CHECK_HEADER( + #~ [RtAudio.h], + #~ [], + #~ [AC_MSG_ERROR([library 'RtAudio' not found!])] +#~ ) +#~ AC_LANG_POP + +# 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 + + AC_LANG_PUSH([C++]) + AC_CHECK_HEADER( + [X11/xpm.h], + [], + [AC_MSG_ERROR([missing xpm.h, maybe you need to install the libxpm-dev package?])] + ) + AC_LANG_POP +fi + +# ------------------------------------------------------------------------------ + +# finalizing + +AC_CONFIG_FILES([Makefile]) +AC_OUTPUT diff --git a/src/core/.dirstamp b/src/core/.dirstamp new file mode 100644 index 0000000..e69de29 diff --git a/src/core/channel.cpp b/src/core/channel.cpp new file mode 100644 index 0000000..95ad99a --- /dev/null +++ b/src/core/channel.cpp @@ -0,0 +1,425 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * channel + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include "../utils/log.h" +#include "../gui/elems/mainWindow/keyboard/channel.h" +#include "channel.h" +#include "pluginHost.h" +#include "plugin.h" +#include "kernelMidi.h" +#include "patch_DEPR_.h" +#include "patch.h" +#include "wave.h" +#include "mixer.h" +#include "mixerHandler.h" +#include "conf.h" +#include "patch.h" +#include "waveFx.h" +#include "midiMapConf.h" + + +extern Recorder G_Recorder; +extern KernelMidi G_KernelMidi; + + +Channel::Channel(int type, int status, int bufferSize, MidiMapConf *midiMapConf) +#if defined(WITH_VST) +: pluginHost(nullptr), +#else +: +#endif + midiMapConf (midiMapConf), + 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), + readActions (false), + armed (false), + recStatus (REC_STOPPED), + vChan (nullptr), + guiChannel (nullptr), + midiIn (true), + midiInKeyPress (0x0), + midiInKeyRel (0x0), + midiInKill (0x0), + midiInArm (0x0), + midiInVolume (0x0), + midiInMute (0x0), + midiInSolo (0x0), + midiOutL (false), + midiOutLplaying(0x0), + midiOutLmute (0x0), + midiOutLsolo (0x0) +{ + vChan = (float *) malloc(bufferSize * sizeof(float)); + if (!vChan) + gu_log("[Channel::allocVchan] unable to alloc memory for vChan\n"); + memset(vChan, 0, bufferSize * sizeof(float)); +} + + +/* -------------------------------------------------------------------------- */ + + +Channel::~Channel() +{ + status = STATUS_OFF; + if (vChan) + free(vChan); +} + + +/* -------------------------------------------------------------------------- */ + + +void Channel::copy(const Channel *src, pthread_mutex_t *pluginMutex) +{ + key = src->key; + volume = src->volume; + volume_i = src->volume_i; + volume_d = src->volume_d; + panLeft = src->panLeft; + panRight = src->panRight; + mute_i = src->mute_i; + mute_s = src->mute_s; + mute = src->mute; + solo = src->solo; + hasActions = src->hasActions; + recStatus = src->recStatus; + midiIn = src->midiIn; + midiInKeyPress = src->midiInKeyPress; + midiInKeyRel = src->midiInKeyRel; + midiInKill = src->midiInKill; + midiInArm = src->midiInArm; + midiInVolume = src->midiInVolume; + midiInMute = src->midiInMute; + midiInSolo = src->midiInSolo; + midiOutL = src->midiOutL; + midiOutLplaying = src->midiOutLplaying; + midiOutLmute = src->midiOutLmute; + midiOutLsolo = src->midiOutLsolo; + + /* clone plugins */ + +#ifdef WITH_VST + for (unsigned i=0; iplugins.size(); i++) + pluginHost->clonePlugin(src->plugins.at(i), PluginHost::CHANNEL, + pluginMutex, this); +#endif + + /* clone actions */ + + for (unsigned i=0; ichan == src->index) { + G_Recorder.rec(index, a->type, a->frame, a->iValue, a->fValue); + hasActions = true; + } + } + } +} + + +/* -------------------------------------------------------------------------- */ + + +void Channel::sendMidiLmessage(uint32_t learn, const MidiMapConf::message_t &msg) +{ + gu_log("[channel::sendMidiLmessage] learn=%#X, chan=%d, msg=%#X, offset=%d\n", + learn, msg.channel, msg.value, msg.offset); + + /* isolate 'channel' from learnt message and offset it as requested by 'nn' + * in the midimap configuration file. */ + + uint32_t out = ((learn & 0x00FF0000) >> 16) << msg.offset; + + /* merge the previously prepared channel into final message, and finally + * send it. */ + + out |= msg.value | (msg.channel << 24); + G_KernelMidi.send(out); +} + + +/* -------------------------------------------------------------------------- */ + + +void Channel::readPatchMidiIn_DEPR_(int i, Patch_DEPR_ &patch) +{ + midiIn = patch.getMidiValue(i, "In"); + midiInKeyPress = patch.getMidiValue(i, "InKeyPress"); + midiInKeyRel = patch.getMidiValue(i, "InKeyRel"); + midiInKill = patch.getMidiValue(i, "InKill"); + midiInVolume = patch.getMidiValue(i, "InVolume"); + midiInMute = patch.getMidiValue(i, "InMute"); + midiInSolo = patch.getMidiValue(i, "InSolo"); +} + +void Channel::readPatchMidiOut_DEPR_(int i, Patch_DEPR_ &patch) +{ + midiOutL = patch.getMidiValue(i, "OutL"); + midiOutLplaying = patch.getMidiValue(i, "OutLplaying"); + midiOutLmute = patch.getMidiValue(i, "OutLmute"); + midiOutLsolo = patch.getMidiValue(i, "OutLsolo"); +} + + +/* -------------------------------------------------------------------------- */ + + +bool Channel::isPlaying() +{ + return status & (STATUS_PLAY | STATUS_ENDING); +} + + +/* -------------------------------------------------------------------------- */ + + +int Channel::writePatch(int i, bool isProject, Patch *patch) +{ + Patch::channel_t pch; + pch.type = type; + pch.key = key; + pch.index = index; + pch.column = guiChannel->getColumnIndex(); + pch.mute = mute; + pch.mute_s = mute_s; + pch.solo = solo; + pch.volume = volume; + pch.panLeft = panLeft; + pch.panRight = panRight; + pch.midiIn = midiIn; + pch.midiInKeyPress = midiInKeyPress; + pch.midiInKeyRel = midiInKeyRel; + pch.midiInKill = midiInKill; + pch.midiInArm = midiInArm; + pch.midiInVolume = midiInVolume; + pch.midiInMute = midiInMute; + pch.midiInSolo = midiInSolo; + pch.midiOutL = midiOutL; + pch.midiOutLplaying = midiOutLplaying; + pch.midiOutLmute = midiOutLmute; + pch.midiOutLsolo = midiOutLsolo; + + for (unsigned i=0; ichan == index) { + Patch::action_t pac; + pac.type = action->type; + pac.frame = action->frame; + pac.fValue = action->fValue; + pac.iValue = action->iValue; + pch.actions.push_back(pac); + } + } + } + +#ifdef WITH_VST + + unsigned numPlugs = pluginHost->countPlugins(PluginHost::CHANNEL, this); + for (unsigned i=0; igetPluginByIndex(i, PluginHost::CHANNEL, this); + Patch::plugin_t pp; + pp.path = pPlugin->getUniqueId(); + pp.bypass = pPlugin->isBypassed(); + for (int k=0; kgetNumParameters(); k++) + pp.params.push_back(pPlugin->getParameter(k)); + for (unsigned k=0; kmidiInParams.size(); k++) + pp.midiInParams.push_back(pPlugin->midiInParams.at(k)); + pch.plugins.push_back(pp); + } + +#endif + + patch->channels.push_back(pch); + + return patch->channels.size() - 1; +} + + +/* -------------------------------------------------------------------------- */ + + +int Channel::readPatch(const string &path, int i, Patch *patch, + pthread_mutex_t *pluginMutex, int samplerate, int rsmpQuality) +{ + int ret = 1; + Patch::channel_t *pch = &patch->channels.at(i); + key = pch->key; + type = pch->type; + index = pch->index; + mute = pch->mute; + mute_s = pch->mute_s; + solo = pch->solo; + volume = pch->volume; + panLeft = pch->panLeft; + panRight = pch->panRight; + midiIn = pch->midiIn; + midiInKeyPress = pch->midiInKeyPress; + midiInKeyRel = pch->midiInKeyRel; + midiInKill = pch->midiInKill; + midiInVolume = pch->midiInVolume; + midiInMute = pch->midiInMute; + midiInSolo = pch->midiInSolo; + midiOutL = pch->midiOutL; + midiOutLplaying = pch->midiOutLplaying; + midiOutLmute = pch->midiOutLmute; + midiOutLsolo = pch->midiOutLsolo; + + for (unsigned k=0; kactions.size(); k++) { + Patch::action_t *ac = &pch->actions.at(k); + G_Recorder.rec(index, ac->type, ac->frame, ac->iValue, ac->fValue); + hasActions = true; + } + +#ifdef WITH_VST + + for (unsigned k=0; kplugins.size(); k++) { + Patch::plugin_t *ppl = &pch->plugins.at(k); + Plugin *plugin = pluginHost->addPlugin(ppl->path, PluginHost::CHANNEL, + pluginMutex, this); + if (plugin == nullptr) { + ret &= 0; + continue; + } + plugin->setBypass(ppl->bypass); + for (unsigned j=0; jparams.size(); j++) + plugin->setParameter(j, ppl->params.at(j)); + plugin->midiInParams.clear(); + for (unsigned j=0; jmidiInParams.size(); j++) + plugin->midiInParams.push_back(ppl->midiInParams.at(j)); + ret &= 1; + } + +#endif + + return ret; +} + + +/* -------------------------------------------------------------------------- */ + + +void Channel::sendMidiLmute() +{ + if (!midiOutL || midiOutLmute == 0x0) + return; + if (mute) + sendMidiLmessage(midiOutLsolo, midiMapConf->muteOn); + else + sendMidiLmessage(midiOutLsolo, midiMapConf->muteOff); +} + + +/* -------------------------------------------------------------------------- */ + + +void Channel::sendMidiLsolo() +{ + if (!midiOutL || midiOutLsolo == 0x0) + return; + if (solo) + sendMidiLmessage(midiOutLsolo, midiMapConf->soloOn); + else + sendMidiLmessage(midiOutLsolo, midiMapConf->soloOff); +} + + +/* -------------------------------------------------------------------------- */ + + +void Channel::sendMidiLplay() +{ + if (!midiOutL || midiOutLplaying == 0x0) + return; + switch (status) { + case STATUS_OFF: + sendMidiLmessage(midiOutLplaying, midiMapConf->stopped); + break; + case STATUS_PLAY: + sendMidiLmessage(midiOutLplaying, midiMapConf->playing); + break; + case STATUS_WAIT: + sendMidiLmessage(midiOutLplaying, midiMapConf->waiting); + break; + case STATUS_ENDING: + sendMidiLmessage(midiOutLplaying, midiMapConf->stopping); + } +} + + +/* -------------------------------------------------------------------------- */ + + +void Channel::receiveMidi(uint32_t msg) +{ +} + +/* -------------------------------------------------------------------------- */ + + +#ifdef WITH_VST + +juce::MidiBuffer &Channel::getPluginMidiEvents() +{ + return midiBuffer; +} + + +/* -------------------------------------------------------------------------- */ + + +void Channel::clearMidiBuffer() +{ + midiBuffer.clear(); +} + + +/* -------------------------------------------------------------------------- */ + + +void Channel::setPluginHost(PluginHost *pluginHost) +{ + this->pluginHost = pluginHost; +} + +#endif diff --git a/src/core/channel.h b/src/core/channel.h new file mode 100644 index 0000000..2df3d14 --- /dev/null +++ b/src/core/channel.h @@ -0,0 +1,289 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * channel + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License 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 +#include +#include "midiMapConf.h" +#include "recorder.h" + +#ifdef WITH_VST + #include "../deps/juce-config.h" +#endif + + +using std::vector; +using std::string; + + +class Channel +{ +protected: + +#ifdef WITH_VST + + /* pluginHost + * Pointer to PluginHost class, which manages and processes plugins. */ + + class PluginHost *pluginHost; + + /* MidiBuffer contains MIDI events. When ready, events are sent to + * each plugin in the channel. This is available for any kind of + * channel, but it makes sense only for MIDI channels. */ + + juce::MidiBuffer midiBuffer; + +#endif + + /* MidiMapConf + * Pointer to MidiMapConf. It deals with Midi lightning operations. */ + + class MidiMapConf *midiMapConf; + + /* bufferSize + * size of every buffer in this channel (vChan, pChan) */ + + int bufferSize; + + /* sendMidiLMessage + * compose a MIDI message by merging bytes from MidiMap conf class, and send + * it to KernelMidi. */ + + void sendMidiLmessage(uint32_t learn, const MidiMapConf::message_t &msg); + +public: + + Channel(int type, int status, int bufferSize, class MidiMapConf *midiMapConf); + + virtual ~Channel(); + + /* copy + * Make a shallow copy (no vChan/pChan allocation) of another channel. */ + + virtual void copy(const Channel *src, pthread_mutex_t *pluginMutex) = 0; + + /* readPatch + * Fill channel with data from patch. */ + + virtual int readPatch_DEPR_(const char *file, int i, class Patch_DEPR_ *patch, + int samplerate, int rsmpQuality) = 0; + virtual int readPatch(const string &basePath, int i, class Patch *patch, + pthread_mutex_t *pluginMutex, int samplerate, int rsmpQuality); + + /* process + Merges vChannels into buffer, plus plugin processing (if any). Warning: + inBuffer might be nullptr if no input devices are available for recording. */ + + virtual void process(float *outBuffer, float *inBuffer) = 0; + + /* start + Action to do when channel starts. doQuantize = false (don't quantize) + when Mixer is reading actions from Recorder. If isUserGenerated means that + the channel has been started by a human key press and not a pre-recorded + action. */ + + virtual void start(int frame, bool doQuantize, int quantize, + bool mixerIsRunning, bool forceStart, bool isUserGenerated) = 0; + + /* stop + * 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(bool chansStopOnSeqHalt) = 0; + + /* quantize + * start channel according to quantizer. Index = array index of + * mixer::channels, used by recorder. LocalFrame = frame within the current + * buffer. */ + + virtual void quantize(int index, int localFrame, class Mixer *m) = 0; + + /* onZero + * action to do when frame goes to zero, i.e. sequencer restart. */ + + virtual void onZero(int frame, bool recsStopOnChanHalt) = 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 */ + + // TODO - quantize is useless! + + virtual void parseAction(Recorder::action *a, int localFrame, int globalFrame, + int quantize, bool mixerIsRunning) = 0; + + /* rewind + * rewind channel when rewind button is pressed. */ + + virtual void rewind() = 0; + + /* clear + Clears all memory buffers. This is actually useful to sample channels only. */ + + virtual void clear() = 0; + + /* canInputRec + Tells whether a channel can accept and handle input audio. Always false for + Midi channels, true for Sample channels only if they don't contain a + sample yet.*/ + + virtual bool canInputRec() = 0; + + /* writePatch + * Fill a patch with channel values. Returns the index of the last + * Patch::channel_t added. */ + + virtual int writePatch(int i, bool isProject, class Patch *patch); + + /* receiveMidi + * Receives and processes midi messages from external devices. */ + + virtual void receiveMidi(uint32_t msg); + + /* ------------------------------------------------------------------------ */ + + 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 + bool readActions; // read what's recorded + bool armed; // armed for recording + int recStatus; // status of recordings (waiting, ending, ...) + float *vChan; // virtual channel + class geChannel *guiChannel; // pointer to a gChannel object, part of the GUI + + // TODO - midi structs, please + + bool midiIn; // enable midi input + uint32_t midiInKeyPress; + uint32_t midiInKeyRel; + uint32_t midiInKill; + uint32_t midiInArm; + uint32_t midiInVolume; + uint32_t midiInMute; + uint32_t midiInSolo; + + /* midiOutL* + * Enable MIDI lightning output, plus a set of midi lighting event to be sent + * to a device. Those events basically contains the MIDI channel, everything + * else gets stripped out. */ + + bool midiOutL; + uint32_t midiOutLplaying; + uint32_t midiOutLmute; + uint32_t midiOutLsolo; + +#ifdef WITH_VST + vector 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_DEPR_(int i, class Patch_DEPR_ &patch); + void readPatchMidiOut_DEPR_(int i, class Patch_DEPR_ &patch); + + /* sendMidiL* + * send MIDI lightning events to a physical device. */ + + void sendMidiLmute(); + void sendMidiLsolo(); + void sendMidiLplay(); + +#ifdef WITH_VST + + /* SetPluginHost + * A neat trick to avoid duplicated constructors (with and without pointer + * to PluginHost). */ + + void setPluginHost(class PluginHost *pluginHost); + + /* getPluginMidiEvents + * Return a reference to midiBuffer stack. This is available for any kind of + * channel, but it makes sense only for MIDI channels. */ + + juce::MidiBuffer &getPluginMidiEvents(); + + void clearMidiBuffer(); + +#endif +}; + + +#endif diff --git a/src/core/conf.cpp b/src/core/conf.cpp new file mode 100644 index 0000000..1a690ac --- /dev/null +++ b/src/core/conf.cpp @@ -0,0 +1,449 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * conf + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License 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 "conf.h" +#include "const.h" +#include "../utils/fs.h" +#include "../utils/log.h" + + +using std::string; + + +Conf::Conf() +{ + /* Initialize confFilePath, i.e. the configuration file. In windows it is in + * the same dir of the .exe, while in Linux and OS X in ~/.giada */ + +#if defined(__linux__) || defined(__APPLE__) + + confFilePath = gu_getHomePath() + G_SLASH + CONF_FILENAME; + confDirPath = gu_getHomePath() + G_SLASH; + +#elif defined(_WIN32) + + confFilePath = CONF_FILENAME; + confDirPath = ""; + +#endif +} + + +/* -------------------------------------------------------------------------- */ + + +int Conf::createConfigFolder() +{ +#if defined(__linux__) || defined(__APPLE__) + + if (gu_dirExists(confDirPath)) + return 1; + + gu_log("[Conf::createConfigFolder] .giada folder not present. Updating...\n"); + + if (gu_mkdir(confDirPath)) { + gu_log("[Conf::createConfigFolder] status: ok\n"); + return 1; + } + else { + gu_log("[Conf::createConfigFolder] status: error!\n"); + return 0; + } + +#else // windows + + return 1; + +#endif +} + + +/* -------------------------------------------------------------------------- */ + + +void Conf::init() +{ + header = "GIADACFG"; + 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; + midiMapPath = ""; + 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 = ""; + patchPath = ""; + samplePath = ""; + + recsStopOnChanHalt = false; + chansStopOnSeqHalt = false; + treatRecsAsLoops = false; + + resizeRecordings = true; + + mainWindowX = 0; + mainWindowY = 0; + mainWindowW = GUI_WIDTH; + mainWindowH = GUI_HEIGHT; + + browserX = 0; + browserY = 0; + browserW = 640; + browserH = 480; + browserPosition = 0; + browserLastValue = 0; + + actionEditorX = 0; + actionEditorY = 0; + actionEditorW = 640; + actionEditorH = 480; + actionEditorZoom = 100; + actionEditorGridOn = false; + actionEditorGridVal = 1; + + sampleEditorX = 0; + sampleEditorY = 0; + sampleEditorW = 640; + sampleEditorH = 480; + + midiInputX = 0; + midiInputY = 0; + midiInputW = G_DEFAULT_MIDI_INPUT_UI_W; + midiInputH = G_DEFAULT_MIDI_INPUT_UI_H; + + pianoRollY = -1; + pianoRollH = 422; + + #ifdef WITH_VST + + pluginChooserX = 0; + pluginChooserY = 0; + pluginChooserW = 640; + pluginChooserH = 480; + pluginSortMethod = 0; + + #endif +} + + +/* -------------------------------------------------------------------------- */ + + +int Conf::read() +{ + init(); + + jRoot = json_load_file(confFilePath.c_str(), 0, &jError); + if (!jRoot) { + gu_log("[Conf::read] unable to read configuration file! Error on line %d: %s\n", jError.line, jError.text); + return 0; + } + + if (!checkObject(jRoot, "root element")) { + json_decref(jRoot); + return 0; + } + + if (!setString(jRoot, CONF_KEY_HEADER, header)) return 0; + if (!setInt(jRoot, CONF_KEY_LOG_MODE, logMode)) return 0; + if (!setInt(jRoot, CONF_KEY_SOUND_SYSTEM, soundSystem)) return 0; + if (!setInt(jRoot, CONF_KEY_SOUND_DEVICE_OUT, soundDeviceOut)) return 0; + if (!setInt(jRoot, CONF_KEY_SOUND_DEVICE_IN, soundDeviceIn)) return 0; + if (!setInt(jRoot, CONF_KEY_CHANNELS_OUT, channelsOut)) return 0; + if (!setInt(jRoot, CONF_KEY_CHANNELS_IN, channelsIn)) return 0; + if (!setInt(jRoot, CONF_KEY_SAMPLERATE, samplerate)) return 0; + if (!setInt(jRoot, CONF_KEY_BUFFER_SIZE, buffersize)) return 0; + if (!setInt(jRoot, CONF_KEY_DELAY_COMPENSATION, delayComp)) return 0; + if (!setBool(jRoot, CONF_KEY_LIMIT_OUTPUT, limitOutput)) return 0; + if (!setInt(jRoot, CONF_KEY_RESAMPLE_QUALITY, rsmpQuality)) return 0; + if (!setInt(jRoot, CONF_KEY_MIDI_SYSTEM, midiSystem)) return 0; + if (!setInt(jRoot, CONF_KEY_MIDI_PORT_OUT, midiPortOut)) return 0; + if (!setInt(jRoot, CONF_KEY_MIDI_PORT_IN, midiPortIn)) return 0; + if (!setBool(jRoot, CONF_KEY_NO_NOTE_OFF, noNoteOff)) return 0; + if (!setString(jRoot, CONF_KEY_MIDIMAP_PATH, midiMapPath)) return 0; + if (!setString(jRoot, CONF_KEY_LAST_MIDIMAP, lastFileMap)) return 0; + if (!setInt(jRoot, CONF_KEY_MIDI_SYNC, midiSync)) return 0; + if (!setFloat(jRoot, CONF_KEY_MIDI_TC_FPS, midiTCfps)) return 0; + if (!setUint32(jRoot, CONF_KEY_MIDI_IN_REWIND, midiInRewind)) return 0; + if (!setUint32(jRoot, CONF_KEY_MIDI_IN_START_STOP, midiInStartStop)) return 0; + if (!setUint32(jRoot, CONF_KEY_MIDI_IN_ACTION_REC, midiInActionRec)) return 0; + if (!setUint32(jRoot, CONF_KEY_MIDI_IN_INPUT_REC, midiInInputRec)) return 0; + if (!setUint32(jRoot, CONF_KEY_MIDI_IN_METRONOME, midiInMetronome)) return 0; + if (!setUint32(jRoot, CONF_KEY_MIDI_IN_VOLUME_IN, midiInVolumeIn)) return 0; + if (!setUint32(jRoot, CONF_KEY_MIDI_IN_VOLUME_OUT, midiInVolumeOut)) return 0; + if (!setUint32(jRoot, CONF_KEY_MIDI_IN_BEAT_DOUBLE, midiInBeatDouble)) return 0; + if (!setUint32(jRoot, CONF_KEY_MIDI_IN_BEAT_HALF, midiInBeatHalf)) return 0; + if (!setBool(jRoot, CONF_KEY_RECS_STOP_ON_CHAN_HALT, recsStopOnChanHalt)) return 0; + if (!setBool(jRoot, CONF_KEY_CHANS_STOP_ON_SEQ_HALT, chansStopOnSeqHalt)) return 0; + if (!setBool(jRoot, CONF_KEY_TREAT_RECS_AS_LOOPS, treatRecsAsLoops)) return 0; + if (!setBool(jRoot, CONF_KEY_RESIZE_RECORDINGS, resizeRecordings)) return 0; + if (!setString(jRoot, CONF_KEY_PLUGINS_PATH, pluginPath)) return 0; + if (!setString(jRoot, CONF_KEY_PATCHES_PATH, patchPath)) return 0; + if (!setString(jRoot, CONF_KEY_SAMPLES_PATH, samplePath)) return 0; + + if (!setInt(jRoot, CONF_KEY_MAIN_WINDOW_X, mainWindowX)) return 0; + if (!setInt(jRoot, CONF_KEY_MAIN_WINDOW_Y, mainWindowY)) return 0; + if (!setInt(jRoot, CONF_KEY_MAIN_WINDOW_W, mainWindowW)) return 0; + if (!setInt(jRoot, CONF_KEY_MAIN_WINDOW_H, mainWindowH)) return 0; + if (!setInt(jRoot, CONF_KEY_BROWSER_X, browserX)) return 0; + if (!setInt(jRoot, CONF_KEY_BROWSER_Y, browserY)) return 0; + if (!setInt(jRoot, CONF_KEY_BROWSER_W, browserW)) return 0; + if (!setInt(jRoot, CONF_KEY_BROWSER_H, browserH)) return 0; + if (!setInt(jRoot, CONF_KEY_BROWSER_POSITION, browserPosition)) return 0; + if (!setString(jRoot, CONF_KEY_BROWSER_LAST_PATH, browserLastPath)) return 0; + if (!setInt(jRoot, CONF_KEY_BROWSER_LAST_VALUE, browserLastValue)) return 0; + if (!setInt(jRoot, CONF_KEY_ACTION_EDITOR_X, actionEditorX)) return 0; + if (!setInt(jRoot, CONF_KEY_ACTION_EDITOR_Y, actionEditorY)) return 0; + if (!setInt(jRoot, CONF_KEY_ACTION_EDITOR_W, actionEditorW)) return 0; + if (!setInt(jRoot, CONF_KEY_ACTION_EDITOR_H, actionEditorH)) return 0; + if (!setInt(jRoot, CONF_KEY_ACTION_EDITOR_ZOOM, actionEditorZoom)) return 0; + if (!setInt(jRoot, CONF_KEY_ACTION_EDITOR_GRID_VAL, actionEditorGridVal)) return 0; + if (!setInt(jRoot, CONF_KEY_ACTION_EDITOR_GRID_ON, actionEditorGridOn)) return 0; + if (!setInt(jRoot, CONF_KEY_SAMPLE_EDITOR_X, sampleEditorX)) return 0; + if (!setInt(jRoot, CONF_KEY_SAMPLE_EDITOR_Y, sampleEditorY)) return 0; + if (!setInt(jRoot, CONF_KEY_SAMPLE_EDITOR_W, sampleEditorW)) return 0; + if (!setInt(jRoot, CONF_KEY_SAMPLE_EDITOR_H, sampleEditorH)) return 0; + if (!setInt(jRoot, CONF_KEY_SAMPLE_EDITOR_GRID_VAL, sampleEditorGridVal)) return 0; + if (!setInt(jRoot, CONF_KEY_SAMPLE_EDITOR_GRID_ON, sampleEditorGridOn)) return 0; + if (!setInt(jRoot, CONF_KEY_PIANO_ROLL_Y, pianoRollY)) return 0; + if (!setInt(jRoot, CONF_KEY_PIANO_ROLL_H, pianoRollH)) return 0; + if (!setInt(jRoot, CONF_KEY_PLUGIN_LIST_X, pluginListX)) return 0; + if (!setInt(jRoot, CONF_KEY_PLUGIN_LIST_Y, pluginListY)) return 0; + if (!setInt(jRoot, CONF_KEY_CONFIG_X, configX)) return 0; + if (!setInt(jRoot, CONF_KEY_CONFIG_Y, configY)) return 0; + if (!setInt(jRoot, CONF_KEY_BPM_X, bpmX)) return 0; + if (!setInt(jRoot, CONF_KEY_BPM_Y, bpmY)) return 0; + if (!setInt(jRoot, CONF_KEY_BEATS_X, beatsX)) return 0; + if (!setInt(jRoot, CONF_KEY_BEATS_Y, beatsY)) return 0; + if (!setInt(jRoot, CONF_KEY_ABOUT_X, aboutX)) return 0; + if (!setInt(jRoot, CONF_KEY_ABOUT_Y, aboutY)) return 0; + if (!setInt(jRoot, CONF_KEY_MIDI_INPUT_X, midiInputX)) return 0; + if (!setInt(jRoot, CONF_KEY_MIDI_INPUT_Y, midiInputY)) return 0; + if (!setInt(jRoot, CONF_KEY_MIDI_INPUT_W, midiInputW)) return 0; + if (!setInt(jRoot, CONF_KEY_MIDI_INPUT_H, midiInputH)) return 0; + +#ifdef WITH_VST + + if (!setInt(jRoot, CONF_KEY_PLUGIN_CHOOSER_X, pluginChooserX)) return 0; + if (!setInt(jRoot, CONF_KEY_PLUGIN_CHOOSER_Y, pluginChooserY)) return 0; + if (!setInt(jRoot, CONF_KEY_PLUGIN_CHOOSER_W, pluginChooserW)) return 0; + if (!setInt(jRoot, CONF_KEY_PLUGIN_CHOOSER_H, pluginChooserH)) return 0; + if (!setInt(jRoot, CONF_KEY_PLUGIN_SORT_METHOD, pluginSortMethod)) return 0; + +#endif + + json_decref(jRoot); + + sanitize(); + + return 1; +} + + +/* -------------------------------------------------------------------------- */ + + +int Conf::write() +{ + if (!createConfigFolder()) + return 0; + + jRoot = json_object(); + + json_object_set_new(jRoot, CONF_KEY_HEADER, json_string(header.c_str())); + json_object_set_new(jRoot, CONF_KEY_LOG_MODE, json_integer(logMode)); + json_object_set_new(jRoot, CONF_KEY_SOUND_SYSTEM, json_integer(soundSystem)); + json_object_set_new(jRoot, CONF_KEY_SOUND_DEVICE_OUT, json_integer(soundDeviceOut)); + json_object_set_new(jRoot, CONF_KEY_SOUND_DEVICE_IN, json_integer(soundDeviceIn)); + json_object_set_new(jRoot, CONF_KEY_CHANNELS_OUT, json_integer(channelsOut)); + json_object_set_new(jRoot, CONF_KEY_CHANNELS_IN, json_integer(channelsIn)); + json_object_set_new(jRoot, CONF_KEY_SAMPLERATE, json_integer(samplerate)); + json_object_set_new(jRoot, CONF_KEY_BUFFER_SIZE, json_integer(buffersize)); + json_object_set_new(jRoot, CONF_KEY_DELAY_COMPENSATION, json_integer(delayComp)); + json_object_set_new(jRoot, CONF_KEY_LIMIT_OUTPUT, json_boolean(limitOutput)); + json_object_set_new(jRoot, CONF_KEY_RESAMPLE_QUALITY, json_integer(rsmpQuality)); + json_object_set_new(jRoot, CONF_KEY_MIDI_SYSTEM, json_integer(midiSystem)); + json_object_set_new(jRoot, CONF_KEY_MIDI_PORT_OUT, json_integer(midiPortOut)); + json_object_set_new(jRoot, CONF_KEY_MIDI_PORT_IN, json_integer(midiPortIn)); + json_object_set_new(jRoot, CONF_KEY_NO_NOTE_OFF, json_boolean(noNoteOff)); + json_object_set_new(jRoot, CONF_KEY_MIDIMAP_PATH, json_string(midiMapPath.c_str())); + json_object_set_new(jRoot, CONF_KEY_LAST_MIDIMAP, json_string(lastFileMap.c_str())); + json_object_set_new(jRoot, CONF_KEY_MIDI_SYNC, json_integer(midiSync)); + json_object_set_new(jRoot, CONF_KEY_MIDI_TC_FPS, json_real(midiTCfps)); + json_object_set_new(jRoot, CONF_KEY_MIDI_IN_REWIND, json_integer(midiInRewind)); + json_object_set_new(jRoot, CONF_KEY_MIDI_IN_START_STOP, json_integer(midiInStartStop)); + json_object_set_new(jRoot, CONF_KEY_MIDI_IN_ACTION_REC, json_integer(midiInActionRec)); + json_object_set_new(jRoot, CONF_KEY_MIDI_IN_INPUT_REC, json_integer(midiInInputRec)); + json_object_set_new(jRoot, CONF_KEY_MIDI_IN_METRONOME, json_integer(midiInMetronome)); + json_object_set_new(jRoot, CONF_KEY_MIDI_IN_VOLUME_IN, json_integer(midiInVolumeIn)); + json_object_set_new(jRoot, CONF_KEY_MIDI_IN_VOLUME_OUT, json_integer(midiInVolumeOut)); + json_object_set_new(jRoot, CONF_KEY_MIDI_IN_BEAT_DOUBLE, json_integer(midiInBeatDouble)); + json_object_set_new(jRoot, CONF_KEY_MIDI_IN_BEAT_HALF, json_integer(midiInBeatHalf)); + json_object_set_new(jRoot, CONF_KEY_RECS_STOP_ON_CHAN_HALT, json_boolean(recsStopOnChanHalt)); + json_object_set_new(jRoot, CONF_KEY_CHANS_STOP_ON_SEQ_HALT, json_boolean(chansStopOnSeqHalt)); + json_object_set_new(jRoot, CONF_KEY_TREAT_RECS_AS_LOOPS, json_boolean(treatRecsAsLoops)); + json_object_set_new(jRoot, CONF_KEY_RESIZE_RECORDINGS, json_boolean(resizeRecordings)); + json_object_set_new(jRoot, CONF_KEY_PLUGINS_PATH, json_string(pluginPath.c_str())); + json_object_set_new(jRoot, CONF_KEY_PATCHES_PATH, json_string(patchPath.c_str())); + json_object_set_new(jRoot, CONF_KEY_SAMPLES_PATH, json_string(samplePath.c_str())); + json_object_set_new(jRoot, CONF_KEY_MAIN_WINDOW_X, json_integer(mainWindowX)); + json_object_set_new(jRoot, CONF_KEY_MAIN_WINDOW_Y, json_integer(mainWindowY)); + json_object_set_new(jRoot, CONF_KEY_MAIN_WINDOW_W, json_integer(mainWindowW)); + json_object_set_new(jRoot, CONF_KEY_MAIN_WINDOW_H, json_integer(mainWindowH)); + json_object_set_new(jRoot, CONF_KEY_BROWSER_X, json_integer(browserX)); + json_object_set_new(jRoot, CONF_KEY_BROWSER_Y, json_integer(browserY)); + json_object_set_new(jRoot, CONF_KEY_BROWSER_W, json_integer(browserW)); + json_object_set_new(jRoot, CONF_KEY_BROWSER_H, json_integer(browserH)); + json_object_set_new(jRoot, CONF_KEY_BROWSER_POSITION, json_integer(browserPosition)); + json_object_set_new(jRoot, CONF_KEY_BROWSER_LAST_PATH, json_string(browserLastPath.c_str())); + json_object_set_new(jRoot, CONF_KEY_BROWSER_LAST_VALUE, json_integer(browserLastValue)); + json_object_set_new(jRoot, CONF_KEY_ACTION_EDITOR_X, json_integer(actionEditorX)); + json_object_set_new(jRoot, CONF_KEY_ACTION_EDITOR_Y, json_integer(actionEditorY)); + json_object_set_new(jRoot, CONF_KEY_ACTION_EDITOR_W, json_integer(actionEditorW)); + json_object_set_new(jRoot, CONF_KEY_ACTION_EDITOR_H, json_integer(actionEditorH)); + json_object_set_new(jRoot, CONF_KEY_ACTION_EDITOR_ZOOM, json_integer(actionEditorZoom)); + json_object_set_new(jRoot, CONF_KEY_ACTION_EDITOR_GRID_VAL, json_integer(actionEditorGridVal)); + json_object_set_new(jRoot, CONF_KEY_ACTION_EDITOR_GRID_ON, json_integer(actionEditorGridOn)); + json_object_set_new(jRoot, CONF_KEY_SAMPLE_EDITOR_X, json_integer(sampleEditorX)); + json_object_set_new(jRoot, CONF_KEY_SAMPLE_EDITOR_Y, json_integer(sampleEditorY)); + json_object_set_new(jRoot, CONF_KEY_SAMPLE_EDITOR_W, json_integer(sampleEditorW)); + json_object_set_new(jRoot, CONF_KEY_SAMPLE_EDITOR_H, json_integer(sampleEditorH)); + json_object_set_new(jRoot, CONF_KEY_SAMPLE_EDITOR_GRID_VAL, json_integer(sampleEditorGridVal)); + json_object_set_new(jRoot, CONF_KEY_SAMPLE_EDITOR_GRID_ON, json_integer(sampleEditorGridOn)); + json_object_set_new(jRoot, CONF_KEY_PIANO_ROLL_Y, json_integer(pianoRollY)); + json_object_set_new(jRoot, CONF_KEY_PIANO_ROLL_H, json_integer(pianoRollH)); + json_object_set_new(jRoot, CONF_KEY_PLUGIN_LIST_X, json_integer(pluginListX)); + json_object_set_new(jRoot, CONF_KEY_PLUGIN_LIST_Y, json_integer(pluginListY)); + json_object_set_new(jRoot, CONF_KEY_CONFIG_X, json_integer(configX)); + json_object_set_new(jRoot, CONF_KEY_CONFIG_Y, json_integer(configY)); + json_object_set_new(jRoot, CONF_KEY_BPM_X, json_integer(bpmX)); + json_object_set_new(jRoot, CONF_KEY_BPM_Y, json_integer(bpmY)); + json_object_set_new(jRoot, CONF_KEY_BEATS_X, json_integer(beatsX)); + json_object_set_new(jRoot, CONF_KEY_BEATS_Y, json_integer(beatsY)); + json_object_set_new(jRoot, CONF_KEY_ABOUT_X, json_integer(aboutX)); + json_object_set_new(jRoot, CONF_KEY_ABOUT_Y, json_integer(aboutY)); + json_object_set_new(jRoot, CONF_KEY_MIDI_INPUT_X, json_integer(midiInputX)); + json_object_set_new(jRoot, CONF_KEY_MIDI_INPUT_Y, json_integer(midiInputY)); + json_object_set_new(jRoot, CONF_KEY_MIDI_INPUT_W, json_integer(midiInputW)); + json_object_set_new(jRoot, CONF_KEY_MIDI_INPUT_H, json_integer(midiInputH)); + +#ifdef WITH_VST + + json_object_set_new(jRoot, CONF_KEY_PLUGIN_CHOOSER_X, json_integer(pluginChooserX)); + json_object_set_new(jRoot, CONF_KEY_PLUGIN_CHOOSER_Y, json_integer(pluginChooserY)); + json_object_set_new(jRoot, CONF_KEY_PLUGIN_CHOOSER_W, json_integer(pluginChooserW)); + json_object_set_new(jRoot, CONF_KEY_PLUGIN_CHOOSER_H, json_integer(pluginChooserH)); + json_object_set_new(jRoot, CONF_KEY_PLUGIN_SORT_METHOD, json_integer(pluginSortMethod)); + +#endif + + if (json_dump_file(jRoot, confFilePath.c_str(), JSON_INDENT(2)) != 0) { + gu_log("[Conf::write] unable to write configuration file!\n"); + return 0; + } + return 1; +} + + +/* -------------------------------------------------------------------------- */ + + +void Conf::sanitize() +{ + if (!(soundSystem & SYS_API_ANY)) soundSystem = DEFAULT_SOUNDSYS; + if (soundDeviceOut < 0) soundDeviceOut = DEFAULT_SOUNDDEV_OUT; + if (soundDeviceIn < -1) soundDeviceIn = DEFAULT_SOUNDDEV_IN; + if (channelsOut < 0) channelsOut = 0; + if (channelsIn < 0) channelsIn = 0; + if (buffersize < 8 || buffersize > 4096) buffersize = DEFAULT_BUFSIZE; + if (delayComp < 0) delayComp = DEFAULT_DELAYCOMP; + if (midiPortOut < -1) midiPortOut = DEFAULT_MIDI_SYSTEM; + if (midiPortOut < -1) midiPortOut = DEFAULT_MIDI_PORT_OUT; + if (midiPortIn < -1) midiPortIn = DEFAULT_MIDI_PORT_IN; + if (browserX < 0) browserX = 0; + if (browserY < 0) browserY = 0; + if (browserW < 396) browserW = 396; + if (browserH < 302) browserH = 302; + if (actionEditorX < 0) actionEditorX = 0; + if (actionEditorY < 0) actionEditorY = 0; + if (actionEditorW < 640) actionEditorW = 640; + if (actionEditorH < 176) actionEditorH = 176; + if (actionEditorZoom < 100) actionEditorZoom = 100; + if (actionEditorGridVal < 0) actionEditorGridVal = 0; + if (actionEditorGridOn < 0) actionEditorGridOn = 0; + if (pianoRollH <= 0) pianoRollH = 422; + if (sampleEditorX < 0) sampleEditorX = 0; + if (sampleEditorY < 0) sampleEditorY = 0; + if (sampleEditorW < 500) sampleEditorW = 500; + if (sampleEditorH < 292) sampleEditorH = 292; + if (sampleEditorGridVal < 0) sampleEditorGridVal = 0; + if (sampleEditorGridOn < 0) sampleEditorGridOn = 0; + if (midiInputX < 0) midiInputX = 0; + if (midiInputY < 0) midiInputY = 0; + if (midiInputW < G_DEFAULT_MIDI_INPUT_UI_W) midiInputW = G_DEFAULT_MIDI_INPUT_UI_W; + if (midiInputH < G_DEFAULT_MIDI_INPUT_UI_H) midiInputH = G_DEFAULT_MIDI_INPUT_UI_H; + if (configX < 0) configX = 0; + if (configY < 0) configY = 0; + if (pluginListX < 0) pluginListX = 0; + if (pluginListY < 0) pluginListY = 0; +#ifdef WITH_VST + if (pluginChooserW < 640) pluginChooserW = 640; + if (pluginChooserH < 480) pluginChooserW = 480; +#endif + if (bpmX < 0) bpmX = 0; + if (bpmY < 0) bpmY = 0; + if (beatsX < 0) beatsX = 0; + if (beatsY < 0) beatsY = 0; + if (aboutX < 0) aboutX = 0; + if (aboutY < 0) aboutY = 0; + if (samplerate < 8000) samplerate = DEFAULT_SAMPLERATE; + if (rsmpQuality < 0 || rsmpQuality > 4) rsmpQuality = 0; +} diff --git a/src/core/conf.h b/src/core/conf.h new file mode 100644 index 0000000..81901bc --- /dev/null +++ b/src/core/conf.h @@ -0,0 +1,144 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * conf + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License 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 "dataStorageJson.h" + + +using std::string; + + +class Conf : public DataStorageJson +{ +private: + + string confFilePath; + string confDirPath; + + /* init + * Init Conf with default values. */ + + void init(); + + /* sanitize + * Avoid funky values from config file. */ + + void sanitize(); + + /* createConfigFolder + * Create local folder where to put the configuration file. Path differs from + * OS to OS. */ + + int createConfigFolder(); + +public: + + Conf(); + + string header; + + 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; + string midiMapPath; + string lastFileMap; + 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; + + string pluginPath; + string patchPath; + string samplePath; + + int mainWindowX, mainWindowY, mainWindowW, mainWindowH; + + int browserX, browserY, browserW, browserH, browserPosition, browserLastValue; + string browserLastPath; + + int actionEditorX, actionEditorY, actionEditorW, actionEditorH, actionEditorZoom; + int actionEditorGridVal; + int actionEditorGridOn; + + int sampleEditorX, sampleEditorY, sampleEditorW, sampleEditorH; + int sampleEditorGridVal; + int sampleEditorGridOn; + + int midiInputX, midiInputY, midiInputW, midiInputH; + + int pianoRollY, pianoRollH; + int pluginListX, pluginListY; + int configX, configY; + int bpmX, bpmY; + int beatsX, beatsY; + int aboutX, aboutY; + +#ifdef WITH_VST + + int pluginChooserX, pluginChooserY, pluginChooserW, pluginChooserH; + int pluginSortMethod; + +#endif + + int read(); + int write(); +}; + +#endif diff --git a/src/core/const.h b/src/core/const.h new file mode 100644 index 0000000..99fa701 --- /dev/null +++ b/src/core/const.h @@ -0,0 +1,483 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * const.h + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License 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 G_VERSION_STR "0.13.2" +#define G_APP_NAME "Giada" +#define G_VERSION_MAJOR 0 +#define G_VERSION_MINOR 13 +#define G_VERSION_PATCH 2 + +#define CONF_FILENAME "giada.conf" + +#ifndef BUILD_DATE + #define BUILD_DATE __DATE__ +#endif + +#ifdef _WIN32 + #define G_SLASH '\\' + #define G_SLASH_STR "\\" +#else + #define G_SLASH '/' + #define G_SLASH_STR "/" +#endif + + +/* -- 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 816 +#define GUI_HEIGHT 510 +#define GUI_PLUGIN_RATE 0.05 // refresh rate for plugin GUIs +#define GUI_FONT_SIZE_BASE 12 + +#define COLOR_BD_0 fl_rgb_color(78, 78, 78) // border off - TODO duplicate! +#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) - TODO duplicate! +#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 - TODO duplicate! +#define COLOR_BG_RICH fl_rgb_color(30, 30, 30) // lighter background +#define COLOR_BG_LINE fl_rgb_color(54, 54, 54) // lighter, for bg lines +#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 MIN_COLUMN_WIDTH 140 + + + +/* -- kernel audio ---------------------------------------------------------- */ +#define SYS_API_NONE 0x00 // 0000 0000 +#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_WASAPI 0x40 // 0100 0000 +#define SYS_API_ANY 0x7F // 0111 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_NONE +#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 1.0f +#define G_DEFAULT_PITCH 1.0f +#define DEFAULT_BOOST 0.0f +#define DEFAULT_OUT_VOL 1.0f +#define DEFAULT_IN_VOL 1.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 +#define DEFAULT_COLUMN_WIDTH 380 +#define G_DEFAULT_PATCH_NAME "(default patch)" +#define G_DEFAULT_MIDI_INPUT_UI_W 300 +#define G_DEFAULT_MIDI_INPUT_UI_H 350 + + + +/* -- 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 + + + +/* -- 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 0x01 +#define PATCH_INVALID 0x02 +#define PATCH_READ_OK 0x04 +#define PATCH_WRONG_PLUGINS 0x08 // currently unused +#define PATCH_WRONG_SAMPLES 0x10 // currently unused + + + +/* -- midimap signals ------------------------------------------------------- */ +#define MIDIMAP_NOT_SPECIFIED 0x00 +#define MIDIMAP_UNREADABLE 0x01 +#define MIDIMAP_INVALID 0x02 +#define MIDIMAP_READ_OK 0x04 + + + +/* -- MIDI signals ------------------------------------------------------------- + * all signals are set to channel 0 (where channels are considered). + * It's up to the caller to bitmask them with the proper channel number. */ + +/* channel voices messages - controller (0xB0) is a special subset of + * this family: it drives knobs, volume, faders and such. */ + +#define MIDI_CONTROLLER 0xB0 << 24 +#define MIDI_NOTE_ON 0x90 << 24 +#define MIDI_NOTE_OFF 0x80 << 24 +#define MIDI_VELOCITY 0x3F << 8 +#define MIDI_ALL_NOTES_OFF (MIDI_CONTROLLER) | (0x7B << 16) +#define MIDI_VOLUME (MIDI_CONTROLLER) | (0x07 << 16) + +/* system common / real-time messages. Single bytes */ + +#define MIDI_SYSEX 0xF0 +#define MIDI_MTC_QUARTER 0xF1 +#define MIDI_POSITION_PTR 0xF2 +#define MIDI_CLOCK 0xF8 +#define MIDI_START 0xFA +#define MIDI_CONTINUE 0xFB +#define MIDI_STOP 0xFC +#define MIDI_EOX 0xF7 // end of sysex + +/* channels */ + +#define MIDI_CHAN_0 0x00 << 24 +#define MIDI_CHAN_1 0x01 << 24 +#define MIDI_CHAN_2 0x02 << 24 +#define MIDI_CHAN_3 0x03 << 24 +#define MIDI_CHAN_4 0x04 << 24 +#define MIDI_CHAN_5 0x05 << 24 +#define MIDI_CHAN_6 0x06 << 24 +#define MIDI_CHAN_7 0x07 << 24 +#define MIDI_CHAN_8 0x08 << 24 +#define MIDI_CHAN_9 0x09 << 24 +#define MIDI_CHAN_10 0x0A << 24 +#define MIDI_CHAN_11 0x0B << 24 +#define MIDI_CHAN_12 0x0C << 24 +#define MIDI_CHAN_13 0x0D << 24 +#define MIDI_CHAN_14 0x0E << 24 +#define MIDI_CHAN_15 0x0F << 24 + +const int MIDI_CHANS[16] = { + MIDI_CHAN_0, MIDI_CHAN_1, MIDI_CHAN_2, MIDI_CHAN_3, + MIDI_CHAN_4, MIDI_CHAN_5, MIDI_CHAN_6, MIDI_CHAN_7, + MIDI_CHAN_8, MIDI_CHAN_9, MIDI_CHAN_10, MIDI_CHAN_11, + MIDI_CHAN_12, MIDI_CHAN_13, MIDI_CHAN_14, MIDI_CHAN_15 +}; + +/* midi sync constants */ + +#define MIDI_SYNC_NONE 0x00 +#define MIDI_SYNC_CLOCK_M 0x01 +#define MIDI_SYNC_CLOCK_S 0x02 +#define MIDI_SYNC_MTC_M 0x04 +#define MIDI_SYNC_MTC_S 0x08 + +/* JSON patch keys */ + +#define PATCH_KEY_HEADER "header" +#define PATCH_KEY_VERSION "version" +#define PATCH_KEY_VERSION_MAJOR "version_major" +#define PATCH_KEY_VERSION_MINOR "version_minor" +#define PATCH_KEY_VERSION_PATCH "version_patch" +#define PATCH_KEY_NAME "name" +#define PATCH_KEY_BPM "bpm" +#define PATCH_KEY_BARS "bars" +#define PATCH_KEY_BEATS "beats" +#define PATCH_KEY_QUANTIZE "quantize" +#define PATCH_KEY_MASTER_VOL_IN "master_vol_in" +#define PATCH_KEY_MASTER_VOL_OUT "master_vol_out" +#define PATCH_KEY_METRONOME "metronome" +#define PATCH_KEY_LAST_TAKE_ID "last_take_id" +#define PATCH_KEY_SAMPLERATE "samplerate" +#define PATCH_KEY_COLUMNS "columns" +#define PATCH_KEY_MASTER_OUT_PLUGINS "master_out_plugins" +#define PATCH_KEY_MASTER_IN_PLUGINS "master_in_plugins" +#define PATCH_KEY_CHANNELS "channels" +#define PATCH_KEY_CHANNEL_TYPE "type" +#define PATCH_KEY_CHANNEL_INDEX "index" +#define PATCH_KEY_CHANNEL_COLUMN "column" +#define PATCH_KEY_CHANNEL_MUTE "mute" +#define PATCH_KEY_CHANNEL_MUTE_S "mute_s" +#define PATCH_KEY_CHANNEL_SOLO "solo" +#define PATCH_KEY_CHANNEL_VOLUME "volume" +#define PATCH_KEY_CHANNEL_PAN_LEFT "pan_left" +#define PATCH_KEY_CHANNEL_PAN_RIGHT "pan_right" +#define PATCH_KEY_CHANNEL_MIDI_IN "midi_in" +#define PATCH_KEY_CHANNEL_MIDI_IN_KEYPRESS "midi_in_keypress" +#define PATCH_KEY_CHANNEL_MIDI_IN_KEYREL "midi_in_keyrel" +#define PATCH_KEY_CHANNEL_MIDI_IN_KILL "midi_in_kill" +#define PATCH_KEY_CHANNEL_MIDI_IN_ARM "midi_in_arm" +#define PATCH_KEY_CHANNEL_MIDI_IN_VOLUME "midi_in_volume" +#define PATCH_KEY_CHANNEL_MIDI_IN_MUTE "midi_in_mute" +#define PATCH_KEY_CHANNEL_MIDI_IN_SOLO "midi_in_solo" +#define PATCH_KEY_CHANNEL_MIDI_OUT_L "midi_out_l" +#define PATCH_KEY_CHANNEL_MIDI_OUT_L_PLAYING "midi_out_l_playing" +#define PATCH_KEY_CHANNEL_MIDI_OUT_L_MUTE "midi_out_l_mute" +#define PATCH_KEY_CHANNEL_MIDI_OUT_L_SOLO "midi_out_l_solo" +#define PATCH_KEY_CHANNEL_SAMPLE_PATH "sample_path" +#define PATCH_KEY_CHANNEL_KEY "key" +#define PATCH_KEY_CHANNEL_MODE "mode" +#define PATCH_KEY_CHANNEL_BEGIN "begin" +#define PATCH_KEY_CHANNEL_END "end" +#define PATCH_KEY_CHANNEL_BOOST "boost" +#define PATCH_KEY_CHANNEL_REC_ACTIVE "rec_active" +#define PATCH_KEY_CHANNEL_PITCH "pitch" +#define PATCH_KEY_CHANNEL_MIDI_IN_READ_ACTIONS "midi_in_read_actions" +#define PATCH_KEY_CHANNEL_MIDI_IN_PITCH "midi_in_pitch" +#define PATCH_KEY_CHANNEL_MIDI_OUT "midi_out" +#define PATCH_KEY_CHANNEL_MIDI_OUT_CHAN "midi_out_chan" +#define PATCH_KEY_CHANNEL_PLUGINS "plugins" +#define PATCH_KEY_CHANNEL_ACTIONS "actions" +#define PATCH_KEY_ACTION_TYPE "type" +#define PATCH_KEY_ACTION_FRAME "frame" +#define PATCH_KEY_ACTION_F_VALUE "f_value" +#define PATCH_KEY_ACTION_I_VALUE "i_value" +#define PATCH_KEY_PLUGIN_PATH "path" +#define PATCH_KEY_PLUGIN_BYPASS "bypass" +#define PATCH_KEY_PLUGIN_PARAMS "params" +#define PATCH_KEY_PLUGIN_MIDI_IN_PARAMS "midi_in_params" +#define PATCH_KEY_COLUMN_INDEX "index" +#define PATCH_KEY_COLUMN_WIDTH "width" +#define PATCH_KEY_COLUMN_CHANNELS "channels" + +/* JSON config keys */ + +#define CONF_KEY_HEADER "header" +#define CONF_KEY_LOG_MODE "log_mode" +#define CONF_KEY_SOUND_SYSTEM "sound_system" +#define CONF_KEY_SOUND_DEVICE_IN "sound_device_in" +#define CONF_KEY_SOUND_DEVICE_OUT "sound_device_out" +#define CONF_KEY_CHANNELS_IN "channels_in" +#define CONF_KEY_CHANNELS_OUT "channels_out" +#define CONF_KEY_SAMPLERATE "samplerate" +#define CONF_KEY_BUFFER_SIZE "buffer_size" +#define CONF_KEY_DELAY_COMPENSATION "delay_compensation" +#define CONF_KEY_LIMIT_OUTPUT "limit_output" +#define CONF_KEY_RESAMPLE_QUALITY "resample_quality" +#define CONF_KEY_MIDI_SYSTEM "midi_system" +#define CONF_KEY_MIDI_PORT_OUT "midi_port_out" +#define CONF_KEY_MIDI_PORT_IN "midi_port_in" +#define CONF_KEY_NO_NOTE_OFF "no_note_off" +#define CONF_KEY_MIDIMAP_PATH "midimap_path" +#define CONF_KEY_LAST_MIDIMAP "last_midimap" +#define CONF_KEY_MIDI_SYNC "midi_sync" +#define CONF_KEY_MIDI_TC_FPS "midi_tc_fps" +#define CONF_KEY_MIDI_IN_REWIND "midi_in_rewind" +#define CONF_KEY_MIDI_IN_START_STOP "midi_in_start_stop" +#define CONF_KEY_MIDI_IN_ACTION_REC "midi_in_action_rec" +#define CONF_KEY_MIDI_IN_INPUT_REC "midi_in_input_rec" +#define CONF_KEY_MIDI_IN_METRONOME "midi_in_metronome" +#define CONF_KEY_MIDI_IN_VOLUME_IN "midi_in_volume_in" +#define CONF_KEY_MIDI_IN_VOLUME_OUT "midi_in_volume_out" +#define CONF_KEY_MIDI_IN_BEAT_DOUBLE "midi_in_beat_doble" +#define CONF_KEY_MIDI_IN_BEAT_HALF "midi_in_beat_half" +#define CONF_KEY_RECS_STOP_ON_CHAN_HALT "recs_stop_on_chan_halt" +#define CONF_KEY_CHANS_STOP_ON_SEQ_HALT "chans_stop_on_seq_halt" +#define CONF_KEY_TREAT_RECS_AS_LOOPS "treat_recs_as_loops" +#define CONF_KEY_RESIZE_RECORDINGS "resize_recordings" +#define CONF_KEY_PLUGINS_PATH "plugins_path" +#define CONF_KEY_PATCHES_PATH "patches_path" +#define CONF_KEY_SAMPLES_PATH "samples_path" +#define CONF_KEY_MAIN_WINDOW_X "main_window_x" +#define CONF_KEY_MAIN_WINDOW_Y "main_window_y" +#define CONF_KEY_MAIN_WINDOW_W "main_window_w" +#define CONF_KEY_MAIN_WINDOW_H "main_window_h" +#define CONF_KEY_BROWSER_X "browser_x" +#define CONF_KEY_BROWSER_Y "browser_y" +#define CONF_KEY_BROWSER_W "browser_w" +#define CONF_KEY_BROWSER_H "browser_h" +#define CONF_KEY_BROWSER_POSITION "browser_position" +#define CONF_KEY_BROWSER_LAST_PATH "browser_last_path" +#define CONF_KEY_BROWSER_LAST_VALUE "browser_last_value" +#define CONF_KEY_ACTION_EDITOR_X "action_editor_x" +#define CONF_KEY_ACTION_EDITOR_Y "action_editor_y" +#define CONF_KEY_ACTION_EDITOR_W "action_editor_w" +#define CONF_KEY_ACTION_EDITOR_H "action_editor_h" +#define CONF_KEY_ACTION_EDITOR_ZOOM "action_editor_zoom" +#define CONF_KEY_ACTION_EDITOR_GRID_VAL "action_editor_grid_val" +#define CONF_KEY_ACTION_EDITOR_GRID_ON "action_editor_grid_on" +#define CONF_KEY_SAMPLE_EDITOR_X "sample_editor_x" +#define CONF_KEY_SAMPLE_EDITOR_Y "sample_editor_y" +#define CONF_KEY_SAMPLE_EDITOR_W "sample_editor_w" +#define CONF_KEY_SAMPLE_EDITOR_H "sample_editor_h" +#define CONF_KEY_SAMPLE_EDITOR_GRID_VAL "sample_editor_grid_val" +#define CONF_KEY_SAMPLE_EDITOR_GRID_ON "sample_editor_grid_on" +#define CONF_KEY_PIANO_ROLL_Y "piano_roll_y" +#define CONF_KEY_PIANO_ROLL_H "piano_roll_h" +#define CONF_KEY_PLUGIN_LIST_X "plugin_list_x" +#define CONF_KEY_PLUGIN_LIST_Y "plugin_list_y" +#define CONF_KEY_CONFIG_X "config_x" +#define CONF_KEY_CONFIG_Y "config_y" +#define CONF_KEY_BPM_X "bpm_x" +#define CONF_KEY_BPM_Y "bpm_y" +#define CONF_KEY_BEATS_X "beats_x" +#define CONF_KEY_BEATS_Y "beats_y" +#define CONF_KEY_ABOUT_X "about_x" +#define CONF_KEY_ABOUT_Y "about_y" +#define CONF_KEY_PLUGIN_CHOOSER_X "plugin_chooser_x" +#define CONF_KEY_PLUGIN_CHOOSER_Y "plugin_chooser_y" +#define CONF_KEY_PLUGIN_CHOOSER_W "plugin_chooser_w" +#define CONF_KEY_PLUGIN_CHOOSER_H "plugin_chooser_h" +#define CONF_KEY_MIDI_INPUT_X "midi_input_x" +#define CONF_KEY_MIDI_INPUT_Y "midi_input_y" +#define CONF_KEY_MIDI_INPUT_W "midi_input_w" +#define CONF_KEY_MIDI_INPUT_H "midi_input_h" +#define CONF_KEY_PLUGIN_SORT_METHOD "plugin_sort_method" + +/* JSON midimaps keys */ + +#define MIDIMAP_KEY_BRAND "brand" +#define MIDIMAP_KEY_DEVICE "device" +#define MIDIMAP_KEY_INIT_COMMANDS "init_commands" +#define MIDIMAP_KEY_MUTE_ON "mute_on" +#define MIDIMAP_KEY_MUTE_OFF "mute_off" +#define MIDIMAP_KEY_SOLO_ON "solo_on" +#define MIDIMAP_KEY_SOLO_OFF "solo_off" +#define MIDIMAP_KEY_WAITING "waiting" +#define MIDIMAP_KEY_PLAYING "playing" +#define MIDIMAP_KEY_STOPPING "stopping" +#define MIDIMAP_KEY_STOPPED "stopped" +#define MIDIMAP_KEY_CHANNEL "channel" +#define MIDIMAP_KEY_MESSAGE "message" + +#endif diff --git a/src/core/dataStorageIni.cpp b/src/core/dataStorageIni.cpp new file mode 100644 index 0000000..a759782 --- /dev/null +++ b/src/core/dataStorageIni.cpp @@ -0,0 +1,70 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * dataStorageIni + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include +#include +#include "../utils/log.h" +#include "dataStorageIni.h" +#include "const.h" + + +std::string DataStorageIni::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) { + gu_log("[DataStorageIni::getValue] key '%s' not found\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_INI_H__ +#define __DATA_STORAGE_INI_H__ + +#include +#include +#include + +#define MAX_LINE_LEN 1024 + + +class DataStorageIni +{ +protected: + + FILE *fp; + std::string getValue(const char *in); +}; + +#endif diff --git a/src/core/dataStorageJson.cpp b/src/core/dataStorageJson.cpp new file mode 100644 index 0000000..a52f142 --- /dev/null +++ b/src/core/dataStorageJson.cpp @@ -0,0 +1,148 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * dataStorageIni + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include +#include "../utils/log.h" +#include "dataStorageJson.h" + + +using std::string; + + +bool DataStorageJson::setString(json_t *jRoot, const char *key, string &output) +{ + json_t *jObject = json_object_get(jRoot, key); + if (!json_is_string(jObject)) { + gu_log("[dataStorageJson::setString] key '%s' is not a string!\n", key); + json_decref(jRoot); + return false; + } + output = json_string_value(jObject); + return true; +} + + +/* -------------------------------------------------------------------------- */ + + +bool DataStorageJson::setFloat(json_t *jRoot, const char *key, float &output) +{ + json_t *jObject = json_object_get(jRoot, key); + if (!jObject) { + gu_log("[dataStorageJson::setFloat] key '%s' not found, using default value\n", key); + output = 0.0f; + return true; + } + if (!json_is_real(jObject)) { + gu_log("[dataStorageJson::setFloat] key '%s' is not a float!\n", key); + json_decref(jRoot); + return false; + } + output = json_real_value(jObject); + return true; +} + + +/* -------------------------------------------------------------------------- */ + + +bool DataStorageJson::setUint32(json_t *jRoot, const char *key, uint32_t &output) +{ + json_t *jObject = json_object_get(jRoot, key); + if (!jObject) { + gu_log("[dataStorageJson::setUint32] key '%s' not found, using default value\n", key); + output = 0; + return true; + } + if (!json_is_integer(jObject)) { + gu_log("[dataStorageJson::setUint32] key '%s' is not an integer!\n", key); + json_decref(jRoot); + return false; + } + output = json_integer_value(jObject); + return true; +} + + +/* -------------------------------------------------------------------------- */ + + +bool DataStorageJson::setBool(json_t *jRoot, const char *key, bool &output) +{ + json_t *jObject = json_object_get(jRoot, key); + if (!jObject) { + gu_log("[dataStorageJson::setBool] key '%s' not found, using default value\n", key); + output = false; + return true; + } + if (!json_is_boolean(jObject)) { + gu_log("[dataStorageJson::setBool] key '%s' is not a boolean!\n", key); + json_decref(jRoot); + return false; + } + output = json_boolean_value(jObject); + return true; +} + + +/* -------------------------------------------------------------------------- */ + + +bool DataStorageJson::setInt(json_t *jRoot, const char *key, int &output) +{ + return setUint32(jRoot, key, (uint32_t&) output); +} + + +/* -------------------------------------------------------------------------- */ + + +bool DataStorageJson::checkObject(json_t *jRoot, const char *key) +{ + if (!json_is_object(jRoot)) { + gu_log("[DataStorageJson::checkObject] malformed json: %s is not an object!\n", key); + json_decref(jRoot); + return false; + } + return true; +} + + +/* -------------------------------------------------------------------------- */ + + +bool DataStorageJson::checkArray(json_t *jRoot, const char *key) +{ + if (!json_is_array(jRoot)) { + gu_log("[DataStorageJson::checkObject] malformed json: %s is not an array!\n", key); + json_decref(jRoot); + return false; + } + return true; +} diff --git a/src/core/dataStorageJson.h b/src/core/dataStorageJson.h new file mode 100644 index 0000000..eb43ef3 --- /dev/null +++ b/src/core/dataStorageJson.h @@ -0,0 +1,65 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * dataStorageIni + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License 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 __DATA_STORAGE_JSON_H__ +#define __DATA_STORAGE_JSON_H__ + + +#include +#include + + +using std::string; + + +class DataStorageJson +{ +protected: + + json_t *jRoot; + json_error_t jError; + + bool setString(json_t *jRoot, const char *key, string &output); + bool setFloat(json_t *jRoot, const char *key, float &output); + bool setUint32(json_t *jRoot, const char *key, uint32_t &output); + bool setInt(json_t *jRoot, const char *key, int &output); + bool setBool(json_t *jRoot, const char *key, bool &output); + + /* checkObject + check whether the jRoot object is a valid json object {} */ + + bool checkObject(json_t *jRoot, const char *key); + + /* checkArray + check whether the jRoot object is a valid json array [] */ + + bool checkArray(json_t *jRoot, const char *key); +}; + +#endif diff --git a/src/core/graphics.cpp b/src/core/graphics.cpp new file mode 100644 index 0000000..2826793 --- /dev/null +++ b/src/core/graphics.cpp @@ -0,0 +1,1794 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * graphics + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + +#include "graphics.h" + +const char *giada_logo_xpm[] = { +"300 82 8 1", +" c #181917", +". c #333432", +"+ c #484A47", +"@ c #5F615E", +"# c #767875", +"$ c #8D8F8C", +"% c #A5A7A4", +"& c #C5C7C4", +" .#%%&$ ", +" ..#%&&&&&# ", +" +@#$%&&&&&&&&&@ ", +" .. +&&&&&&&&&&&&&&. ", +" +$%%#+ +%&&&&&&&&&&&&&. ", +" #&&&&&%+ .+@@$&&&&&&&&&%. ", +" #&&&&&&&$ +&&&&&&&&# ", +" .&&&&&&&&%. #&&&&&&&@ ", +" @&&&&&&&&$ +&&&&&&&+ ", +" $&&&&&&&&# .&&&&&&&. ", +" #&&&&&&&&. +&&&&&&%. ", +" .&&&&&&&@ @&&&&&&$. ", +" @&&&&&@ $&&&&&&# ", +" .##@. %&&&&&&@ ", +" &&&&&&&+ ", +" .&&&&&&%. ", +" @&&&&&&$. ", +" .+@@###@+. ...... ... ++@@###@@. ....... .+@@###@@+. #&&&&&&# .+@@###@@+ ....... ", +" .@$%%&&&&&&&%$+ +%%%%%%. .+@#$%%$. .@$%&&&&&&&&%$@. $%%%%%%+ .@#%%&&&&&&&&%$@ %&&&&&&@ .@$%%&&&&&&&%$#. @%%%%%%@ ", +" #%&&&&&&&&&&&&&&$. #&&&&&& +@#$$%%%&&&&&%. .$%&&&&&&&&&&&&&&%@ .&&&&&&&+ #%&&&&&&&&&&&&&&&$+ &&&&&&&@ #%&&&&&&&&&&&&&&%#. %&&&&&&@ ", +" +%&&&&&&&&&&&&&&&&&%@ .&&&&&&% .%&&&&&&&&&&&&$ #%&&&&&&&&&&&&&&&&&&$ +&&&&&&%. @%&&&&&&&&&&&&&&&&&&&# .&&&&&&%+ @%&&&&&&&&&&&&&&&&&&%. &&&&&&&+ ", +" #&&&&&&&&&#+....@$&&&&@@&&&&&&$ .&&&&&&&&&&&&&# +%&&&&&&&&%#+....+#%&&&$#&&&&&&$. .$&&&&&&&&&$+.....@%&&&&$@&&&&&&%. .$&&&&&&&&&#@.....#%&&&%+&&&&&&%. ", +" $&&&&&&&&@ .%&&&&&&&&&&@ .@$&&&&&&&&&&@ +%&&&&&&&%@ .#&&&&&&&&&&$ .%&&&&&&&&@ .$&&&&&&&&&&$ .%&&&&&&&&#. @&&&&&&&&&&%. ", +" $&&&&&&&%. @&&&&&&&&&+ .%&&&&&&&%+ @&&&&&&&&# +%&&&&&&&&# +%&&&&&&&$. @&&&&&&&&&# .%&&&&&&&$. .%&&&&&&&&$ ", +" %&&&&&&&# @&&&&&&&&. +%&&&&&&%. @&&&&&&&&# .%&&&&&&&@ +%&&&&&&&# @&&&&&&&&@ +%&&&&&&&# %&&&&&&&@ ", +" $&&&&&&&$ #&&&&&&%. %&&&&&&% +&&&&&&&&@ +&&&&&&%+ .%&&&&&&&# @&&&&&&&+ .%&&&&&&&# &&&&&&&+ ", +" @&&&&&&&$. #&&&&&&$ %&&&&&&$ .%&&&&&&&@ @&&&&&&%. .$&&&&&&&$ +&&&&&&%+ $&&&&&&&$ .&&&&&&&+ ", +" +&&&&&&&%+ %&&&&&&# %&&&&&&# $&&&&&&&$ $&&&&&&% @&&&&&&&% #&&&&&&%. #&&&&&&&%. @&&&&&&%. ", +" %&&&&&&&# &&&&&&&+ .%&&&&&&@ @&&&&&&&$ %&&&&&&$ +&&&&&&&&+ $&&&&&&$ +&&&&&&&%. $&&&&&&$. ", +" @&&&&&&&%. .&&&&&&&. +%&&&&&%. .&&&&&&&&. .%&&&&&&# $&&&&&&&# %&&&&&&# .$&&&&&&&@ %&&&&&&# ", +" .%&&&&&&&@ @&&&&&&&. @&&&&&&% @&&&&&&&# @&&&&&&&+ +&&&&&&&&. +&&&&&&&@ +&&&&&&&% .&&&&&&&@ ", +" @&&&&&&&% $&&&&&&%. #&&&&&&% &&&&&&&&. #&&&&&&%. %&&&&&&&# @&&&&&&%+ .$&&&&&&&@ +&&&&&&&+ ", +" .$&&&&&&&# %&&&&&&# $&&&&&&# #&&&&&&&# $&&&&&&% +&&&&&&&&. $&&&&&&%. +&&&&&&&% #&&&&&&%+ ", +" +%&&&&&&&+ .&&&&&&&@ .%&&&&&&@ %&&&&&&&+ .%&&&&&&$ $&&&&&&&$ %&&&&&&% #&&&&&&&# %&&&&&&%. ", +" @&&&&&&&% +&&&&&&&+ +%&&&&&&+ .&&&&&&&%. +&&&&&&&# &&&&&&&&+ +%&&&&&&$ .%&&&&&&&. &&&&&&&$ ", +" $&&&&&&&@ #&&&&&&&. @&&&&&&&. #&&&&&&&# #&&&&&&&@ +&&&&&&&%. @&&&&&&&# .&&&&&&&% +&&&&&&&# ", +" .%&&&&&&&. %&&&&&&%. #&&&&&&% %&&&&&&&+ $&&&&&&&+ #&&&&&&&$. $&&&&&&&+ @&&&&&&&@ #&&&&&&&@ ", +" +&&&&&&&& &&&&&&&$. #&&&&&&$ &&&&&&&%. .%&&&&&&& %&&&&&&&# .%&&&&&&%. $&&&&&&&+ $&&&&&&%+ ", +" +&&&&&&&$ +&&&&&&&# .$&&&&&&# .&&&&&&&$. +&&&&&&&% %&&&&&&&+ +%&&&&&&% %&&&&&&&. .%&&&&&&%. ", +" @&&&&&&&@ $&&&&&&&@ .%&&&&&&+ +&&&&&&&# #&&&&&&&$ &&&&&&&&+ #&&&&&&&% &&&&&&&% @&&&&&&&$ ", +" @&&&&&&&+ &&&&&&&&+ +%&&&&&&. @&&&&&&&@ .%&&&&&&&@ .&&&&&&&%. .%&&&&&&&# &&&&&&&# $&&&&&&&$ ", +" #&&&&&&&. @&&&&&&&%. @&&&&&&& @&&&&&&&@ @&&&&&&&&+ .&&&&&&&%. @&&&&&&&&@ .&&&&&&&# +%&&&&&&&# ", +" #&&&&&&&. %&&&&&&&$. #&&&&&&% #&&&&&&&+ .$&&&&&&&&. .&&&&&&&$. .$&&&&&&&&. .&&&&&&&@ $&&&&&&&&@ ", +" #&&&&&&& #&&&&&&&&$ $&&&&&&# @&&&&&&&+ @&&&&&&&&& .&&&&&&&$. @&&&&&&&&& .&&&&&&&+ +%&&&&&&&%+ ", +" @&&&&&&& .%&&&&&&&&# .%&&&&&&@ @&&&&&&%+ .%&&&&&&&&% &&&&&&&$. .%&&&&&&&&% &&&&&&&+ $&&&&&&&&%. ", +" @&&&&&&&. $&&&&&&&&&@ +%&&&&&&. +&&&&&&&@ @&&&&&&&&&$ &&&&&&&%. #&&&&&&&&&$ &&&&&&&@ +&&&&&&&&&% ", +" +&&&&&&&+ @&&&&&&&&&%+ +&&&&&&& &&&&&&&@ +&&&&&&&&&&# $&&&&&&%+ @&&&&&&&&&&# $&&&&&&# .%&&&&&&&&&% ", +" .%&&&&&&@ +%&&$&&&&&&%. +&&&&&&& %&&&&&&# .&&&&&&&&&&&@ @&&&&&&&+ +&&&$%&&&&&&@ @&&&&&&$ .$&&&&&&&&&&$ ", +" #&&&&&&$ .$&&##&&&&&&$ @&&&&&&& @&&&&&&%. .%&&%@%&&&&&&@ .&&&&&&&# .&&&$+%&&&&&&. .&&&&&&&. .#&&%@%&&&&&&$ ", +" +&&&&&&&. .%&&% $&&&&&&# +&&&&&&&. +.&&&&&&&@ .%&&%+.%&&&&&&$ @+$&&&&&&&+ +&&&% +&&&&&&&+ .+ $&&&&&&$ .$&&&@ %&&&&&&% .# ", +" $&&&&&&$ +%&&&+ %&&&&&&@ +&&&&&&&@ #&$#&&&&&&%+ @&&&&@ .$&&&&&&&+ #&&@&&&&&&&$. .#&&&%. +%&&&&&&# .$&$ +&&&&&&&+ +%&&&# $&&&&&&&# +&&$ ", +" +%&&&&&&#. +#&&&%@ .%&&&&&&+ .%&&&&&&&+ .$&&%.%&&&&&&%+ +#&&&&@ #&&&&&&&%@..+@$&&&+@&&&&&&&%+ .@%&&&%+ .%&&&&&&&+ +%&&$. #&&&&&&%@ +#&&&&# @&&&&&&&&@...+#&&&# ", +" @&&&&&&&$@+..++#%&&&%+ +&&&&&&%. $&&&&&&&%#@@#%&&%. .%&&&&&&%@+...+@$&&&&%+ +%&&&&&&&&%$%&&&&+ $&&&&&&&&$#@+@@#%&&&&$. #&&&&&&&&#@+@$&&&$. .$&&&&&&&#+...++#%&&&&@ .%&&&&&&&&%$%&&&&# ", +" @&&&&&&&&%%%%&&&&&$. @&&&&&&% +%&&&&&&&&&&&&&$. +%&&&&&&&&%$%%&&&&&$. #&&&&&&&&&&&&&%+ .$&&&&&&&&&&&&&&&&&&# .%&&&&&&&&&&&&&&# .$&&&&&&&&%$%%&&&&&%+ @&&&&&&&&&&&&&%@ ", +" @%&&&&&&&&&&&&&%@. #&&&&&&$ #&&&&&&&&&&&%@. .$&&&&&&&&&&&&&&%@. .%&&&&&&&&&&&#. @&&&&&&&&&&&&&&&$+ +&&&&&&&&&&&&%+ .#&&&&&&&&&&&&&&&#. $&&&&&&&&&&&$+ ", +" .#%&&&&&&&&&%+. %&&&&&&# +%&&&&&&&%#. +$&&&&&&&&&&%@. .$&&&&&&&&#+ .#&&&&&&&&&&%#. .$&&&&&&&&%@. +$&&&&&&&&&&%@. .@&&&&&&&&$+. ", +" .+#$%%$#+.. .%&&&&&&+ .@#$%$#+. .@#$%%$#@+ +@$%$#+. .+#$%%$#@+. +@$%$$@. .+#$%%$#@+. .@$%$#@. ", +" +&&&&&&%. ", +" #&&&&&&% ", +" .%&&&&&&@ ", +" @&&&&&&%. ", +" $&&&&&&$ ", +" @&&&&&&&+ ", +" @#$#+ .$&&&&&&$ ", +" $&&&&&# #&&&&&&% ", +"#&&&&&&&@ @&&&&&&&+ ", +"%&&&&&&&%. @&&&&&&%+ ", +"%&&&&&&&&# #&&&&&&%. ", +"@&&&&&&&&&@ .$&&&&&&#. ", +" $&&&&&&&&&$+ +$&&&&&&%+ ", +" +&&&&&&&&&&%#@@#$%&&&&&&&#. +. .+ +@. ++ .+ +++++ +. .+ ++ +++++. .++++. +@. .@+ .++++. .+++++++ +. +@. .+@. .++++. ++. ++. .+. .+@. .+ +. .+ .+. .+ .+++++++ ", +" .$&&&&&&&&&&&&&&&&&&&%@ $%. %@ +&&%&%. .$$ +& .&&&&&&# .%@ +&. %&+ $&&&&&%. @&&&&&%. +&&%&%. .%&%&&+ #&&&&&&@ +&&&&&&%. &# +&&%&%. @&&%&%. +&&&&&&@ $&% +%&+ .$&@ @&&%&$. $% .%$ $% +&%. +%. +&&&&&&$. ", +" +$%%&&&&&&&&&&%$@. +&# #% +&# %% $$ +& .&+ @&@ .%@ +&. +$&$ $$ .%% @% .%&. .&$ .%$. %%. #&+ #& .$&+ +&. &# +&# $%. @&# .%%. +&. $&+ $%&+ #%&+ .&%$ @&# +%$ $% .%# $% +&%$ +%. +&. ", +" .++@###@@+. #&. .&+ $%. .&@ $$ +& .&+ %$ .%@ +&. ##$% $$ @&. @% %$ $%. @&. @&+ %$. #& .%@ +&. &# %% .&# %% .&@ +&. &# $#%$ $#&+ @%@%. %% @%+ $% .%# $% +&+%+ +%. +&. ", +" .%$ $$ .&# %% $$ +& .&+ %$ .%@ +&. .%@+&+ $$ @&. @% #& &# +. %% #&. #& .%@ +&. &# .&@ $$ +&+ .$$ +&. %# $#@& +$@&+ $#.%@ +&@ .+. $% .%# $% +& $$ +%. +&. ", +" @&#&. .&@ $& $$ +& .&#+++#&+ .%%####$&. +%. %$ $%.++@&$ @% +&..&@ &$ @&+ #&++++$%. +&$###@ &# +&+ #%.@&. #% +&+ ..#&+ $#.&@ ##+&+ .&..$$ @&. $&$###$&# $% +& +%@ +%. +&####+ ", +" %&@ +&+ #& $$ +& .&%$$%%@ .%%####$&. #$ #& $&$$%&#. @% +&++&@ &# +&+ #&$$%&%+ +&$$$$# &# @&. #%.@&. #%.+%%%%%%@ $# $% .$+.&+ @% @%. @&. $&$###$&# $% +& #%.+%. +&$$$$@ ", +" #&. .&@ $% $$ +& .&#+.$%. .%@ +&. .$%##$&+ $%.+@&@ @% +&..&@ &$ @&. #&+++$$ +&+ &# +&. #% @&. $$ +&#@@++ $# +&.+$.+&+ $%@#$&+ @&. $% .%# $% +& .%@+%. +&. ", +" @& .%# &$ $$ +% .&+ .%@ .%@ +&. +%$###&$ $$ $% @% $& &$ .$+ $% #%. #& +&+ +&. &# .&@ .%$ +&@ .%# +&. $# .%### +&+ .&####&# .&@ .$+ $% .%# $% +& @%@%. +&. ", +" @& #%. @&. #%. $% .&+ $% .%@ +&. @$. #& $$ +&+ @% +&@ #&. #&. +&+ .%@ #& .%# +&. &# $% +&+ %% @&+ +&. $# #&%+ +&+ @% #%. %% #%. $% .%# $% +& .$%%. +&. ", +" @& .%%+.@&# .%%++#&@ .&+ +&+ .%@ +&. $# .&+ $$ %$ @&+++#&%. $%+.#&# #&@.+%%. #& @%. +&@+++++. &$++++. .%%+.@%# .%%..@&# +&. $# +&$ +&+ %# +&+ .%%+.#&# $% .%# $% +& +&&. +&@++++. %&", +" @& .$&&%@ +%&&&@ .&+ %$ .%@ +&..%+ %$ $$ @&. @&&&&%@ .$%&&# @%&&$. #& .%@ +&&&&&&%+ &&&&&&$. .$&&%# .$&&%@ +&. $# .%# +&+.&. .%# .$&&&@ $% .%# $% +& $&. +&&&&&&%. &&"}; + + +const char *loopRepeat_xpm[] = { +"18 18 8 1", +" c #181917", +". c #242523", +"+ c #323331", +"@ c #4D4F4C", +"# c #646663", +"$ c #787A77", +"% c #919390", +"& c #BFC1BD", +"..................", +"..................", +"..................", +"...&%#......#%&...", +"...&&&%+..+%&&&...", +"...$%&&%..%&&%$...", +".....$&&##&&$.....", +"......%&%%&%......", +"......$&&&&$......", +"......$&&&&$......", +"......%&%%&%......", +".....$&&##&&$.....", +"...$%&&%..%&&%$...", +"...&&&%+..+%&&&...", +"...&%#......#%&...", +"..................", +"..................", +".................."}; + + +const char *loopBasic_xpm[] = { +"18 18 8 1", +" c #181917", +". c #242523", +"+ c #313230", +"@ c #4D4F4C", +"# c #666765", +"$ c #787A77", +"% c #919390", +"& c #BEC0BD", +"..................", +"..................", +"..................", +"......#%&&%#......", +"....+%&&&&&&%+....", +"....%&&&%%&&&%....", +"...#&&%+..+%&&#...", +"...%&&+....+&&%...", +"...&&%......%&&...", +"...&&%......%&&...", +"...%&&+....+&&%...", +"...#&&%+..+%&&#...", +"....%&&&%%&&&%....", +"....+%&&&&&&%+....", +"......#%&&%#......", +"..................", +"..................", +".................."}; + + +const char *loopOnce_xpm[] = { +"18 18 8 1", +" c #181917", +". c #242523", +"+ c #323331", +"@ c #4D4F4C", +"# c #646663", +"$ c #787A77", +"% c #919390", +"& c #BFC1BD", +"..................", +"..................", +"..................", +"......$%&&%#......", +"....+&&&&&&&&+....", +"...+&&&&$$&&&&+...", +"...$&&$....$&&$...", +"...%&&......%&%...", +"..................", +"..................", +"...%&&+.....&&&...", +"...#&&$....$&&#...", +"....%&&&%$&&&%....", +"....+%&&&&&&%+....", +"......#%&&%#......", +"..................", +"..................", +".................."}; + + +const char *loopOnceBar_xpm[] = { +"18 18 8 1", +" c #242523", +". c #393A38", +"+ c #545553", +"@ c #747673", +"# c #A3A5A2", +"$ c #ADAFAC", +"% c #B5B7B4", +"& c #C7C9C6", +" ", +" ", +" ", +" @$&%#@ ", +" .$&&&&&&$. ", +" %&&#@@#&&$ ", +" @&&@ @&&@ ", +" %&# +%$+ #&$ ", +" %&&% ", +" %&&% ", +" $&# +%%+ #&$ ", +" @&&@ @&&@ ", +" $&&#@@#&&$ ", +" .$&&&&&&$. ", +" @#&&#@ ", +" ", +" ", +" "}; + + +const char *oneshotBasic_xpm[] = { +"18 18 8 1", +" c #181917", +". c #242523", +"+ c #313230", +"@ c #4D4F4C", +"# c #666765", +"$ c #787A77", +"% c #919390", +"& c #BEC0BD", +"..................", +"..................", +"..................", +"..................", +"..................", +"..................", +"..................", +"..................", +"..................", +"..................", +"..................", +"..................", +"...$$$$$$$$$$$$...", +"...&&&&&&&&&&&&...", +"...&&&&&&&&&&&&...", +"..................", +"..................", +".................."}; + + +const char *oneshotRetrig_xpm[] = { +"18 18 8 1", +" c #181917", +". c #242523", +"+ c #313230", +"@ c #4D4F4C", +"# c #666765", +"$ c #787A77", +"% c #919390", +"& c #BEC0BD", +"..................", +"..................", +"..................", +"..................", +"..................", +"...$$$$$$$#@......", +"...&&&&&&&&&&@....", +"...&&&&&&&&&&&+...", +"..........+$&&%...", +"............%&&...", +"............%&&...", +"...........+&&%...", +"...$$$$$$$%&&&#...", +"...&&&&&&&&&&%....", +"...&&&&&&&&%#.....", +"..................", +"..................", +".................."}; + + +const char *oneshotPress_xpm[] = { +"18 18 8 1", +" c #181917", +". c #242523", +"+ c #313230", +"@ c #4D4F4C", +"# c #666765", +"$ c #787A77", +"% c #919390", +"& c #BEC0BD", +"..................", +"..................", +"..................", +"..................", +"..................", +"..................", +"...+%&%+..........", +"...%&&&%..........", +"...&&&&&..........", +"...$&&&$..........", +"...+$&$+..........", +"..................", +"...$$$$$$$$$$$$...", +"...&&&&&&&&&&&&...", +"...&&&&&&&&&&&&...", +"..................", +"..................", +".................."}; + + +const char *oneshotEndless_xpm[] = { +"18 18 6 1", +" c #242523", +". c #464745", +"+ c #6D6F6C", +"@ c #888A87", +"# c #ADAFAC", +"$ c #C6C8C5", +" ", +" ", +" ", +" ", +" ", +" .++. ", +" @$$$$#. ", +" @$$$$$$$. ", +" .$$#. +$$@ ", +" +$$. @$# ", +" +$$ @$$ ", +" .$$+ #$# ", +" @@@$$$@@#$$+ ", +" $$$$$$$$$$@ ", +" $$$$$$$$#+ ", +" ", +" ", +" "}; + + +const char *updirOff_xpm[] = { +"18 18 8 1", +" c #242523", +". c #332F2E", +"+ c #54494A", +"@ c #6B5A5C", +"# c #866C6B", +"$ c #967B7A", +"% c #987D7C", +"& c #B18E8F", +" ", +" ", +" ", +" ", +" @@ ", +" #&&# ", +" .#&&&&#. ", +" .$&&&&&&$. ", +" +@%&&&&%@+ ", +" #&&&&# ", +" #&&&&# ", +" #&&&&# ", +" #&&&&# ", +" #&&&&# ", +" .... ", +" ", +" ", +" "}; + + +const char *updirOn_xpm[] = { +"18 18 8 1", +" c #4D4F4C", +". c #555150", +"+ c #706465", +"@ c #7D6B6E", +"# c #877373", +"$ c #957978", +"% c #9F8382", +"& c #B18E8F", +" ", +" ", +" ", +" ", +" ## ", +" #&&# ", +" .$&&&&$. ", +" .%&&&&&&%. ", +" +@%&&&&%@+ ", +" $&&&&$ ", +" $&&&&$ ", +" $&&&&$ ", +" $&&&&$ ", +" $&&&&$ ", +" .... ", +" ", +" ", +" "}; + + +const char *pause_xpm[] = { +"23 23 8 1", +" c #4D4F4C", +". c #514E53", +"+ c #5C4F61", +"@ c #6F507E", +"# c #855098", +"$ c #9551AE", +"% c #A652C5", +"& c #AE52D1", +" ", +" ", +" ", +" ", +" ", +" #+ ", +" &%#. ", +" &&&%@ ", +" &&&&&$+ ", +" &&&&&&&#. ", +" &&&&&&&&%@ ", +" &&&&&&&&&&# ", +" &&&&&&&&%@. ", +" &&&&&&&#. ", +" &&&&&$+ ", +" &&&%@ ", +" &&#. ", +" $+ ", +" ", +" ", +" ", +" ", +" "}; + + +const char *play_xpm[] = { +"23 23 8 1", +" c #242523", +". c #393534", +"+ c #574B4C", +"@ c #6E5B5A", +"# c #7C6663", +"$ c #8C7170", +"% c #A48384", +"& c #B18E8F", +" ", +" ", +" ", +" ", +" ", +" $. ", +" &&@ ", +" &&&%+ ", +" &&&&&$. ", +" &&&&&&&@ ", +" &&&&&&&&%+ ", +" &&&&&&&&&&# ", +" &&&&&&&&%+ ", +" &&&&&&&#. ", +" &&&&&$. ", +" &&&%+ ", +" &&@ ", +" $. ", +" ", +" ", +" ", +" ", +" "}; + + +const char *rewindOff_xpm[] = { +"23 23 8 1", +" c #242523", +". c #393534", +"+ c #574B4C", +"@ c #6E5B5A", +"# c #7C6663", +"$ c #8C7170", +"% c #A48384", +"& c #B18E8F", +" ", +" ", +" ", +" ", +" ", +" .$ ", +" @&& ", +" +%&&& ", +" .$&&&&& ", +" @&&&&&&& ", +" +%&&&&&&&& ", +" #&&&&&&&&&& ", +" +%&&&&&&&& ", +" .#&&&&&&& ", +" .$&&&&& ", +" +%&&& ", +" @&& ", +" .$ ", +" ", +" ", +" ", +" ", +" "}; + + +const char *rewindOn_xpm[] = { +"23 23 8 1", +" c #4D4F4C", +". c #514E53", +"+ c #5C4F61", +"@ c #6F507E", +"# c #855098", +"$ c #9551AE", +"% c #A652C5", +"& c #AE52D1", +" ", +" ", +" ", +" ", +" ", +" +# ", +" .#%& ", +" @%&&& ", +" +$&&&&& ", +" .#&&&&&&& ", +" @%&&&&&&&& ", +" #&&&&&&&&&& ", +" .@%&&&&&&&& ", +" .#&&&&&&& ", +" +$&&&&& ", +" @%&&& ", +" .#&& ", +" +$ ", +" ", +" ", +" ", +" ", +" "}; + + +// 18x18 +/* +const unsigned char giada_icon[] = { + 0x00, 0x00, 0x00, 0xfc, 0xff, 0x00, 0xfe, 0xff, 0x01, 0xfe, 0xff, 0x01, + 0x3e, 0xf0, 0x01, 0x1e, 0xe0, 0x01, 0x0e, 0xc3, 0x01, 0x8e, 0xff, 0x01, + 0x8e, 0xc1, 0x01, 0x8e, 0xc1, 0x01, 0x8e, 0xc7, 0x01, 0x0e, 0xc7, 0x01, + 0x1e, 0xc0, 0x01, 0x3e, 0xf0, 0x01, 0xfe, 0xff, 0x01, 0xfe, 0xff, 0x01, + 0xfc, 0xff, 0x00, 0x00, 0x00, 0x00 }; +*/ + +const char *giada_icon[] = { +"65 65 11 1", +" c None", +". c #000000", +"+ c #000100", +"@ c #FFFFFF", +"# c #FDFFFC", +"$ c #CBCDC9", +"% c #292B28", +"& c #626461", +"* c #484A47", +"= c #888A87", +"- c #A7A9A6", +"....+++++++++++++++++++++++++++++++++++++++++++++++++++++++++....", +".@@@#####################$%+++++++++++++%&&*%+++++++&##%+++++....", +".@@#####################&++++++++++++=$#######-*+++++&$+++++++...", +".@@####################&++++++++++%-############$*+++++++++++++..", +"+#####################*++++++++++&################-++++++++++++%+", +"+####################*++++++++++=##################$+++++++++++*+", +"+###################*++++++++++$####################$++++++++++=+", +"+##################=++++++++++-######################-+++++++++-+", +"+#################$++++++++++=#######################=+++++++++$+", +"+#################%+++++++++*########################*+++++++++#+", +"+################*++++++++++#########################%++++++++*#+", +"+###############-++++++++++=########################$+++++++++&#+", +"+###############%+++++++++%#########################-+++++++++-#+", +"+##############-++++++++++-#########################&+++++++++$#+", +"+##############%+++++++++%##########################*+++++++++##+", +"+#############-++++++++++-##########################+++++++++%##+", +"+#############%++++++++++##########################$+++++++++*##+", +"+############$++++++++++&##########################=+++++++++=##+", +"+############=++++++++++$##########################&+++++++++-##+", +"+############*+++++++++%###########################%+++++++++$##+", +"+############++++++++++=###########################++++++++++###+", +"+###########$++++++++++$##########################-+++++++++*###+", +"+###########=++++++++++###########################=+++++++++&###+", +"+###########*+++++++++%###########################*+++++++++-###+", +"+###########%+++++++++&###########################++++++++++$###+", +"+###########%+++++++++-##########################=++++++++++####+", +"+###########++++++++++$##########################%+++++++++%####+", +"+###########++++++++++##########################$++++++++++&####+", +"+##########$++++++++++##########################&++++++++++=####+", +"+##########$+++++++++%#########################$+++++++++++-####+", +"+##########$+++++++++%#########################&+++++++++++$####+", +"+###########+++++++++*########################$++++++++++++#####+", +"+###########+++++++++*########################*+++++++++++*#####+", +"+###########%++++++++%#######################=++++++++++++&#####+", +"+###########&+++++++++######################$+++++++++++++-#####+", +"+###########=+++++++++$#####################%+++%+++++++++$#####+", +"+###########$+++++++++$####################&+++%=+++++++++######+", +"+############%++++++++&###################=++++$&++++++++%######+", +"+############-+++++++++$#################&++++=#*++++++++&######+", +"+#############+++++++++&################*++++*##+++++++++=######+", +"+#############=+++++++++-#############$%++++%###+++++++++-######+", +"+##############*+++++++++=##########$*+++++%###$+++++++++#######+", +"+###############++++++++++&$######$&++++++&####=+++++++++#######+", +"+###############$++++++++++++%&*%++++++++=#####&++++++++*#######+", +"+################$%+++++++++++++++++++++-######%++++++++=#######+", +"+##################&++++++++++++++++++=########+++++++++-#######+", +"+###################$%+++++++++++++%=#########$+++++++++########+", +"+#####################$=%++++++%*=-###########=++++++++%########+", +"+##########################$$$################*++++++++&########+", +"+#############################################+++++++++=########+", +"+############################################$+++++++++$########+", +"+############################################&++++++++%#########+", +"+############################################+++++++++=#########+", +"+###########################################=+++++++++##########+", +"+###########################################%++++++++*##########+", +"+##########################################=+++++++++-##########+", +"+#########-==$############################$+++++++++&###########+", +"+#######=++++++-##########################*+++++++++############+", +"+######$++++++++$########################=+++++++++-############+", +"+######=+++++++++$######################-+++++++++&#############+", +"+######=+++++++++*#####################$+++++++++&##############.", +".@#####=++++++++++-###################-+++++++++=##############@.", +".@@####=++++++++++%##################=+++++++++=#############@@@.", +".@@@###=+++++++++++*###############$*++++++++%$##############@@@.", +"....++++++++++++++++++++++++++++++++++++++++++++++++++++++++....."}; + +const char *recOff_xpm[] = { +"23 23 8 1", +" c #242523", +". c #342F2E", +"+ c #3F3B3A", +"@ c #594F4F", +"# c #7A6663", +"$ c #8C7170", +"% c #A68384", +"& c #B18E8F", +" ", +" ", +" ", +" ", +" ", +" @$%%$@ ", +" .$&&&&&&$. ", +" $&&&&&&&&$ ", +" @&&&#++#&&&@ ", +" $&&# #&&$ ", +" %&&+ +&&% ", +" %&&+ +&&% ", +" $&&# #&&$ ", +" @&&&#++#&&&@ ", +" $&&&&&&&&$ ", +" .$&&&&&&$. ", +" @$%%$@ ", +" ", +" ", +" ", +" ", +" ", +" "}; + +const char *recOn_xpm[] = { +"23 23 8 1", +" c #4D4F4C", +". c #5F4E50", +"+ c #6E4F50", +"@ c #8C5050", +"# c #AE5454", +"$ c #BB5253", +"% c #C55352", +"& c #E85557", +" ", +" ", +" ", +" ", +" ", +" @$&&$@ ", +" .%&&&&&&%. ", +" %&&&&&&&&% ", +" @&&&#++#&&&@ ", +" $&&# #&&$ ", +" &&&+ +&&& ", +" &&&+ +&&& ", +" $&&# #&&$ ", +" @&&&#++#&&&@ ", +" %&&&&&&&&% ", +" .%&&&&&&%. ", +" @$&&$@ ", +" ", +" ", +" ", +" ", +" ", +" "}; + +const char *inputRecOn_xpm[] = { +"23 23 8 1", +" c #524D4C", +". c #4D4F4C", +"+ c #5D4F50", +"@ c #8C5050", +"# c #BB5253", +"$ c #C45251", +"% c #DD5256", +"& c #EA5657", +".......................", +".......................", +".......................", +".......................", +".......................", +"........ @#%%#@ .......", +".......+$&&&&&&$+......", +"...... $&&&&&&&&$ .....", +"......@&&&&&&&&&&@.....", +"......#&&&&&&&&&&#.....", +"......%&&&&&&&&&&%.....", +"......%&&&&&&&&&&%.....", +"......#&&&&&&&&&&#.....", +"......@&&&&&&&&&&@.....", +".......$&&&&&&&&$......", +".......+$&&&&&&$+......", +"........ @#%%#@ .......", +".......................", +".......................", +".......................", +".......................", +".......................", +"......................."}; + +const char *inputRecOff_xpm[] = { +"23 23 8 1", +" c #242523", +". c #252724", +"+ c #332F2E", +"@ c #594E4F", +"# c #896E6D", +"$ c #8D7271", +"% c #A68384", +"& c #B18E8F", +" ", +" ", +" ", +" ", +" ", +" .@#%%#@. ", +" +$&&&&&&$+ ", +" .$&&&&&&&&$. ", +" @&&&&&&&&&&@ ", +" #&&&&&&&&&&# ", +" %&&&&&&&&&&% ", +" %&&&&&&&&&&% ", +" #&&&&&&&&&&# ", +" @&&&&&&&&&&@ ", +" $&&&&&&&&$ ", +" +$&&&&&&$+ ", +" .@#%%#@. ", +" ", +" ", +" ", +" ", +" ", +" "}; + +const char *inputToOutputOn_xpm[] = { +"10 10 8 1", +" c #4D4F4C", +". c #585A57", +"+ c #666765", +"@ c #6F716E", +"# c #939592", +"$ c #999B98", +"% c #AEB0AD", +"& c #BCBEBB", +" ", +" ", +" .#@ ", +" #&&#+ ", +" @$&%. ", +" @$&%. ", +" #&&#+ ", +" .#@ ", +" ", +" "}; + +const char *inputToOutputOff_xpm[] = { +"10 10 8 1", +" c #242523", +". c #2E302D", +"+ c #3A3B39", +"@ c #4F514E", +"# c #828481", +"$ c #8B8D8A", +"% c #A7A9A6", +"& c #B9BBB7", +" ", +" ", +" +$@ ", +" .#&&#+ ", +" @$&%. ", +" @$&%. ", +" .#&&#+ ", +" +$@ ", +" ", +" "}; + +const char *muteOff_xpm[] = { +"18 18 8 1", +" c #242523", +". c #2E2F2D", +"+ c #3B3C3A", +"@ c #525451", +"# c #6F716E", +"$ c #878986", +"% c #ADAFAC", +"& c #C6C8C5", +" ", +" ", +" ", +" ", +" ++. .++ ", +" +&&$ $&&+ ", +" +&&% %&&+ ", +" +&%&++&%&+ ", +" +&$&##&$&+ ", +" +&#%$$%#&+ ", +" +&#$%%$#&+ ", +" +&#@&&@#&+ ", +" +&#+&&+#&+ ", +" .#@ ## @#. ", +" ", +" ", +" ", +" "}; + +const char *muteOn_xpm[] = { +"18 18 8 1", +" c #4D4F4C", +". c #585A57", +"+ c #616260", +"@ c #7A7C79", +"# c #888A87", +"$ c #989A97", +"% c #B2B4B1", +"& c #C6C8C5", +" ", +" ", +" ", +" ", +" .. .. ", +" +&&$ $&&+ ", +" +&&% %&&+ ", +" +&%&++&%&+ ", +" +&$&@@&$&+ ", +" +&#%$$%#&+ ", +" +&#$&&$#&+ ", +" +&#@&&@#&+ ", +" +&#.&&.#&+ ", +" .#+ ## +#. ", +" ", +" ", +" ", +" "}; + + +const char *readActionOff_xpm[] = { +"18 18 8 1", +" c #242523", +". c #393B38", +"+ c #555754", +"@ c #6B6D6A", +"# c #7F807E", +"$ c #9C9E9B", +"% c #B1B3B0", +"& c #C3C5C2", +" ", +" ", +" ", +" ", +" .... ", +" %&&&&%+ ", +" %&@@@&& ", +" %% $&. ", +" %&@@#&$ ", +" %&&&&@ ", +" %% +&$ ", +" %% #&# ", +" %% %&+ ", +" @@ .#+ ", +" ", +" ", +" ", +" "}; + + +const char *readActionOn_xpm[] = { +"18 18 8 1", +" c #4D4F4C", +". c #696B68", +"+ c #7A7C79", +"@ c #888A87", +"# c #939592", +"$ c #A7A9A6", +"% c #B7B9B6", +"& c #C4C6C3", +" ", +" ", +" ", +" ", +" ", +" %&&&&%. ", +" %&++@&& ", +" %% $& ", +" %&@@#&$ ", +" %&&&&@ ", +" %% +&$ ", +" %% #&# ", +" %% %&. ", +" +@ .@+ ", +" ", +" ", +" ", +" "}; + + +const char *metronomeOff_xpm[] = { +"13 13 8 1", +" c #242523", +". c #2D2928", +"+ c #34302F", +"@ c #443D3C", +"# c #4F4445", +"$ c #685659", +"% c #826A68", +"& c #A18282", +" ", +" ", +" . . ", +" #% %# ", +" .&+ +&. ", +" %$ $% ", +" @& &@ ", +" &@ @& ", +" $% %$ ", +" +&. .&+ ", +" %# #% ", +" . . ", +" "}; + + +const char *metronomeOn_xpm[] = { +"13 13 8 1", +" c #4D4F4C", +". c #565150", +"+ c #645C5C", +"@ c #716465", +"# c #837070", +"$ c #8F7775", +"% c #977C7B", +"& c #A68787", +" ", +" ", +" . . ", +" @% %@ ", +" .&. .&. ", +" $# #$ ", +" +& &+ ", +" &+ +& ", +" #$ $# ", +" .&. .&. ", +" %@ @% ", +" . . ", +" "}; + + +const char *zoomInOff_xpm[] = { +"18 18 8 1", +" c None", +". c #252525", +"+ c #262626", +"@ c #535353", +"# c #ACACAC", +"$ c #AEAEAE", +"% c #B1B1B1", +"& c #C4C4C4", +"++++++++++++++++++", +"+................+", +"+................+", +"+................+", +"+................+", +"+.......@@.......+", +"+.......#$.......+", +"+.......#$.......+", +"+....@%%&&%%@....+", +"+....@%%&&%%@....+", +"+.......#$.......+", +"+.......#$.......+", +"+.......@@.......+", +"+................+", +"+................+", +"+................+", +"+................+", +"++++++++++++++++++"}; + + +const char *zoomInOn_xpm[] = { +"18 18 8 1", +" c None", +". c #4E4E4E", +"+ c #707070", +"@ c #717171", +"# c #B3B3B3", +"$ c #B5B5B5", +"% c #B7B7B7", +"& c #C5C5C5", +"..................", +"..................", +"..................", +"..................", +"..................", +"........++........", +"........#$........", +"........#$........", +".....@%%&&%%@.....", +".....@%%&&%%@.....", +"........#$........", +"........#$........", +"........++........", +"..................", +"..................", +"..................", +"..................", +".................."}; + + +const char *zoomOutOff_xpm[] = { +"18 18 5 1", +" c None", +". c #252525", +"+ c #262626", +"@ c #9C9C9C", +"# c #BBBBBB", +"++++++++++++++++++", +"+................+", +"+................+", +"+................+", +"+................+", +"+................+", +"+................+", +"+................+", +"+......@##@......+", +"+......@##@......+", +"+................+", +"+................+", +"+................+", +"+................+", +"+................+", +"+................+", +"+................+", +"++++++++++++++++++"}; + + +const char *zoomOutOn_xpm[] = { +"18 18 4 1", +" c None", +". c #4E4E4E", +"+ c #A7A7A7", +"@ c #BEBEBE", +"..................", +"..................", +"..................", +"..................", +"..................", +"..................", +"..................", +"..................", +".......+@@+.......", +".......+@@+.......", +"..................", +"..................", +"..................", +"..................", +"..................", +"..................", +"..................", +".................."}; + + + +const char *scrollRightOff_xpm[] = { +"12 12 8 1", +" c #181917", +". c #242523", +"+ c #2E2F2D", +"@ c #4D4F4C", +"# c #5D5F5C", +"$ c #828481", +"% c #9B9D9A", +"& c #BCBEBB", +"............", +"............", +"...+........", +"...&$@......", +"...$&&%@....", +"....+#%&%...", +"....+#%&%...", +"...$&&%#....", +"...&$@......", +"...+........", +"............", +"............"}; + + +const char *scrollLeftOff_xpm[] = { +"12 12 8 1", +" c #181917", +". c #242523", +"+ c #2E2F2D", +"@ c #4D4F4C", +"# c #5D5F5C", +"$ c #828481", +"% c #9B9D9A", +"& c #BCBEBB", +"............", +"............", +"........+...", +"......@$&...", +"....@%&&$...", +"...%&%#+....", +"...%&%#+....", +"....#%&&$...", +"......@$&...", +"........+...", +"............", +"............"}; + + +const char *scrollLeftOn_xpm[] = { +"12 12 8 1", +" c #4D4F4C", +". c #6B6D6A", +"+ c #7B7D7A", +"@ c #969895", +"# c #A6A8A5", +"$ c #B4B6B3", +"% c #C0C2BF", +"& c #FEFFFC", +" ", +" ", +" ", +" .@$ ", +" +#%%@ ", +" $%#+ ", +" %%#+ ", +" +$%%@ ", +" .#$ ", +" ", +" ", +" "}; + + +const char *scrollRightOn_xpm[] = { +"12 12 8 1", +" c #4D4F4C", +". c #6B6D6A", +"+ c #7B7D7A", +"@ c #969895", +"# c #A6A8A5", +"$ c #B4B6B3", +"% c #C0C2BF", +"& c #FEFFFC", +" ", +" ", +" ", +" %@. ", +" @%%#. ", +" +#%# ", +" +#%# ", +" @%%#+ ", +" %@. ", +" ", +" ", +" "}; + + +const char *soloOn_xpm[] = { +"18 18 8 1", +" c #4D4F4C", +". c #616360", +"+ c #737572", +"@ c #838582", +"# c #929491", +"$ c #A5A7A4", +"% c #B1B3B0", +"& c #C6C8C5", +" ", +" ", +" ", +" ", +" .@+. ", +" #&&&&# ", +" .&$ %&. ", +" &%+ .. ", +" #&&&$. ", +" .@$&&. ", +" .#. @&@ ", +" .&$. #&+ ", +" #&&&&$ ", +" .+@+ ", +" ", +" ", +" ", +" "}; + + +const char *soloOff_xpm[] = { +"18 18 8 1", +" c #242523", +". c #3D3F3D", +"+ c #525451", +"@ c #666865", +"# c #80827F", +"$ c #979996", +"% c #A7A9A6", +"& c #C6C8C5", +" ", +" ", +" ", +" ", +" .@@. ", +" #&&&&# ", +" .&$ %&. ", +" &%+ .. ", +" #&&&$+ ", +" .@%&&. ", +" +#. @&@ ", +" .&$..#&+ ", +" #&&&&$ ", +" .@@+ ", +" ", +" ", +" ", +" "}; + + +#ifdef WITH_VST + + +const char *fxOff_xpm[] = { +"18 18 8 1", +" c #242523", +". c #40423F", +"+ c #4D4E4C", +"@ c #686A67", +"# c #7B7D7A", +"$ c #919390", +"% c #AEB0AD", +"& c #C1C3C0", +" ", +" ", +" ", +" ", +" ", +" ..... . . ", +" $&&&$ $% @&. ", +" $$ .&#&@ ", +" $%##. @&$ ", +" $%##. #&% ", +" $$ .&@&# ", +" $$ %$ @&. ", +" .. + +. ", +" ", +" ", +" ", +" ", +" "}; + + +const char *fxOn_xpm[] = { +"18 18 8 1", +" c #4D4F4C", +". c #565855", +"+ c #636562", +"@ c #80827F", +"# c #8E908D", +"$ c #9FA19E", +"% c #B1B3B0", +"& c #C1C3C0", +" ", +" ", +" ", +" ", +" ", +" .++++ +. +. ", +" $&&&$ $% @&. ", +" $$ .&#&@ ", +" $%##+ @&$ ", +" $%##+ #&% ", +" $$ +&@&# ", +" $$ %$ @&+ ", +" ++ .+. ++ ", +" ", +" ", +" ", +" ", +" "}; + + +const char *fxShiftUpOff_xpm[] = { +"18 18 7 1", +" c #242523", +". c #4D4F4C", +"+ c #A3A5A2", +"@ c #868885", +"# c #C1C3C0", +"$ c #313330", +"% c #626361", +" ", +" ", +" ", +" ", +" ", +" ", +" .+@ ", +" @+#. ", +" $#%+@ ", +" %# %#$ ", +" +@ $#% ", +" $#. @+ ", +" $. $. ", +" ", +" ", +" ", +" ", +" "}; + + +const char *fxShiftUpOn_xpm[] = { +"18 18 5 1", +" c #4D4F4C", +". c #70726F", +"+ c #A5A7A4", +"@ c #C1C3BF", +"# c #8E908D", +" ", +" ", +" ", +" ", +" ", +" ", +" .++ ", +" +@@. ", +" @.+# ", +" .@ .@ ", +" +# @. ", +" .@. #+ ", +" . . ", +" ", +" ", +" ", +" ", +" "}; + + +const char *fxShiftDownOff_xpm[] = { +"18 18 7 1", +" c #242523", +". c #4D4F4C", +"+ c #A3A5A2", +"@ c #313330", +"# c #626361", +"$ c #868885", +"% c #C1C3C0", +" ", +" ", +" ", +" ", +" ", +" ", +" .+@ #$ ", +" @%# +$ ", +" $+ .%@ ", +" .%@$+ ", +" +$%# ", +" #%%@ ", +" @.. ", +" ", +" ", +" ", +" ", +" "}; + + +const char *fxShiftDownOn_xpm[] = { +"18 18 5 1", +" c #4D4F4C", +". c #70726F", +"+ c #A5A7A4", +"@ c #C1C3BF", +"# c #8E908D", +" ", +" ", +" ", +" ", +" ", +" ", +" .+ .+ ", +" @. +# ", +" #+ .@. ", +" .@.#+ ", +" +#@. ", +" #@@ ", +" .. ", +" ", +" ", +" ", +" ", +" "}; + + +const char *vstLogo_xpm[] = { +"65 38 8 1", +" c #161715", +". c #2B2D2A", +"+ c #474846", +"@ c #6A6C69", +"# c #8C8E8B", +"$ c #A8AAA7", +"% c #C7C9C6", +"& c #EEF0ED", +" @#############################################################+ ", +"@#.............................................................$+", +"#. .#", +"#. .#", +"#. ...... .. .#", +"#. .@$$$####$%$#@.+&$ .#", +"#. .#$$#+. +#$%%%%$ .#", +"#. .$$#$ .#$$%$ .#", +"#. ............. ....$$$$$ ++$$%$+@@@@@@@@@@@@@@@ .#", +"#. ##$$$$$$%%%%@ %%&&&&%%$@ %&@#$$@@$%%&&&%%%&&&&& .#", +"#. +$$$$$%@ .&%####%$@ $&$.$# #$%%%& @&%& .#", +"#. +$$$$$% +&$###$%%&&$@. $&. #$%%%&. .%& .#", +"#. @$$$$%$ %##$##$%&&&&&&%#.%# #$$%%&. @& .#", +"#. $$$$$%+ #& #$$%%&&&&&&&%%$$@ #$$%%&. + .#", +"#. .$$$$$% +&+ .#%&&&&&&&&%$$#$$# #$$%%&. .#", +"#. @$$$$%$ %$ @%&&&&&&%$$###$$ #$$%%&. .#", +"#. #$$$%%@ #& . +$&&&%$####$%$ #$$%%&. .#", +"#. $$$%%% .&@ +%# .@$$$###$$% #$$$%&. .#", +"#. +%$%%%$$% +$$+ #$#$$$% @$$$%&. .#", +"#. #%%%%%&. +%$$ ##$$%$ @$$$%%. .#", +"#. $$%%%@ +%$$$. #$$$%. @$$$$%. .#", +"#. +%%%$ +%$$#$@ +$$%$ @#$$$%+ .#", +"#. @%%. +%%%$$$$#@++.++@#$$$@ @@##$$$%%%$$@ .#", +"#. #@ +&# .@@###$$$###@. @+++@@@@###$@ .#", +"#. .#", +"#. .#", +"#. .#", +"#. .#", +"#. .#", +"#. .@$$$$$$$$ .$%%%%%%# .#", +"#. ....... .@@@@@@@@@. .#", +"#. ........ @@@+@@@@@@@@@@+ .#", +"@# ......... .####@@@@@@@@@@@+ #@", +" @$$$$$$$$$$$$$$$.......... .@@@@@@@@@@@@@@@@@$$$$$$$$$$$$$$$$@ ", +" ......... .@@@@@@@@@@@@@@@. ", +" ........ @@@@@@@@@@. ", +" ........... .@@@@@@@@@ ", +" .......... .@@@@@@@@ "}; + + +const char *fxRemoveOff_xpm[] = { +"18 18 9 1", +" c None", +". c #242623", +"+ c #2F312E", +"@ c #393A38", +"# c #484A47", +"$ c #5D5F5C", +"% c #8E908D", +"& c #9B9D9A", +"* c #BDBFBC", +"..................", +"..................", +"..................", +"..................", +"..................", +".....+#@..@#+.....", +"......&*++*&......", +"......@*%%*@......", +".......$**$.......", +".......#**#.......", +"......+*&&*+......", +"......%*@@*%......", +"......@@..@@......", +"..................", +"..................", +"..................", +"..................", +".................."}; + + +const char *fxRemoveOn_xpm[] = { +"18 18 9 1", +" c None", +". c #4D4F4C", +"+ c #575956", +"@ c #5C5D5B", +"# c #666865", +"$ c #787977", +"% c #9C9E9B", +"& c #A6A8A5", +"* c #BFC1BE", +"..................", +"..................", +"..................", +"..................", +"..................", +"......#@..@#......", +"......&*++*&......", +"......@*%%*@......", +".......$**$.......", +".......#**#.......", +"......+*&&*+......", +"......%*@+*%......", +"......@+..+@......", +"..................", +"..................", +"..................", +"..................", +".................."}; +#endif // #ifdef WITH_VST + + +const char *divideOn_xpm[] = { +"18 18 7 1", +" c #5A5A5A", +". c #696969", +"+ c #757575", +"@ c #8B8B8B", +"# c #AAAAAA", +"$ c #BBBBBB", +"% c #BDBDBD", +" ", +" ", +" ", +" ", +" ", +" @@ ", +" %$ ", +" ++ ", +" .########. ", +" .########. ", +" ++ ", +" %$ ", +" @@ ", +" ", +" ", +" ", +" ", +" "}; + + +const char *divideOff_xpm[] = { +"18 18 8 1", +" c #252525", +". c #3B3B3B", +"+ c #4D4D4D", +"@ c #6D6D6D", +"# c #6E6E6E", +"$ c #9C9C9C", +"% c #B5B5B5", +"& c #B7B7B7", +" ", +" ", +" ", +" ", +" ", +" @# ", +" &% ", +" ++ ", +" .$$$$$$$$. ", +" .$$$$$$$$. ", +" ++ ", +" &% ", +" @# ", +" ", +" ", +" ", +" ", +" "}; + + +const char *multiplyOn_xpm[] = { +"18 18 8 1", +" c #595B58", +". c #737572", +"+ c #747673", +"@ c #8B8D8A", +"# c #8D8F8C", +"$ c #8E908D", +"% c #8F918E", +"& c #C7C9C6", +" ", +" ", +" ", +" ", +" ", +" + . ", +" +&$ #&. ", +" #&$#&# ", +" @&&# ", +" @&&% ", +" @&#@&% ", +" +&# #&+ ", +" + . ", +" ", +" ", +" ", +" ", +" "}; + + +const char *multiplyOff_xpm[] = { +"18 18 8 1", +" c #242523", +". c #4A4C49", +"+ c #4D4E4C", +"@ c #6D6F6C", +"# c #717370", +"$ c #737572", +"% c #757774", +"& c #C7C9C6", +" ", +" ", +" ", +" ", +" ", +" + . ", +" +&$ #&. ", +" #&$#&# ", +" @&&# ", +" @&&% ", +" @&$@&% ", +" +&# #&+ ", +" + . ", +" ", +" ", +" ", +" ", +" "}; + + +const char *channelStop_xpm[] = { +"18 18 8 1", +" c #242523", +". c #312D2C", +"+ c #413A3A", +"@ c #615253", +"# c #73605F", +"$ c #7A6663", +"% c #9C7E7D", +"& c #B08D8E", +" ", +" ", +" ", +" ", +" ##. ", +" $&%@ ", +" $&&&%+ ", +" $&&&&&$. ", +" $&&&&&&&@ ", +" $&&&&&&&@. ", +" $&&&&&$. ", +" $&&&%+ ", +" $&&@ ", +" $#. ", +" . ", +" ", +" ", +" "}; + + + +const char *channelPlay_xpm[] = { +"18 18 8 1", +" c #4D4F4C", +". c #554E56", +"+ c #5A4D59", +"@ c #605068", +"# c #775086", +"$ c #8A509C", +"% c #9E50B5", +"& c #AD52D0", +" ", +" ", +" ", +" . ", +" $$. ", +" $&%# ", +" $&&&%@ ", +" $&&&&&$. ", +" $&&&&&&&#. ", +" $&&&&&&&#. ", +" $&&&&&$+ ", +" $&&&%@ ", +" $&&# ", +" $$. ", +" . ", +" ", +" ", +" "}; + + +const char *armOff_xpm[] = { +"18 18 8 1", +" c #242523", +". c #4F4445", +"+ c #514647", +"@ c #6D5C5E", +"# c #8E7372", +"$ c #AA8889", +"% c #AC898A", +"& c #B18E8F", +" ", +" ", +" ", +" ", +" +#%%#. ", +" @&&&&&&@ ", +" +&&&&&&&&. ", +" #&&&&&&&&# ", +" %&&&&&&&&% ", +" %&&&&&&&&% ", +" #&&&&&&&&# ", +" .&&&&&&&&. ", +" @&&&&&&@ ", +" .#%%#. ", +" ", +" ", +" ", +" "}; + + +const char *armOn_xpm[] = { +"18 18 8 1", +" c #4D4F4C", +". c #6B5077", +"+ c #805191", +"@ c #9950AD", +"# c #9751B3", +"$ c #9553AD", +"% c #AA52C9", +"& c #AE52D1", +" ", +" ", +" ", +" ", +" .#%%#. ", +" +&&&&&&+ ", +" .&&&&&&&&. ", +" #&&&&&&&&@ ", +" %&&&&&&&&% ", +" %&&&&&&&&% ", +" #&&&&&&&&$ ", +" .&&&&&&&&. ", +" +&&&&&&+ ", +" .@%%$. ", +" ", +" ", +" ", +" "}; diff --git a/src/core/graphics.h b/src/core/graphics.h new file mode 100644 index 0000000..7d93be8 --- /dev/null +++ b/src/core/graphics.h @@ -0,0 +1,111 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * graphics + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License 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 *inputToOutputOn_xpm[]; +extern const char *inputToOutputOff_xpm[]; + +extern const char *divideOn_xpm[]; +extern const char *divideOff_xpm[]; +extern const char *multiplyOn_xpm[]; +extern const char *multiplyOff_xpm[]; + +extern const char *muteOff_xpm[]; +extern const char *muteOn_xpm[]; + +extern const char *soloOff_xpm[]; +extern const char *soloOn_xpm[]; + +extern const char *armOff_xpm[]; +extern const char *armOn_xpm[]; + +extern const char *readActionOn_xpm[]; +extern const char *readActionOff_xpm[]; + +extern const char *channelStop_xpm[]; +extern const char *channelPlay_xpm[]; + +#ifdef WITH_VST +extern const char *fxOff_xpm[]; +extern const char *fxOn_xpm[]; + +extern const char *fxShiftUpOn_xpm[]; +extern const char *fxShiftUpOff_xpm[]; +extern const char *fxShiftDownOn_xpm[]; +extern const char *fxShiftDownOff_xpm[]; + +extern const char *fxRemoveOff_xpm[]; +extern const char *fxRemoveOn_xpm[]; + +extern const char *vstLogo_xpm[]; +#endif + +extern const char *giada_icon[]; + +#endif diff --git a/src/core/init.cpp b/src/core/init.cpp new file mode 100644 index 0000000..41105a6 --- /dev/null +++ b/src/core/init.cpp @@ -0,0 +1,226 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * init + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include +#include "../utils/log.h" +#include "../utils/fs.h" +#include "../utils/gui.h" +#include "../gui/dialogs/gd_mainWindow.h" +#include "../gui/dialogs/gd_warnings.h" +#include "init.h" +#include "mixer.h" +#include "wave.h" +#include "const.h" +#include "channel.h" +#include "mixerHandler.h" +#include "patch_DEPR_.h" +#include "patch.h" +#include "conf.h" +#include "pluginHost.h" +#include "recorder.h" +#include "midiMapConf.h" +#include "kernelMidi.h" + + +extern KernelAudio G_KernelAudio; +extern Mixer G_Mixer; +extern Recorder G_Recorder; +extern KernelMidi G_KernelMidi; +extern bool G_audio_status; +extern bool G_quit; +extern Patch_DEPR_ G_Patch_DEPR_; +extern Patch G_Patch; +extern Conf G_Conf; +extern MidiMapConf G_MidiMap; +extern gdMainWindow *G_MainWin; + +#ifdef WITH_VST +extern PluginHost G_PluginHost; +#endif + + +void init_prepareParser() +{ + time_t t; + time (&t); + gu_log("[init] Giada " G_VERSION_STR " - %s", ctime(&t)); + + G_Conf.read(); + G_Patch_DEPR_.setDefault(); + G_Patch.init(); + + if (!gu_logInit(G_Conf.logMode)) + gu_log("[init] log init failed! Using default stdout\n"); + + gu_log("[init] configuration file ready\n"); +} + + +/* -------------------------------------------------------------------------- */ + + +void init_prepareKernelAudio() +{ + G_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(); + G_Recorder.init(); + +#ifdef WITH_VST + + /* If with Jack don't use buffer size stored in Conf. Use real buffersize + from the soundcard (G_KernelAudio.realBufsize). */ + + if (G_Conf.soundSystem == SYS_API_JACK) + G_PluginHost.init(G_KernelAudio.realBufsize, G_Conf.samplerate); + else + G_PluginHost.init(G_Conf.buffersize, G_Conf.samplerate); + + G_PluginHost.sortPlugins(G_Conf.pluginSortMethod); + +#endif +} + + +/* -------------------------------------------------------------------------- */ + + +void init_prepareKernelMIDI() +{ + G_KernelMidi.setApi(G_Conf.midiSystem); + G_KernelMidi.openOutDevice(G_Conf.midiPortOut); + G_KernelMidi.openInDevice(G_Conf.midiPortIn); +} + + +/* -------------------------------------------------------------------------- */ + + +void init_prepareMidiMap() +{ + G_MidiMap.init(); + G_MidiMap.setDefault_DEPR_(); + G_MidiMap.setDefault(); + + /* read with deprecated method first. If it fails, try with the new one. */ + // TODO - do the opposite: if json fails, go with deprecated one + + if (G_MidiMap.read(G_Conf.midiMapPath) != MIDIMAP_READ_OK) { + gu_log("[init_prepareMidiMap] JSON-based midimap read failed, trying with the deprecated one...\n"); + if (G_MidiMap.readMap_DEPR_(G_Conf.midiMapPath) == MIDIMAP_INVALID) + gu_log("[init_prepareMidiMap] unable to read deprecated midimap. Nothing to do\n"); + } +} + + +/* -------------------------------------------------------------------------- */ + + +void init_startGUI(int argc, char **argv) +{ + G_MainWin = new gdMainWindow(GUI_WIDTH, GUI_HEIGHT, "", argc, argv); + G_MainWin->resize(G_Conf.mainWindowX, G_Conf.mainWindowY, G_Conf.mainWindowW, + G_Conf.mainWindowH); + + gu_updateMainWinLabel(G_Patch.name == "" ? G_DEFAULT_PATCH_NAME : G_Patch.name); + + /* never update the GUI elements if G_audio_status is bad, segfaults + * are around the corner */ + + if (G_audio_status) + gu_updateControls(); + else + gdAlert("Your soundcard isn't configured correctly.\n" + "Check the configuration and restart Giada."); +} + +/* -------------------------------------------------------------------------- */ + + +void init_startKernelAudio() +{ + if (G_audio_status) + G_KernelAudio.startStream(); +} + + +/* -------------------------------------------------------------------------- */ + + +void init_shutdown() +{ + G_quit = true; + + /* store position and size of the main window for the next startup */ + + G_Conf.mainWindowX = G_MainWin->x(); + G_Conf.mainWindowY = G_MainWin->y(); + G_Conf.mainWindowW = G_MainWin->w(); + G_Conf.mainWindowH = G_MainWin->h(); + + /* close any open subwindow, especially before cleaning PluginHost_DEPR_ to + * avoid mess */ + + gu_closeAllSubwindows(); + gu_log("[init] all subwindows closed\n"); + + /* write configuration file */ + + if (!G_Conf.write()) + gu_log("[init] error while saving configuration file!\n"); + else + gu_log("[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) { + G_KernelAudio.closeDevice(); + G_Mixer.close(); + gu_log("[init] Mixer closed\n"); + } + + G_Recorder.clearAll(); + for (unsigned i=0; ihasActions = false; + G_Mixer.channels.at(i)->readActions = false; + //if (G_Mixer.channels.at(i)->type == CHANNEL_SAMPLE) + // ((SampleChannel*)G_Mixer.channels.at(i))->readActions = false; + } + gu_log("[init] Recorder cleaned up\n"); + +#ifdef WITH_VST + G_PluginHost.freeAllStacks(&G_Mixer.channels, &G_Mixer.mutex_plugins); + gu_log("[init] PluginHost cleaned up\n"); +#endif + + gu_log("[init] Giada " G_VERSION_STR " closed\n\n"); + gu_logClose(); +} diff --git a/src/core/init.h b/src/core/init.h new file mode 100644 index 0000000..7d631bc --- /dev/null +++ b/src/core/init.h @@ -0,0 +1,50 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * init + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License 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_prepareMidiMap(); +void init_startKernelAudio(); +void init_shutdown(); + + +#endif diff --git a/src/core/kernelAudio.cpp b/src/core/kernelAudio.cpp new file mode 100644 index 0000000..b487df9 --- /dev/null +++ b/src/core/kernelAudio.cpp @@ -0,0 +1,501 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * KernelAudio + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include "../utils/log.h" +#include "../glue/main.h" +#include "conf.h" +#include "mixer.h" +#include "const.h" +#include "kernelAudio.h" + + +extern KernelAudio G_KernelAudio; +extern Mixer G_Mixer; +extern Conf G_Conf; +extern bool G_audio_status; + + +using std::string; +using std::vector; + + +KernelAudio::KernelAudio() +{ + system = nullptr; + numDevs = 0; + inputEnabled = 0; + realBufsize = 0; + api = 0; +} + + +/* -------------------------------------------------------------------------- */ + + + +int KernelAudio::openDevice(int _api, int outDev, int inDev, int outChan, + int inChan, int samplerate, int buffersize) +{ + api = _api; + gu_log("[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); + else + if (api == SYS_API_WASAPI && hasAPI(RtAudio::WINDOWS_WASAPI)) + system = new RtAudio(RtAudio::WINDOWS_WASAPI); + +#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; + } + + gu_log("[KA] Opening devices %d (out), %d (in), f=%d...\n", outDev, inDev, samplerate); + + numDevs = system->getDeviceCount(); + + if (numDevs < 1) { + gu_log("[KA] no devices found with this API\n"); + closeDevice(); + G_audio_status = false; + return 0; + } + else { + gu_log("[KA] %d device(s) found\n", numDevs); + for (unsigned i=0; iopenStream( + &outParams, // output params + inDev != -1 ? &inParams : nullptr, // input params if inDevice is selected + RTAUDIO_FLOAT32, // audio format + samplerate, // sample rate + &realBufsize, // buffer size in byte + &G_Mixer.masterPlay, // audio callback + nullptr, // user data (unused) + &options); + G_audio_status = true; + +#if defined(__linux__) + if (api == SYS_API_JACK) + jackSetSyncCb(); +#endif + + return 1; + } + catch (RtAudioError &e) { + gu_log("[KA] system init error: %s\n", e.getMessage().c_str()); + closeDevice(); + G_audio_status = false; + return 0; + } +} + + +/* -------------------------------------------------------------------------- */ + + +int KernelAudio::startStream() +{ + try { + system->startStream(); + gu_log("[KA] latency = %lu\n", system->getStreamLatency()); + return 1; + } + catch (RtAudioError &e) { + gu_log("[KA] Start stream error: %s\n", e.getMessage().c_str()); + return 0; + } +} + + +/* -------------------------------------------------------------------------- */ + + +int KernelAudio::stopStream() +{ + try { + system->stopStream(); + return 1; + } + catch (RtAudioError &e) { + gu_log("[KA] Stop stream error\n"); + return 0; + } +} + + +/* -------------------------------------------------------------------------- */ + + +string KernelAudio::getDeviceName(unsigned dev) +{ + try { + return ((RtAudio::DeviceInfo) system->getDeviceInfo(dev)).name; + } + catch (RtAudioError &e) { + gu_log("[KA] invalid device ID = %d\n", dev); + return ""; + } +} + + +/* -------------------------------------------------------------------------- */ + + +int KernelAudio::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 = nullptr; + } + return 1; +} + + +/* -------------------------------------------------------------------------- */ + + +unsigned KernelAudio::getMaxInChans(int dev) +{ + if (dev == -1) return 0; + + try { + return ((RtAudio::DeviceInfo) system->getDeviceInfo(dev)).inputChannels; + } + catch (RtAudioError &e) { + gu_log("[KA] Unable to get input channels\n"); + return 0; + } +} + + +/* -------------------------------------------------------------------------- */ + + +unsigned KernelAudio::getMaxOutChans(unsigned dev) +{ + try { + return ((RtAudio::DeviceInfo) system->getDeviceInfo(dev)).outputChannels; + } + catch (RtAudioError &e) { + gu_log("[KA] Unable to get output channels\n"); + return 0; + } +} + + +/* -------------------------------------------------------------------------- */ + + +bool KernelAudio::isProbed(unsigned dev) +{ + try { + return ((RtAudio::DeviceInfo) system->getDeviceInfo(dev)).probed; + } + catch (RtAudioError &e) { + return 0; + } +} + + +/* -------------------------------------------------------------------------- */ + + +unsigned KernelAudio::getDuplexChans(unsigned dev) +{ + try { + return ((RtAudio::DeviceInfo) system->getDeviceInfo(dev)).duplexChannels; + } + catch (RtAudioError &e) { + return 0; + } +} + + +/* -------------------------------------------------------------------------- */ + + +bool KernelAudio::isDefaultIn(unsigned dev) +{ + try { + return ((RtAudio::DeviceInfo) system->getDeviceInfo(dev)).isDefaultInput; + } + catch (RtAudioError &e) { + return 0; + } +} + + +/* -------------------------------------------------------------------------- */ + + +bool KernelAudio::isDefaultOut(unsigned dev) +{ + try { + return ((RtAudio::DeviceInfo) system->getDeviceInfo(dev)).isDefaultOutput; + } + catch (RtAudioError &e) { + return 0; + } +} + + +/* -------------------------------------------------------------------------- */ + + +int KernelAudio::getTotalFreqs(unsigned dev) +{ + try { + return ((RtAudio::DeviceInfo) system->getDeviceInfo(dev)).sampleRates.size(); + } + catch (RtAudioError &e) { + return 0; + } +} + + +/* -------------------------------------------------------------------------- */ + + +int KernelAudio::getFreq(unsigned dev, int i) +{ + try { + return ((RtAudio::DeviceInfo) system->getDeviceInfo(dev)).sampleRates.at(i); + } + catch (RtAudioError &e) { + return 0; + } +} + + +/* -------------------------------------------------------------------------- */ + + +int KernelAudio::getDefaultIn() +{ + return system->getDefaultInputDevice(); +} + +int KernelAudio::getDefaultOut() +{ + return system->getDefaultOutputDevice(); +} + + +/* -------------------------------------------------------------------------- */ + + +int KernelAudio::getDeviceByName(const char *name) +{ + for (unsigned i=0; i APIs; + RtAudio::getCompiledApi(APIs); + for (unsigned i=0; irtapi_->__HACK__getJackClient(); +} + + +/* -------------------------------------------------------------------------- */ + + +void KernelAudio::jackStart() +{ + if (api == SYS_API_JACK) { + jack_client_t *client = jackGetHandle(); + jack_transport_start(client); + } +} + + +/* -------------------------------------------------------------------------- */ + + +void KernelAudio::jackStop() +{ + if (api == SYS_API_JACK) { + jack_client_t *client = jackGetHandle(); + jack_transport_stop(client); + } +} + + +/* -------------------------------------------------------------------------- */ + + +void KernelAudio::jackSetSyncCb() +{ + jack_client_t *client = jackGetHandle(); + jack_set_sync_callback(client, jackSyncCb, nullptr); + //jack_set_sync_timeout(client, 8); +} + + +/* -------------------------------------------------------------------------- */ + + +int KernelAudio::__jackSyncCb(jack_transport_state_t state, jack_position_t *pos, + void *arg) +{ + switch (state) { + case JackTransportStopped: + gu_log("[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: + gu_log("[KA] Jack transport rolling\n"); + break; + + case JackTransportStarting: + gu_log("[KA] Jack transport starting, frame=%d\n", pos->frame); + glue_startSeq(false); // false = not from GUI + if (pos->frame == 0) + glue_rewindSeq(); + break; + + default: + gu_log("[KA] Jack transport [unknown]\n"); + } + return 1; +} + +#endif diff --git a/src/core/kernelAudio.h b/src/core/kernelAudio.h new file mode 100644 index 0000000..ba3667e --- /dev/null +++ b/src/core/kernelAudio.h @@ -0,0 +1,92 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * KernelAudio + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License 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 "../deps/rtaudio-mod/RtAudio.h" +#ifdef __linux__ + #include + #include + #include +#endif + + +class KernelAudio +{ +public: + + 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); + std::string 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(); + static int jackSyncCb(jack_transport_state_t state, jack_position_t *pos, void *arg); + int __jackSyncCb(jack_transport_state_t state, jack_position_t *pos, void *arg); + +#endif + + unsigned numDevs; + bool inputEnabled; + unsigned realBufsize; // reale bufsize from the soundcard + int api; + +private: + + RtAudio *system; +}; + +#endif diff --git a/src/core/kernelMidi.cpp b/src/core/kernelMidi.cpp new file mode 100644 index 0000000..f8c519b --- /dev/null +++ b/src/core/kernelMidi.cpp @@ -0,0 +1,474 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * KernelMidi + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include +#include "../utils/log.h" +#include "../glue/channel.h" +#include "../glue/main.h" +#include "../glue/io.h" +#include "mixer.h" +#include "const.h" +#include "channel.h" +#include "sampleChannel.h" +#include "midiChannel.h" +#include "conf.h" +#include "midiMapConf.h" +#ifdef WITH_VST + #include "pluginHost.h" + #include "plugin.h" +#endif +#include "kernelMidi.h" + + +extern bool G_midiStatus; +extern Conf G_Conf; +extern Mixer G_Mixer; +extern KernelMidi G_KernelMidi; +extern MidiMapConf G_MidiMap; +#ifdef WITH_VST +extern PluginHost G_PluginHost; +#endif + +using std::string; +using std::vector; + + +KernelMidi::KernelMidi() + : numOutPorts(0), + numInPorts (0), + api (0), // one api for both in & out + midiOut (nullptr), + midiIn (nullptr), + cb_learn (nullptr), + cb_data (nullptr) +{ +} + + +/* -------------------------------------------------------------------------- */ + + +void KernelMidi::callback(double t, vector *msg, void *data) +{ + G_KernelMidi.__callback(t, msg, data); +} + + +/* -------------------------------------------------------------------------- */ + + +void KernelMidi::sendMidiLightningInitMsgs() +{ + for(unsigned i=0; igetPortCount(); + gu_log("[KM] %d output MIDI ports found\n", numOutPorts); + for (unsigned i=0; i 0) { + try { + midiOut->openPort(port, getOutPortName(port)); + gu_log("[KM] MIDI out port %d open\n", port); + + /* TODO - it shold send midiLightning message only if there is a map loaded + and available in G_MidiMap. */ + + sendMidiLightningInitMsgs(); + return 1; + } + catch (RtMidiError &error) { + gu_log("[KM] unable to open MIDI out port %d: %s\n", port, error.getMessage().c_str()); + G_midiStatus = false; + return 0; + } + } + else + return 2; +} + + +/* -------------------------------------------------------------------------- */ + + +int KernelMidi::openInDevice(int port) +{ + try { + midiIn = new RtMidiIn((RtMidi::Api) api, "Giada MIDI input"); + G_midiStatus = true; + } + catch (RtMidiError &error) { + gu_log("[KM] MIDI in device error: %s\n", error.getMessage().c_str()); + G_midiStatus = false; + return 0; + } + + /* print input ports */ + + numInPorts = midiIn->getPortCount(); + gu_log("[KM] %d input MIDI ports found\n", numInPorts); + for (unsigned i=0; i 0) { + try { + midiIn->openPort(port, getInPortName(port)); + midiIn->ignoreTypes(true, false, true); // ignore all system/time msgs, for now + gu_log("[KM] MIDI in port %d open\n", port); + midiIn->setCallback(&callback); + return 1; + } + catch (RtMidiError &error) { + gu_log("[KM] unable to open MIDI in port %d: %s\n", port, error.getMessage().c_str()); + G_midiStatus = false; + return 0; + } + } + else + return 2; +} + + +/* -------------------------------------------------------------------------- */ + + +bool KernelMidi::hasAPI(int API) +{ + vector APIs; + RtMidi::getCompiledApi(APIs); + for (unsigned i=0; igetPortName(p); } + catch (RtMidiError &error) { return ""; } +} + +string KernelMidi::getInPortName(unsigned p) +{ + try { return midiIn->getPortName(p); } + catch (RtMidiError &error) { return ""; } +} + + +/* -------------------------------------------------------------------------- */ + + +void KernelMidi::send(uint32_t data) +{ + if (!G_midiStatus) + return; + + vector msg(1, getB1(data)); + msg.push_back(getB2(data)); + msg.push_back(getB3(data)); + + midiOut->sendMessage(&msg); + gu_log("[KM] send msg=0x%X (%X %X %X)\n", data, msg[0], msg[1], msg[2]); +} + + +/* -------------------------------------------------------------------------- */ + + +void KernelMidi::send(int b1, int b2, int b3) +{ + if (!G_midiStatus) + return; + + vector msg(1, b1); + + if (b2 != -1) + msg.push_back(b2); + if (b3 != -1) + msg.push_back(b3); + + midiOut->sendMessage(&msg); + //gu_log("[KM] send msg=(%X %X %X)\n", b1, b2, b3); +} + + +/* -------------------------------------------------------------------------- */ + + +void KernelMidi::__callback(double t, 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) { + gu_log("[KM] MIDI received - unknown signal - size=%d, value=0x", (int) msg->size()); + for (unsigned i=0; isize(); i++) + gu_log("%X", (int) msg->at(i)); + gu_log("\n"); + return; + } + + /* 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 + + gu_log("[KM] MIDI received - 0x%X (chan %d)\n", 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) + cb_learn(pure, cb_data); + else { + processMaster(pure, value); + processChannels(pure, value); + } +} + + +/* -------------------------------------------------------------------------- */ + + +void KernelMidi::processMaster(uint32_t pure, uint32_t value) +{ + if (pure == G_Conf.midiInRewind) { + gu_log(" >>> rewind (master) (pure=0x%X)\n", pure); + glue_rewindSeq(); + } + else if (pure == G_Conf.midiInStartStop) { + gu_log(" >>> startStop (master) (pure=0x%X)\n", pure); + glue_startStopSeq(false); + } + else if (pure == G_Conf.midiInActionRec) { + gu_log(" >>> actionRec (master) (pure=0x%X)\n", pure); + glue_startStopActionRec(false); + } + else if (pure == G_Conf.midiInInputRec) { + gu_log(" >>> inputRec (master) (pure=0x%X)\n", pure); + glue_startStopInputRec(false); + } + else if (pure == G_Conf.midiInMetronome) { + gu_log(" >>> metronome (master) (pure=0x%X)\n", pure); + glue_startStopMetronome(false); + } + else if (pure == G_Conf.midiInVolumeIn) { + float vf = (value >> 8)/127.0f; + gu_log(" >>> input volume (master) (pure=0x%X, value=%d, float=%f)\n", + pure, value >> 8, vf); + glue_setInVol(vf, false); + } + else if (pure == G_Conf.midiInVolumeOut) { + float vf = (value >> 8)/127.0f; + gu_log(" >>> output volume (master) (pure=0x%X, value=%d, float=%f)\n", + pure, value >> 8, vf); + glue_setOutVol(vf, false); + } + else if (pure == G_Conf.midiInBeatDouble) { + gu_log(" >>> sequencer x2 (master) (pure=0x%X)\n", pure); + glue_beatsMultiply(); + } + else if (pure == G_Conf.midiInBeatHalf) { + gu_log(" >>> sequencer /2 (master) (pure=0x%X)\n", pure); + glue_beatsDivide(); + } +} + + +/* -------------------------------------------------------------------------- */ + + +void KernelMidi::processChannels(uint32_t pure, uint32_t value) +{ + for (unsigned i=0; imidiIn) + continue; + + if (pure == ch->midiInKeyPress) { + gu_log(" >>> keyPress, ch=%d (pure=0x%X)\n", ch->index, pure); + glue_keyPress(ch, false, false); + } + else if (pure == ch->midiInKeyRel) { + gu_log(" >>> keyRel ch=%d (pure=0x%X)\n", ch->index, pure); + glue_keyRelease(ch, false, false); + } + else if (pure == ch->midiInMute) { + gu_log(" >>> mute ch=%d (pure=0x%X)\n", ch->index, pure); + glue_setMute(ch, false); + } + else if (pure == ch->midiInSolo) { + gu_log(" >>> solo ch=%d (pure=0x%X)\n", ch->index, pure); + ch->solo ? glue_setSoloOn(ch, false) : glue_setSoloOff(ch, false); + } + else if (pure == ch->midiInVolume) { + float vf = (value >> 8)/127.0f; + gu_log(" >>> volume ch=%d (pure=0x%X, value=%d, float=%f)\n", + 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] + gu_log(" >>> pitch ch=%d (pure=0x%X, value=%d, float=%f)\n", + ch->index, pure, value >> 8, vf); + glue_setPitch(nullptr, (SampleChannel*)ch, vf, false); + } + else if (pure == ((SampleChannel*)ch)->midiInReadActions) { + gu_log(" >>> start/stop read actions ch=%d (pure=0x%X)\n", ch->index, pure); + glue_startStopReadingRecs((SampleChannel*)ch, false); + } + +#ifdef WITH_VST + + /* Process plugins' parameters */ + + processPlugins(ch, pure, value); + +#endif + + /* Redirect full midi message (pure + value) to plugins */ + + ch->receiveMidi(pure | value); + } +} + + +/* -------------------------------------------------------------------------- */ + + +#ifdef WITH_VST + +void KernelMidi::processPlugins(Channel *ch, uint32_t pure, uint32_t value) +{ + /* Plugins' parameters layout reflect the structure of the matrix + Channel::midiInPlugins. It is safe to assume then that i and k indexes match + both the structure of Channel::midiInPlugins and vector *plugins. */ + + vector *plugins = G_PluginHost.getStack(PluginHost::CHANNEL, ch); + + for (unsigned i=0; isize(); i++) + { + Plugin *plugin = plugins->at(i); + for (unsigned k=0; kmidiInParams.size(); k++) { + uint32_t midiInParam = plugin->midiInParams.at(k); + if (pure != midiInParam) + continue; + float vf = (value >> 8)/127.0f; + plugin->setParameter(k, vf); + gu_log(" >>> [plugin %d parameter %d] ch=%d (pure=0x%X, value=%d, float=%f)\n", + i, k, ch->index, pure, value >> 8, vf); + } + } +} + +#endif + + +/* -------------------------------------------------------------------------- */ + + +string KernelMidi::getRtMidiVersion() +{ + return midiOut->getVersion(); +} diff --git a/src/core/kernelMidi.h b/src/core/kernelMidi.h new file mode 100644 index 0000000..e8cb0f4 --- /dev/null +++ b/src/core/kernelMidi.h @@ -0,0 +1,127 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * KernelMidi + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License 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 + + +#ifdef __APPLE__ // our compiler still doesn't know about cstdint (c++11 stuff) + #include +#else + #include +#endif +#include +#include + + +class KernelMidi +{ +public: + + unsigned numOutPorts; + unsigned numInPorts; + + KernelMidi(); + + typedef void (cb_midiLearn) (uint32_t, void *); + + void startMidiLearn(cb_midiLearn *cb, void *data); + void stopMidiLearn(); + + int getB1(uint32_t iValue) { return (iValue >> 24) & 0xFF; } + int getB2(uint32_t iValue) { return (iValue >> 16) & 0xFF; } + int getB3(uint32_t iValue) { return (iValue >> 8) & 0xFF; } + + uint32_t getIValue(int b1, int b2, int b3) { + return (b1 << 24) | (b2 << 16) | (b3 << 8) | (0x00); + } + + /* 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'. */ + + std::string getInPortName(unsigned p); + std::string getOutPortName(unsigned p); + + bool hasAPI(int API); + + std::string getRtMidiVersion(); + +private: + + int api; + class RtMidiOut *midiOut; + class RtMidiIn *midiIn; + + /* cb_learn + * callback prepared by the gdMidiGrabber window and called by + * kernelMidi. It contains things to do once the midi message has been + * stored. */ + + cb_midiLearn *cb_learn; + void *cb_data; + + /* callback + * master callback for input events. */ + + static void callback(double t, std::vector *msg, void *data); + void __callback(double t, std::vector *msg, void *data); + + void sendMidiLightningInitMsgs(); + + + void processMaster(uint32_t pure, uint32_t value); + void processChannels(uint32_t pure, uint32_t value); +#ifdef WITH_VST + void processPlugins(class Channel *ch, uint32_t pure, uint32_t value); +#endif +}; + +#endif diff --git a/src/core/midiChannel.cpp b/src/core/midiChannel.cpp new file mode 100644 index 0000000..ad531d6 --- /dev/null +++ b/src/core/midiChannel.cpp @@ -0,0 +1,374 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * channel + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include "../utils/log.h" +#include "midiChannel.h" +#include "channel.h" +#include "patch_DEPR_.h" +#include "patch.h" +#include "conf.h" +#include "mixer.h" +#ifdef WITH_VST + #include "pluginHost.h" +#endif +#include "kernelMidi.h" + + +extern Recorder G_Recorder; +extern KernelMidi G_KernelMidi; +extern Mixer G_Mixer; +#ifdef WITH_VST +extern PluginHost G_PluginHost; +#endif + +MidiChannel::MidiChannel(int bufferSize, MidiMapConf *midiMapConf) + : Channel (CHANNEL_MIDI, STATUS_OFF, bufferSize, midiMapConf), + midiOut (false), + midiOutChan(MIDI_CHANS[0]) +{ +} + + +/* -------------------------------------------------------------------------- */ + + +MidiChannel::~MidiChannel() {} + + +/* -------------------------------------------------------------------------- */ + + +void MidiChannel::copy(const Channel *_src, pthread_mutex_t *pluginMutex) +{ + Channel::copy(_src, pluginMutex); + MidiChannel *src = (MidiChannel *) _src; + midiOut = src->midiOut; + midiOutChan = src->midiOutChan; +} + + +/* -------------------------------------------------------------------------- */ + + +#ifdef WITH_VST + +void MidiChannel::addVstMidiEvent(uint32_t msg, int localFrame) +{ + juce::MidiMessage message = juce::MidiMessage( + G_KernelMidi.getB1(msg), + G_KernelMidi.getB2(msg), + G_KernelMidi.getB3(msg)); + midiBuffer.addEvent(message, localFrame); +} + +#endif + + +/* -------------------------------------------------------------------------- */ + + +void MidiChannel::onBar(int frame) {} + + +/* -------------------------------------------------------------------------- */ + + +void MidiChannel::stop() {} + + +/* -------------------------------------------------------------------------- */ + + +void MidiChannel::empty() {} + + +/* -------------------------------------------------------------------------- */ + + +void MidiChannel::quantize(int index, int localFrame, Mixer *m) {} + + +/* -------------------------------------------------------------------------- */ + + +void MidiChannel::parseAction(Recorder::action *a, int localFrame, + int globalFrame, int quantize, bool mixerIsRunning) +{ + if (a->type == ACTION_MIDI) + sendMidi(a, localFrame/2); +} + + +/* -------------------------------------------------------------------------- */ + + +void MidiChannel::onZero(int frame, bool recsStopOnChanHalt) +{ + if (status == STATUS_ENDING) { + status = STATUS_OFF; + sendMidiLplay(); + } + else + if (status == STATUS_WAIT) { + status = STATUS_PLAY; + sendMidiLplay(); + } +} + + +/* -------------------------------------------------------------------------- */ + + +void MidiChannel::setMute(bool internal) +{ + mute = true; // internal mute does not exist for midi (for now) + if (midiOut) + G_KernelMidi.send(MIDI_ALL_NOTES_OFF); +#ifdef WITH_VST + addVstMidiEvent(MIDI_ALL_NOTES_OFF, 0); +#endif + sendMidiLmute(); +} + + +/* -------------------------------------------------------------------------- */ + + +void MidiChannel::unsetMute(bool internal) +{ + mute = false; // internal mute does not exist for midi (for now) + sendMidiLmute(); +} + + +/* -------------------------------------------------------------------------- */ + + +void MidiChannel::process(float *outBuffer, float *inBuffer) +{ +#ifdef WITH_VST + pluginHost->processStack(vChan, PluginHost::CHANNEL, this); +#endif + + /* TODO - isn't this useful only if WITH_VST ? */ + for (int j=0; jgetVol(i); + index = patch->getIndex(i); + mute = patch->getMute(i); + mute_s = patch->getMute_s(i); + solo = patch->getSolo(i); + panLeft = patch->getPanLeft(i); + panRight = patch->getPanRight(i); + + midiOut = patch->getMidiValue(i, "Out"); + midiOutChan = patch->getMidiValue(i, "OutChan"); + + readPatchMidiIn_DEPR_(i, *patch); + readPatchMidiOut_DEPR_(i, *patch); + + return SAMPLE_LOADED_OK; /// TODO - change name, it's meaningless here +} + + +/* -------------------------------------------------------------------------- */ + + +int MidiChannel::readPatch(const string &basePath, int i, Patch *patch, + pthread_mutex_t *pluginMutex, int samplerate, int rsmpQuality) +{ + Channel::readPatch("", i, patch, pluginMutex, samplerate, rsmpQuality); + + Patch::channel_t *pch = &patch->channels.at(i); + + midiOut = pch->midiOut; + midiOutChan = pch->midiOutChan; + + return SAMPLE_LOADED_OK; /// TODO - change name, it's meaningless here +} + + +/* -------------------------------------------------------------------------- */ + + +void MidiChannel::sendMidi(Recorder::action *a, int localFrame) +{ + if (status & (STATUS_PLAY | STATUS_ENDING) && !mute) { + if (midiOut) + G_KernelMidi.send(a->iValue | MIDI_CHANS[midiOutChan]); + +#ifdef WITH_VST + addVstMidiEvent(a->iValue, localFrame); +#endif + } +} + + +void MidiChannel::sendMidi(uint32_t data) +{ + if (status & (STATUS_PLAY | STATUS_ENDING) && !mute) { + if (midiOut) + G_KernelMidi.send(data | MIDI_CHANS[midiOutChan]); +#ifdef WITH_VST + addVstMidiEvent(data, 0); +#endif + } +} + + +/* -------------------------------------------------------------------------- */ + + +void MidiChannel::rewind() +{ + if (midiOut) + G_KernelMidi.send(MIDI_ALL_NOTES_OFF); +#ifdef WITH_VST + addVstMidiEvent(MIDI_ALL_NOTES_OFF, 0); +#endif +} + + +/* -------------------------------------------------------------------------- */ + + +int MidiChannel::writePatch(int i, bool isProject, Patch *patch) +{ + int pchIndex = Channel::writePatch(i, isProject, patch); + Patch::channel_t *pch = &patch->channels.at(pchIndex); + + pch->midiOut = midiOut; + pch->midiOutChan = midiOutChan; + + return 0; +} + + +/* -------------------------------------------------------------------------- */ + + +void MidiChannel::receiveMidi(uint32_t msg) +{ + if (!armed) + return; + +#ifdef WITH_VST + + while (true) { + if (pthread_mutex_trylock(&G_PluginHost.mutex_midi) != 0) + continue; + gu_log("[Channel::processMidi] msg=%X\n", msg); + addVstMidiEvent(msg, 0); + pthread_mutex_unlock(&G_PluginHost.mutex_midi); + break; + } + +#endif + + if (G_Recorder.canRec(this, &G_Mixer)) { + G_Recorder.rec(index, ACTION_MIDI, G_Mixer.currentFrame, msg); + hasActions = true; + } +} + + +/* -------------------------------------------------------------------------- */ + + +bool MidiChannel::canInputRec() +{ + return false; // midi channels don't handle input audio +} + + +/* -------------------------------------------------------------------------- */ + + +void MidiChannel::clear() {} diff --git a/src/core/midiChannel.h b/src/core/midiChannel.h new file mode 100644 index 0000000..94336b8 --- /dev/null +++ b/src/core/midiChannel.h @@ -0,0 +1,97 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * channel + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License 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 + #include "../deps/juce-config.h" +#endif + +#include "channel.h" + + +class MidiChannel : public Channel +{ +public: + + MidiChannel(int bufferSize, class MidiMapConf *midiMapConf); + ~MidiChannel(); + + bool midiOut; // enable midi output + uint8_t midiOutChan; // midi output channel + + void copy(const Channel *src, pthread_mutex_t *pluginMutex) override; + void clear() override; + void process(float *outBuffer, float *inBuffer) override; + void start(int frame, bool doQuantize, int quantize, bool mixerIsRunning, + bool forceStart, bool isUserGenerated) override; + void kill(int frame) override; + void empty() override; + void stopBySeq(bool chansStopOnSeqHalt) override; + void stop() override; + void rewind() override; + void setMute(bool internal) override; + void unsetMute(bool internal) override; + int readPatch_DEPR_(const char *file, int i, class Patch_DEPR_ *patch, + int samplerate, int rsmpQuality) override; + int readPatch(const string &basePath, int i, class Patch *patch, + pthread_mutex_t *pluginMutex, int samplerate, int rsmpQuality) override; + int writePatch(int i, bool isProject, class Patch *patch) override; + void quantize(int index, int localFrame, class Mixer *m) override; + void onZero(int frame, bool recsStopOnChanHalt) override; + void onBar(int frame) override; + void parseAction(Recorder::action *a, int localFrame, int globalFrame, + int quantize, bool mixerIsRunning) override; + void receiveMidi(uint32_t msg) override; + bool canInputRec() override; + + /* ------------------------------------------------------------------------ */ + + /* sendMidi + * send Midi event to the outside world. */ + + void sendMidi(Recorder::action *a, int localFrame); + void sendMidi(uint32_t data); + +#ifdef WITH_VST + + /* addVstMidiEvent + * Add a new Midi event to the midiEvent stack fom a composite uint32_t raw + * Midi event. LocalFrame is the offset: it tells where to put the event + * inside the buffer. */ + + void addVstMidiEvent(uint32_t msg, int localFrame); + +#endif +}; + + +#endif diff --git a/src/core/midiMapConf.cpp b/src/core/midiMapConf.cpp new file mode 100644 index 0000000..c2b40eb --- /dev/null +++ b/src/core/midiMapConf.cpp @@ -0,0 +1,432 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * midiMapConf + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include +#include +#include +#include +#include +#include +#include "midiMapConf.h" +#include "const.h" +#include "../utils/string.h" +#include "../utils/log.h" + + +using std::string; +using std::vector; + + + +void MidiMapConf::init() +{ + midimapsPath = gu_getHomePath() + G_SLASH + "midimaps" + G_SLASH; + + /* scan dir of midi maps and load the filenames into <>maps. */ + + gu_log("[MidiMapConf::init] scanning midimaps directory...\n"); + + DIR *dp; + dirent *ep; + dp = opendir(midimapsPath.c_str()); + + if (!dp) { + gu_log("[MidiMapConf::init] unable to scan midimaps directory!\n"); + return; + } + + while ((ep = readdir(dp))) { + if (!strcmp(ep->d_name, ".") || !strcmp(ep->d_name, "..")) + continue; + + // TODO - check if is a valid midimap file (verify headers) + + gu_log("[MidiMapConf::init] found midimap '%s'\n", ep->d_name); + + maps.push_back(ep->d_name); + } + + gu_log("[MidiMapConf::init] total midimaps found: %d\n", maps.size()); + closedir(dp); +} + + +/* -------------------------------------------------------------------------- */ + + +void MidiMapConf::setDefault() +{ + brand = ""; + device = ""; + muteOn.channel = 0; + muteOn.valueStr = ""; + muteOn.offset = -1; + muteOn.value = 0; + muteOff.channel = 0; + muteOff.valueStr = ""; + muteOff.offset = -1; + muteOff.value = 0; + soloOn.channel = 0; + soloOn.valueStr = ""; + soloOn.offset = -1; + soloOn.value = 0; + soloOff.channel = 0; + soloOff.valueStr = ""; + soloOff.offset = -1; + soloOff.value = 0; + waiting.channel = 0; + waiting.valueStr = ""; + waiting.offset = -1; + waiting.value = 0; + playing.channel = 0; + playing.valueStr = ""; + playing.offset = -1; + playing.value = 0; + stopping.channel = 0; + stopping.valueStr = ""; + stopping.offset = -1; + stopping.value = 0; + stopped.channel = 0; + stopped.valueStr = ""; + stopped.offset = -1; + stopped.value = 0; +} + + +/* -------------------------------------------------------------------------- */ + + +int MidiMapConf::read(const string &file) +{ + if (file.empty()) { + gu_log("[MidiMapConf::read] midimap not specified, nothing to do\n"); + return MIDIMAP_NOT_SPECIFIED; + } + + gu_log("[MidiMapConf::read] reading midimap file '%s'\n", file.c_str()); + + string path = midimapsPath + file; + jRoot = json_load_file(path.c_str(), 0, &jError); + if (!jRoot) { + gu_log("[MidiMapConf::read] unreadable midimap file. Error on line %d: %s\n", jError.line, jError.text); + return MIDIMAP_UNREADABLE; + } + + if (!setString(jRoot, MIDIMAP_KEY_BRAND, brand)) return MIDIMAP_UNREADABLE; + if (!setString(jRoot, MIDIMAP_KEY_DEVICE, device)) return MIDIMAP_UNREADABLE; + if (!readInitCommands(jRoot)) return MIDIMAP_UNREADABLE; + if (!readCommand(jRoot, &muteOn, MIDIMAP_KEY_MUTE_ON)) return MIDIMAP_UNREADABLE; + if (!readCommand(jRoot, &muteOff, MIDIMAP_KEY_MUTE_OFF)) return MIDIMAP_UNREADABLE; + if (!readCommand(jRoot, &soloOn, MIDIMAP_KEY_SOLO_ON)) return MIDIMAP_UNREADABLE; + if (!readCommand(jRoot, &soloOff, MIDIMAP_KEY_SOLO_OFF)) return MIDIMAP_UNREADABLE; + if (!readCommand(jRoot, &waiting, MIDIMAP_KEY_WAITING)) return MIDIMAP_UNREADABLE; + if (!readCommand(jRoot, &playing, MIDIMAP_KEY_PLAYING)) return MIDIMAP_UNREADABLE; + if (!readCommand(jRoot, &stopping, MIDIMAP_KEY_STOPPING)) return MIDIMAP_UNREADABLE; + if (!readCommand(jRoot, &stopped, MIDIMAP_KEY_STOPPED)) return MIDIMAP_UNREADABLE; + + /* parse messages */ + + parse(&muteOn); + parse(&muteOff); + parse(&soloOn); + parse(&soloOff); + parse(&waiting); + parse(&playing); + parse(&stopping); + parse(&stopped); + + return MIDIMAP_READ_OK; +} + + +/* -------------------------------------------------------------------------- */ + + +bool MidiMapConf::readInitCommands(json_t *jContainer) +{ + json_t *jInitCommands = json_object_get(jContainer, MIDIMAP_KEY_INIT_COMMANDS); + if (!checkArray(jInitCommands, MIDIMAP_KEY_INIT_COMMANDS)) + return 0; + + size_t commandIndex; + json_t *jInitCommand; + json_array_foreach(jInitCommands, commandIndex, jInitCommand) { + + string indexStr = "init command " + gu_itoa(commandIndex); + if (!checkObject(jInitCommand, indexStr.c_str())) + return 0; + + message_t message; + if (!setInt(jInitCommand, MIDIMAP_KEY_CHANNEL, message.channel)) return 0; + if (!setString(jInitCommand, MIDIMAP_KEY_MESSAGE, message.valueStr)) return 0; + message.value = strtoul(message.valueStr.c_str(), NULL, 16); + + initCommands.push_back(message); + } + + return 1; +} + + +/* -------------------------------------------------------------------------- */ + + +bool MidiMapConf::readCommand(json_t *jContainer, message_t *msg, const string &key) +{ + json_t *jCommand = json_object_get(jContainer, key.c_str()); + if (!checkObject(jCommand, key.c_str())) + return 0; + + if (!setInt(jCommand, MIDIMAP_KEY_CHANNEL, msg->channel)) return 0; + if (!setString(jCommand, MIDIMAP_KEY_MESSAGE, msg->valueStr)) return 0; + + return 1; +} + + +/* -------------------------------------------------------------------------- */ + + +void MidiMapConf::parse(message_t *message) +{ + /* Remove '0x' part from the original string. */ + + string input = message->valueStr.replace(0, 2, ""); + + /* Then transform string value into the actual uint32_t value, by parsing + * each char (i.e. nibble) in the original string. Substitute 'n' with + * zeros. */ + + string output; + for (unsigned i=0, p=24; ioffset == -1) // do it once + message->offset = p; + } + else + output += input[i]; + } + + /* from string to uint32_t */ + + message->value = strtoul(output.c_str(), NULL, 16); + + gu_log("[MidiMapConf::parse] parsed chan=%d valueStr=%s value=%#x, offset=%d\n", + message->channel, message->valueStr.c_str(), message->value, message->offset); +} + + +/* -------------------------------------------------------------------------- */ + + +void MidiMapConf::setDefault_DEPR_() +{ + brand = ""; + device = ""; + + for (int i=0; i ic; + gu_split(getValue("init_commands"), ";", &ic); + for (unsigned i=0; i<(unsigned)MAX_INIT_COMMANDS && i. + * + * -------------------------------------------------------------------------- */ + + +#ifndef __MIDIMAPCONF_H__ +#define __MIDIMAPCONF_H__ + + +#include +#include +#include +#include "dataStorageIni.h" +#include "dataStorageJson.h" +#include "../utils/fs.h" +#if defined(__APPLE__) +#include +#endif + + +using std::string; +using std::vector; + + +class MidiMapConf : public DataStorageIni, public DataStorageJson +{ +public: + + struct message_t + { + int channel; + string valueStr; + int offset; + uint32_t value; + }; + + string brand; + string device; + vector initCommands; + message_t muteOn; + message_t muteOff; + message_t soloOn; + message_t soloOff; + message_t waiting; + message_t playing; + message_t stopping; + message_t stopped; + + /* midimapsPath + * path of midimap files, different between OSes. */ + + string midimapsPath; + + /* maps + * Maps are the available .giadamap files. Each element of the vector + * represents a .giadamap filename. */ + + vector maps; + + /* init + Parse the midi maps folders and find the available maps. */ + + void init(); + + /* setDefault + Set default values in case no maps are available/choosen. */ + + void setDefault(); + + /* read + Read a midi map from file 'file'. */ + + int read(const string &file); + + /* --- DEPRECATED STUFF --------------------------------------------------- */ + /* --- DEPRECATED STUFF --------------------------------------------------- */ + /* --- DEPRECATED STUFF --------------------------------------------------- */ + + static const int MAX_INIT_COMMANDS = 32; + static const int MAX_MIDI_BYTES = 4; + static const int MAX_MIDI_NIBBLES = 8; + + /* init_* + * init_commands. These messages are sent to the physical device as a wake up + * signal. */ + + int init_channels[MAX_INIT_COMMANDS]; + uint32_t init_messages[MAX_INIT_COMMANDS]; + + /* events + * [event]Channel: the MIDI output channel to send the event to + * [event]notePos: the byte where the note is stored ('nn' placeholder) + * [event]offset: the note offset (i.e. of 'nn' placeholder) */ + + int muteOnChan; + int muteOnOffset; + uint32_t muteOnMsg; + + int muteOffChan; + int muteOffOffset; + uint32_t muteOffMsg; + + int soloOnChan; + int soloOnOffset; + uint32_t soloOnMsg; + + int soloOffChan; + int soloOffOffset; + uint32_t soloOffMsg; + + int waitingChan; + int waitingOffset; + uint32_t waitingMsg; + + int playingChan; + int playingOffset; + uint32_t playingMsg; + + int stoppingChan; + int stoppingOffset; + uint32_t stoppingMsg; + + int stoppedChan; + int stoppedOffset; + uint32_t stoppedMsg; + + /* setDefault + Set default values in case no maps are available/choosen. */ + + void setDefault_DEPR_(); + + /* readMap + Read a midi map from file 'file'. */ + + int readMap_DEPR_(string file); + +private: + + bool readInitCommands(json_t *jContainer); + + bool readCommand(json_t *jContainer, message_t *msg, const string &key); + + void parse(message_t *message); + + /* --- DEPRECATED STUFF --------------------------------------------------- */ + /* --- DEPRECATED STUFF --------------------------------------------------- */ + /* --- DEPRECATED STUFF --------------------------------------------------- */ + + void close_DEPR_(); + void parse_DEPR_(string key, int *chan, uint32_t *msg, int *offset); +}; + +#endif diff --git a/src/core/mixer.cpp b/src/core/mixer.cpp new file mode 100644 index 0000000..efe2532 --- /dev/null +++ b/src/core/mixer.cpp @@ -0,0 +1,797 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * mixer + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include "../utils/log.h" +#include "wave.h" +#include "recorder.h" +#include "pluginHost.h" +#include "patch_DEPR_.h" +#include "conf.h" +#include "mixerHandler.h" +#include "channel.h" +#include "sampleChannel.h" +#include "midiChannel.h" +#include "kernelMidi.h" +#include "mixer.h" + + +extern KernelAudio G_KernelAudio; +extern Mixer G_Mixer; +extern Recorder G_Recorder; +extern KernelMidi G_KernelMidi; +extern MidiMapConf G_MidiMap; +extern Patch_DEPR_ G_Patch_DEPR_; +extern Conf G_Conf; +#ifdef WITH_VST +extern PluginHost G_PluginHost; +#endif + + +Mixer::Mixer() + : vChanInput(NULL), + vChanInToOut(NULL) +{} + + +/* -------------------------------------------------------------------------- */ + + +#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; + recording = false; + ready = true; + waitRec = 0; + currentFrame = 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; + 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(G_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 = G_KernelAudio.realBufsize * 2; + + if (type == CHANNEL_SAMPLE) + ch = new SampleChannel(bufferSize, &G_MidiMap); + else + ch = new MidiChannel(bufferSize, &G_MidiMap); + +#ifdef WITH_VST + ch->setPluginHost(&G_PluginHost); +#endif + + while (true) { + int lockStatus = pthread_mutex_trylock(&mutex_chans); + if (lockStatus == 0) { + channels.push_back(ch); + pthread_mutex_unlock(&mutex_chans); + break; + } + } + + ch->index = getNewIndex(); + gu_log("[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 index = -1; + for (unsigned i=0; iindex); + return 0; + } + + int lockStatus; + while (true) { + lockStatus = pthread_mutex_trylock(&mutex_chans); + if (lockStatus == 0) { + channels.erase(channels.begin() + index); + delete ch; + pthread_mutex_unlock(&mutex_chans); + return 1; + } + //else + // gu_log("[mixer::deleteChannel] waiting for mutex...\n"); + } +} + + +/* -------------------------------------------------------------------------- */ + + +Channel *Mixer::getChannelByIndex(int index) +{ + for (unsigned i=0; iindex == index) + return channels.at(i); + gu_log("[mixer::getChannelByIndex] channel at index %d not found!\n", index); + return NULL; +} + + +/* -------------------------------------------------------------------------- */ + + +void Mixer::sendMIDIsync() +{ + if (G_Conf.midiSync == MIDI_SYNC_CLOCK_M) { + if (currentFrame % (framesPerBeat/24) == 0) + G_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 (currentFrame % midiTCrate == 0) { + + /* frame low nibble + * frame high nibble + * seconds low nibble + * seconds high nibble */ + + if (midiTCframes % 2 == 0) { + G_KernelMidi.send(MIDI_MTC_QUARTER, (midiTCframes & 0x0F) | 0x00, -1); + G_KernelMidi.send(MIDI_MTC_QUARTER, (midiTCframes >> 4) | 0x10, -1); + G_KernelMidi.send(MIDI_MTC_QUARTER, (midiTCseconds & 0x0F) | 0x20, -1); + G_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 { + G_KernelMidi.send(MIDI_MTC_QUARTER, (midiTCminutes & 0x0F) | 0x40, -1); + G_KernelMidi.send(MIDI_MTC_QUARTER, (midiTCminutes >> 4) | 0x50, -1); + G_KernelMidi.send(MIDI_MTC_QUARTER, (midiTChours & 0x0F) | 0x60, -1); + G_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; + } + } + //gu_log("%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) { + G_KernelMidi.send(MIDI_SYSEX, 0x7F, 0x00); // send msg on channel 0 + G_KernelMidi.send(0x01, 0x01, 0x00); // hours 0 + G_KernelMidi.send(0x00, 0x00, 0x00); // mins, secs, frames 0 + G_KernelMidi.send(MIDI_EOX, -1, -1); // end of sysex + } +} + +/* -------------------------------------------------------------------------- */ + + +int Mixer::masterPlay(void *outBuf, void *inBuf, unsigned bufferSize, + double streamTime, RtAudioStreamStatus status, void *userData) +{ + return G_Mixer.__masterPlay(outBuf, inBuf, bufferSize); +} + + +/* -------------------------------------------------------------------------- */ + + +int Mixer::__masterPlay(void *_outBuf, void *_inBuf, unsigned bufferSize) +{ + if (!ready) + return 0; + + float *outBuf = (float *) _outBuf; + float *inBuf = G_KernelAudio.inputEnabled ? (float *) _inBuf : nullptr; + bufferSize *= 2; // stereo + peakOut = 0.0f; // reset peak calculator + peakIn = 0.0f; // reset peak calculator + + clearAllBuffers(outBuf, bufferSize); + + for (unsigned j=0; j 0) + deleteChannel(channels.at(0)); + + if (vChanInput) { + free(vChanInput); + vChanInput = NULL; + } + if (vChanInToOut) { + free(vChanInToOut); + vChanInToOut = NULL; + } + return 1; +} + + +/* -------------------------------------------------------------------------- */ + + +bool Mixer::isSilent() +{ + for (unsigned i=0; istatus == STATUS_PLAY) + return false; + return true; +} + + +/* -------------------------------------------------------------------------- */ + + +void Mixer::rewind() +{ + currentFrame = 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; +} + + +/* -------------------------------------------------------------------------- */ + + +void Mixer::mergeVirtualInput() +{ + for (unsigned i=0; itype == CHANNEL_MIDI) + continue; + SampleChannel *ch = (SampleChannel*) channels.at(i); + if (ch->armed) + memcpy(ch->wave->data, vChanInput, totalFrames * sizeof(float)); + } + memset(vChanInput, 0, totalFrames * sizeof(float)); // clear vchan +} + + +/* -------------------------------------------------------------------------- */ + + +void Mixer::lineInRec(float *inBuf, unsigned frame) +{ + if (!mh_hasArmedSampleChannels() || !G_KernelAudio.inputEnabled || !recording) + return; + + /* 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; + return; + } + + vChanInput[inputTracker] += inBuf[frame] * inVol; + vChanInput[inputTracker+1] += inBuf[frame+1] * inVol; + inputTracker += 2; + if (inputTracker >= totalFrames) + inputTracker = 0; +} + + +/* -------------------------------------------------------------------------- */ + + +void Mixer::processLineIn(float *inBuf, unsigned frame) +{ + if (!G_KernelAudio.inputEnabled) + return; + + /* input peak calculation (left chan only so far). */ + + if (inBuf[frame] * inVol > peakIn) + peakIn = inBuf[frame] * inVol; + + /* "hear what you're playing" - process, copy and paste the input buffer + * onto the output buffer */ + + if (inToOut) { + vChanInToOut[frame] = inBuf[frame] * inVol; + vChanInToOut[frame+1] = inBuf[frame+1] * inVol; + } +} + + +/* -------------------------------------------------------------------------- */ + + +void Mixer::readActions(unsigned frame) +{ + pthread_mutex_lock(&mutex_recs); + for (unsigned i=0; ichan; + Channel *ch = getChannelByIndex(index); + ch->parseAction(G_Recorder.global.at(i).at(j), frame, currentFrame, quantize, running); + } + break; + } + } + pthread_mutex_unlock(&mutex_recs); +} + + +/* -------------------------------------------------------------------------- */ + + +void Mixer::doQuantize(unsigned frame) +{ + if (quantize < 0 || quanto <= 0) // if quantizer disabled + return; + if (currentFrame % (quanto) != 0) // if a quanto has not passed yet + return; + + if (rewindWait) { + rewindWait = false; + rewind(); + } + pthread_mutex_lock(&mutex_chans); + for (unsigned i=0; iquantize(i, frame, this); // j == localFrame + pthread_mutex_unlock(&mutex_chans); +} + + +/* -------------------------------------------------------------------------- */ + + +void Mixer::testBar(unsigned frame) +{ + if (currentFrame % framesPerBar != 0 || currentFrame == 0) + return; + + if (metronome) + tickPlay = true; + + pthread_mutex_lock(&mutex_chans); + for (unsigned k=0; konBar(frame); + pthread_mutex_unlock(&mutex_chans); +} + + +/* -------------------------------------------------------------------------- */ + + +void Mixer::testFirstBeat(unsigned frame) +{ + if (currentFrame != 0) + return; + pthread_mutex_lock(&mutex_chans); + for (unsigned k=0; konZero(frame, G_Conf.recsStopOnChanHalt); + pthread_mutex_unlock(&mutex_chans); +} + + +/* -------------------------------------------------------------------------- */ + + +void Mixer::testLastBeat() +{ + /* if currentFrame > totalFrames the sequencer returns to frame 0, + * beat 0. This must be the last operation. */ + + if (currentFrame > totalFrames) { + currentFrame = 0; + actualBeat = 0; + } + else + if (currentFrame % framesPerBeat == 0 && currentFrame > 0) { + actualBeat++; + + /* avoid tick and tock to overlap when a new bar has passed (which + * is also a beat) */ + + if (metronome && !tickPlay) + tockPlay = true; + } +} + + +/* -------------------------------------------------------------------------- */ + + +void Mixer::sumChannels(unsigned frame) +{ + pthread_mutex_lock(&mutex_chans); + for (unsigned k=0; ktype == CHANNEL_SAMPLE) + ((SampleChannel*)channels.at(k))->sum(frame, running); + } + pthread_mutex_unlock(&mutex_chans); +} + + +/* -------------------------------------------------------------------------- */ + + +void Mixer::renderMetronome(float *outBuf, unsigned frame) +{ + if (tockPlay) { + outBuf[frame] += tock[tockTracker]; + outBuf[frame+1] += tock[tockTracker]; + tockTracker++; + if (tockTracker >= TICKSIZE-1) { + tockPlay = false; + tockTracker = 0; + } + } + if (tickPlay) { + outBuf[frame] += tick[tickTracker]; + outBuf[frame+1] += tick[tickTracker]; + tickTracker++; + if (tickTracker >= TICKSIZE-1) { + tickPlay = false; + tickTracker = 0; + } + } +} + + +/* -------------------------------------------------------------------------- */ + + +void Mixer::renderIO(float *outBuf, float *inBuf) +{ + pthread_mutex_lock(&mutex_chans); + for (unsigned k=0; kprocess(outBuf, inBuf); + pthread_mutex_unlock(&mutex_chans); + +#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 +} + + +/* -------------------------------------------------------------------------- */ + + +void Mixer::computePeak(float *outBuf, unsigned frame) +{ + /* TODO it takes into account only left channel so far! */ + if (outBuf[frame] > peakOut) + peakOut = outBuf[frame]; +} + + +/* -------------------------------------------------------------------------- */ + + +void Mixer::limitOutput(float *outBuf, unsigned frame) +{ + if (outBuf[frame] > 1.0f) + outBuf[frame] = 1.0f; + else + if (outBuf[frame] < -1.0f) + outBuf[frame] = -1.0f; + + if (outBuf[frame+1] > 1.0f) + outBuf[frame+1] = 1.0f; + else + if (outBuf[frame+1] < -1.0f) + outBuf[frame+1] = -1.0f; +} + + +/* -------------------------------------------------------------------------- */ + + +void Mixer::finalizeOutput(float *outBuf, unsigned frame) +{ + /* merge vChanInToOut, if enabled */ + + if (inToOut) { + outBuf[frame] += vChanInToOut[frame]; + outBuf[frame+1] += vChanInToOut[frame+1]; + } + outBuf[frame] *= outVol; + outBuf[frame+1] *= outVol; +} + + +/* -------------------------------------------------------------------------- */ + + +void Mixer::clearAllBuffers(float *outBuf, unsigned bufferSize) +{ + memset(outBuf, 0, sizeof(float) * bufferSize); // out + memset(vChanInToOut, 0, sizeof(float) * bufferSize); // inToOut vChan + + pthread_mutex_lock(&mutex_chans); + for (unsigned i=0; iclear(); + pthread_mutex_unlock(&mutex_chans); +} diff --git a/src/core/mixer.h b/src/core/mixer.h new file mode 100644 index 0000000..66d0823 --- /dev/null +++ b/src/core/mixer.h @@ -0,0 +1,268 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * mixer + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License 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 "kernelAudio.h" + + +class Mixer +{ +public: + + 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 *outBuf, void *inBuf, unsigned bufferSize, + double streamTime, RtAudioStreamStatus status, void *userData); + int __masterPlay(void *outBuf, void *inBuf, unsigned bufferSize); + + /* 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() */ + + void mergeVirtualInput(); + + /* getChannelByIndex + * return channel with given index 'i'. */ + + Channel *getChannelByIndex(int i); + + /* getLastChannel + * Return last channel in the stack. */ + + inline Channel* getLastChannel() { return channels.back(); } + + + /* ---------------------------------------------------------------- */ + + + 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 + }; + + std::vector channels; + + bool running; + bool recording; // is recording something? + 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 currentFrame; + int actualBeat; + +#define TICKSIZE 38 + static float tock[TICKSIZE]; + static float tick[TICKSIZE]; + int tickTracker, tockTracker; + bool tickPlay, tockPlay; // 1 = play, 0 = stop + + /* 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(); + + /* lineInRec + Records from line in. */ + + void lineInRec(float *inBuf, unsigned frame); + + /* ProcessLineIn + Computes line in peaks, plus handles "hear what you're playin'" thing. */ + + void processLineIn(float *inBuf, unsigned frame); + + /* clearAllBuffers + Cleans up every buffer, both in Mixer and in channels. */ + + void clearAllBuffers(float *outBuf, unsigned bufferSize); + + /* readActions + Reads all recorded actions. */ + + void readActions(unsigned frame); + + /* doQuantize + Computes quantization on 'rewind' button and all channels. */ + + void doQuantize(unsigned frame); + + /* sumChannels + Sums channels, i.e. lets them add sample frames to their virtual channels. + This is required for CHANNEL_SAMPLE only */ + + void sumChannels(unsigned frame); + + /* renderMetronome + Generates metronome when needed and pastes it to the output buffer. */ + + void renderMetronome(float *outBuf, unsigned frame); + + /* renderIO + Final processing stage. Take each channel and process it (i.e. copy its + content to the output buffer). Process plugins too, if any. */ + + void renderIO(float *outBuf, float *inBuf); + + /* limitOutput + Applies a very dumb hard limiter. */ + + void limitOutput(float *outBuf, unsigned frame); + + /* computePeak */ + + void computePeak(float *outBuf, unsigned frame); + + /* finalizeOutput + Last touches after the output has been rendered: apply inToOut if any, apply + output volume. */ + + void finalizeOutput(float *outBuf, unsigned frame); + + /* test* + Checks if the sequencer has reached a specific point (bar, first beat or + last frame). */ + + void testBar(unsigned frame); + void testFirstBeat(unsigned frame); + void testLastBeat(); +}; + +#endif diff --git a/src/core/mixerHandler.cpp b/src/core/mixerHandler.cpp new file mode 100644 index 0000000..5bb2bd4 --- /dev/null +++ b/src/core/mixerHandler.cpp @@ -0,0 +1,292 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * mixerHandler + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License 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 +#include "../utils/fs.h" +#include "../utils/string.h" +#include "../utils/log.h" +#include "../glue/main.h" +#include "../glue/channel.h" +#include "mixerHandler.h" +#include "kernelMidi.h" +#include "mixer.h" +#include "const.h" +#include "init.h" +#include "pluginHost.h" +#include "plugin.h" +#include "waveFx.h" +#include "conf.h" +#include "patch_DEPR_.h" +#include "patch.h" +#include "recorder.h" +#include "channel.h" +#include "sampleChannel.h" +#include "wave.h" + + +extern Mixer G_Mixer; +extern Patch_DEPR_ G_Patch_DEPR_; +extern Patch G_Patch; +extern Conf G_Conf; + +#ifdef WITH_VST +extern PluginHost G_PluginHost; +#endif + + +using std::vector; + + +#ifdef WITH_VST + +static int __mh_readPatchPlugins__(vector *list, int type) +{ + int ret = 1; + for (unsigned i=0; isize(); i++) { + Patch::plugin_t *ppl = &list->at(i); + // TODO use glue_addPlugin() + Plugin *plugin = G_PluginHost.addPlugin(ppl->path.c_str(), type, + &G_Mixer.mutex_plugins, NULL); + if (plugin != NULL) { + plugin->setBypass(ppl->bypass); + for (unsigned j=0; jparams.size(); j++) + plugin->setParameter(j, ppl->params.at(j)); + ret &= 1; + } + else + ret &= 0; + } + return ret; +} + +#endif + + +/* -------------------------------------------------------------------------- */ + + +void mh_stopSequencer() +{ + G_Mixer.running = false; + for (unsigned i=0; istopBySeq(G_Conf.chansStopOnSeqHalt); +} + + +/* -------------------------------------------------------------------------- */ + + +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_DEPR_(bool isProject, const char *projPath) +{ + G_Mixer.init(); + G_Mixer.ready = false; // put it in wait mode + + int numChans = G_Patch_DEPR_.getNumChans(); + for (int i=0; ireadPatch_DEPR_(samplePath.c_str(), i, &G_Patch_DEPR_, G_Conf.samplerate, + G_Conf.rsmpQuality); + } + + G_Mixer.outVol = G_Patch_DEPR_.getOutVol(); + G_Mixer.inVol = G_Patch_DEPR_.getInVol(); + G_Mixer.bpm = G_Patch_DEPR_.getBpm(); + G_Mixer.bars = G_Patch_DEPR_.getBars(); + G_Mixer.beats = G_Patch_DEPR_.getBeats(); + G_Mixer.quantize = G_Patch_DEPR_.getQuantize(); + G_Mixer.metronome = G_Patch_DEPR_.getMetronome(); + G_Patch_DEPR_.lastTakeId = G_Patch_DEPR_.getLastTakeId(); + G_Patch_DEPR_.samplerate = G_Patch_DEPR_.getSamplerate(); + + /* rewind and update frames in Mixer (it's vital) */ + + G_Mixer.rewind(); + G_Mixer.updateFrameBars(); + G_Mixer.ready = true; +} + + +/* -------------------------------------------------------------------------- */ + + +void mh_readPatch() +{ + G_Mixer.ready = false; + + G_Mixer.outVol = G_Patch.masterVolOut; + G_Mixer.inVol = G_Patch.masterVolIn; + G_Mixer.bpm = G_Patch.bpm; + G_Mixer.bars = G_Patch.bars; + G_Mixer.beats = G_Patch.beats; + G_Mixer.quantize = G_Patch.quantize; + G_Mixer.metronome = G_Patch.metronome; + +#ifdef WITH_VST + + __mh_readPatchPlugins__(&G_Patch.masterInPlugins, PluginHost::MASTER_IN); + __mh_readPatchPlugins__(&G_Patch.masterOutPlugins, PluginHost::MASTER_OUT); + +#endif + + /* rewind and update frames in Mixer (it's essential) */ + + 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(); +} + + +/* -------------------------------------------------------------------------- */ + + +bool mh_startInputRec() +{ + int channelsReady = 0; + + for (unsigned i=0; icanInputRec()) + continue; + + SampleChannel *ch = (SampleChannel*) G_Mixer.channels.at(i); + + /* Allocate empty sample for the current channel. */ + + if (!ch->allocEmpty(G_Mixer.totalFrames, G_Conf.samplerate, G_Patch.lastTakeId)) + { + gu_log("[mh_startInputRec] unable to allocate new Wave in chan %d!\n", + ch->index); + continue; + } + + /* Increase lastTakeId until the sample name TAKE-[n] is unique */ + + while (!mh_uniqueSampleName(ch, ch->wave->name)) { + G_Patch_DEPR_.lastTakeId++; + G_Patch.lastTakeId++; + ch->wave->name = "TAKE-" + gu_itoa(G_Patch.lastTakeId); + } + + gu_log("[mh_startInputRec] start input recs using chan %d with size %d " + "frame=%d\n", ch->index, G_Mixer.totalFrames, G_Mixer.inputTracker); + + channelsReady++; + } + + if (channelsReady > 0) { + G_Mixer.recording = true; + /* start to write from the currentFrame, not the beginning */ + /** FIXME: this should be done before wave allocation */ + G_Mixer.inputTracker = G_Mixer.currentFrame; + return true; + } + return false; +} + + +/* -------------------------------------------------------------------------- */ + + +void mh_stopInputRec() +{ + G_Mixer.mergeVirtualInput(); + G_Mixer.recording = false; + G_Mixer.waitRec = 0; // in case delay compensation is in use + gu_log("[mh] stop input recs\n"); +} + + +/* -------------------------------------------------------------------------- */ + + +bool mh_hasArmedSampleChannels() +{ + for (unsigned i=0; itype == CHANNEL_SAMPLE && ch->armed) + return true; + } + return false; +} + + +/* -------------------------------------------------------------------------- */ + + +bool mh_uniqueSampleName(SampleChannel *ch, const string &name) +{ + for (unsigned i=0; itype != CHANNEL_SAMPLE) + continue; + SampleChannel *other = (SampleChannel*) G_Mixer.channels.at(i); + if (other->wave != NULL && name == other->wave->name) + return false; + } + return true; +} diff --git a/src/core/mixerHandler.h b/src/core/mixerHandler.h new file mode 100644 index 0000000..9c33b1c --- /dev/null +++ b/src/core/mixerHandler.h @@ -0,0 +1,78 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * mixerHandler + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License 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 + + +/* stopSequencer + * stop the sequencer, with special case if samplesStopOnSeqHalt is + * true. */ + +void mh_stopSequencer(); + +void mh_rewindSequencer(); + +/* 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_DEPR_(bool isProject, const char *projPath=0); +void mh_readPatch(); + +/* 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. */ + +bool mh_startInputRec(); + +void 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 std::string &s); + +/* hasArmedSampleChannels +Tells whether Mixer has one or more sample channels armed for input +recording. */ + +bool mh_hasArmedSampleChannels(); + +#endif diff --git a/src/core/patch.cpp b/src/core/patch.cpp new file mode 100644 index 0000000..b622537 --- /dev/null +++ b/src/core/patch.cpp @@ -0,0 +1,490 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * patch + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include "../utils/log.h" +#include "../utils/string.h" +#include "const.h" +#include "conf.h" +#include "mixer.h" +#include "patch.h" + + +extern Mixer G_Mixer; +extern Conf G_Conf; + + +void Patch::init() +{ + columns.clear(); + channels.clear(); +#ifdef WITH_VST + masterInPlugins.clear(); + masterOutPlugins.clear(); +#endif + header = "GIADAPTC"; + lastTakeId = 0; + samplerate = DEFAULT_SAMPLERATE; +} + + +/* -------------------------------------------------------------------------- */ + + +int Patch::write(const string &file) +{ + jRoot = json_object(); + + writeCommons(jRoot); + writeColumns(jRoot, &columns); + writeChannels(jRoot, &channels); +#ifdef WITH_VST + writePlugins(jRoot, &masterInPlugins, PATCH_KEY_MASTER_IN_PLUGINS); + writePlugins(jRoot, &masterOutPlugins, PATCH_KEY_MASTER_OUT_PLUGINS); +#endif + + if (json_dump_file(jRoot, file.c_str(), JSON_COMPACT) != 0) { + gu_log("[Patch::write] unable to write patch file!\n"); + return 0; + } + return 1; +} + + +/* -------------------------------------------------------------------------- */ + + +int Patch::read(const string &file) +{ + jRoot = json_load_file(file.c_str(), 0, &jError); + if (!jRoot) { + gu_log("[Patch::read] unable to read patch file! Error on line %d: %s\n", jError.line, jError.text); + return PATCH_UNREADABLE; + } + + if (!checkObject(jRoot, "root element")) + return PATCH_INVALID; + + init(); + + /* TODO json_decref also when PATCH_INVALID */ + + if (!readCommons(jRoot)) return setInvalid(); + if (!readColumns(jRoot)) return setInvalid(); + if (!readChannels(jRoot)) return setInvalid(); +#ifdef WITH_VST + if (!readPlugins(jRoot, &masterInPlugins, PATCH_KEY_MASTER_IN_PLUGINS)) return setInvalid(); + if (!readPlugins(jRoot, &masterOutPlugins, PATCH_KEY_MASTER_OUT_PLUGINS)) return setInvalid(); +#endif + + json_decref(jRoot); + + sanitize(); + + return PATCH_READ_OK; +} + +/* -------------------------------------------------------------------------- */ + +#ifdef WITH_VST + +void Patch::writePlugins(json_t *jContainer, vector *plugins, const char *key) +{ + json_t *jPlugins = json_array(); + for (unsigned j=0; jsize(); j++) { + json_t *jPlugin = json_object(); + plugin_t plugin = plugins->at(j); + json_object_set_new(jPlugin, PATCH_KEY_PLUGIN_PATH, json_string(plugin.path.c_str())); + json_object_set_new(jPlugin, PATCH_KEY_PLUGIN_BYPASS, json_boolean(plugin.bypass)); + json_array_append_new(jPlugins, jPlugin); + + /* plugin params */ + + json_t *jPluginParams = json_array(); + for (unsigned z=0; z *columns) +{ + json_t *jColumns = json_array(); + for (unsigned i=0; isize(); i++) { + json_t *jColumn = json_object(); + column_t column = columns->at(i); + json_object_set_new(jColumn, PATCH_KEY_COLUMN_INDEX, json_integer(column.index)); + json_object_set_new(jColumn, PATCH_KEY_COLUMN_WIDTH, json_integer(column.width)); + json_array_append_new(jColumns, jColumn); + } + json_object_set_new(jContainer, PATCH_KEY_COLUMNS, jColumns); +} + + +/* -------------------------------------------------------------------------- */ + + +void Patch::writeActions(json_t *jContainer, vector *actions) +{ + json_t *jActions = json_array(); + for (unsigned k=0; ksize(); k++) { + json_t *jAction = json_object(); + action_t action = actions->at(k); + json_object_set_new(jAction, PATCH_KEY_ACTION_TYPE, json_integer(action.type)); + json_object_set_new(jAction, PATCH_KEY_ACTION_FRAME, json_integer(action.frame)); + json_object_set_new(jAction, PATCH_KEY_ACTION_F_VALUE, json_real(action.fValue)); + json_object_set_new(jAction, PATCH_KEY_ACTION_I_VALUE, json_integer(action.iValue)); + json_array_append_new(jActions, jAction); + } + json_object_set_new(jContainer, PATCH_KEY_CHANNEL_ACTIONS, jActions); +} + + +/* -------------------------------------------------------------------------- */ + + +void Patch::writeCommons(json_t *jContainer) +{ + json_object_set_new(jContainer, PATCH_KEY_HEADER, json_string(header.c_str())); + json_object_set_new(jContainer, PATCH_KEY_VERSION, json_string(version.c_str())); + json_object_set_new(jContainer, PATCH_KEY_VERSION_MAJOR, json_integer(versionMajor)); + json_object_set_new(jContainer, PATCH_KEY_VERSION_MINOR, json_integer(versionMinor)); + json_object_set_new(jContainer, PATCH_KEY_VERSION_PATCH, json_integer(versionPatch)); + json_object_set_new(jContainer, PATCH_KEY_NAME, json_string(name.c_str())); + json_object_set_new(jContainer, PATCH_KEY_BPM, json_real(bpm)); + json_object_set_new(jContainer, PATCH_KEY_BARS, json_integer(bars)); + json_object_set_new(jContainer, PATCH_KEY_BEATS, json_integer(beats)); + json_object_set_new(jContainer, PATCH_KEY_QUANTIZE, json_integer(quantize)); + json_object_set_new(jContainer, PATCH_KEY_MASTER_VOL_IN, json_real(masterVolIn)); + json_object_set_new(jContainer, PATCH_KEY_MASTER_VOL_OUT, json_real(masterVolOut)); + json_object_set_new(jContainer, PATCH_KEY_METRONOME, json_integer(metronome)); + json_object_set_new(jContainer, PATCH_KEY_LAST_TAKE_ID, json_integer(lastTakeId)); + json_object_set_new(jContainer, PATCH_KEY_SAMPLERATE, json_integer(samplerate)); +} + + +/* -------------------------------------------------------------------------- */ + + +void Patch::writeChannels(json_t *jContainer, vector *channels) +{ + json_t *jChannels = json_array(); + for (unsigned i=0; isize(); i++) { + json_t *jChannel = json_object(); + channel_t channel = channels->at(i); + json_object_set_new(jChannel, PATCH_KEY_CHANNEL_TYPE, json_integer(channel.type)); + json_object_set_new(jChannel, PATCH_KEY_CHANNEL_INDEX, json_integer(channel.index)); + json_object_set_new(jChannel, PATCH_KEY_CHANNEL_COLUMN, json_integer(channel.column)); + json_object_set_new(jChannel, PATCH_KEY_CHANNEL_MUTE, json_integer(channel.mute)); + json_object_set_new(jChannel, PATCH_KEY_CHANNEL_MUTE_S, json_integer(channel.mute_s)); + json_object_set_new(jChannel, PATCH_KEY_CHANNEL_SOLO, json_integer(channel.solo)); + json_object_set_new(jChannel, PATCH_KEY_CHANNEL_VOLUME, json_real(channel.volume)); + json_object_set_new(jChannel, PATCH_KEY_CHANNEL_PAN_LEFT, json_real(channel.panLeft)); + json_object_set_new(jChannel, PATCH_KEY_CHANNEL_PAN_RIGHT, json_real(channel.panRight)); + json_object_set_new(jChannel, PATCH_KEY_CHANNEL_MIDI_IN, json_boolean(channel.midiIn)); + json_object_set_new(jChannel, PATCH_KEY_CHANNEL_MIDI_IN_KEYPRESS, json_integer(channel.midiInKeyPress)); + json_object_set_new(jChannel, PATCH_KEY_CHANNEL_MIDI_IN_KEYREL, json_integer(channel.midiInKeyRel)); + json_object_set_new(jChannel, PATCH_KEY_CHANNEL_MIDI_IN_KILL, json_integer(channel.midiInKill)); + json_object_set_new(jChannel, PATCH_KEY_CHANNEL_MIDI_IN_ARM, json_integer(channel.midiInArm)); + json_object_set_new(jChannel, PATCH_KEY_CHANNEL_MIDI_IN_VOLUME, json_integer(channel.midiInVolume)); + json_object_set_new(jChannel, PATCH_KEY_CHANNEL_MIDI_IN_MUTE, json_integer(channel.midiInMute)); + json_object_set_new(jChannel, PATCH_KEY_CHANNEL_MIDI_IN_SOLO, json_integer(channel.midiInSolo)); + json_object_set_new(jChannel, PATCH_KEY_CHANNEL_MIDI_OUT_L, json_boolean(channel.midiOutL)); + json_object_set_new(jChannel, PATCH_KEY_CHANNEL_MIDI_OUT_L_PLAYING, json_integer(channel.midiOutLplaying)); + json_object_set_new(jChannel, PATCH_KEY_CHANNEL_MIDI_OUT_L_MUTE, json_integer(channel.midiOutLmute)); + json_object_set_new(jChannel, PATCH_KEY_CHANNEL_MIDI_OUT_L_SOLO, json_integer(channel.midiOutLsolo)); + json_object_set_new(jChannel, PATCH_KEY_CHANNEL_SAMPLE_PATH, json_string(channel.samplePath.c_str())); + json_object_set_new(jChannel, PATCH_KEY_CHANNEL_KEY, json_integer(channel.key)); + json_object_set_new(jChannel, PATCH_KEY_CHANNEL_MODE, json_integer(channel.mode)); + json_object_set_new(jChannel, PATCH_KEY_CHANNEL_BEGIN, json_integer(channel.begin)); + json_object_set_new(jChannel, PATCH_KEY_CHANNEL_END, json_integer(channel.end)); + json_object_set_new(jChannel, PATCH_KEY_CHANNEL_BOOST, json_real(channel.boost)); + json_object_set_new(jChannel, PATCH_KEY_CHANNEL_REC_ACTIVE, json_integer(channel.recActive)); + json_object_set_new(jChannel, PATCH_KEY_CHANNEL_PITCH, json_real(channel.pitch)); + json_object_set_new(jChannel, PATCH_KEY_CHANNEL_MIDI_IN_READ_ACTIONS, json_integer(channel.midiInReadActions)); + json_object_set_new(jChannel, PATCH_KEY_CHANNEL_MIDI_IN_PITCH, json_integer(channel.midiInPitch)); + json_object_set_new(jChannel, PATCH_KEY_CHANNEL_MIDI_OUT, json_integer(channel.midiOut)); + json_object_set_new(jChannel, PATCH_KEY_CHANNEL_MIDI_OUT_CHAN, json_integer(channel.midiOutChan)); + json_array_append_new(jChannels, jChannel); + + writeActions(jChannel, &channel.actions); + +#ifdef WITH_VST + + writePlugins(jChannel, &channel.plugins, PATCH_KEY_CHANNEL_PLUGINS); + +#endif + } + json_object_set_new(jContainer, PATCH_KEY_CHANNELS, jChannels); +} + + +/* -------------------------------------------------------------------------- */ + + +bool Patch::readCommons(json_t *jContainer) +{ + if (!setString(jContainer, PATCH_KEY_HEADER, header)) return 0; + if (!setString(jContainer, PATCH_KEY_VERSION, version)) return 0; + if (!setInt (jContainer, PATCH_KEY_VERSION_MAJOR, versionMajor)) return 0; + if (!setInt (jContainer, PATCH_KEY_VERSION_MINOR, versionMinor)) return 0; + if (!setInt (jContainer, PATCH_KEY_VERSION_PATCH, versionPatch)) return 0; + if (!setString(jContainer, PATCH_KEY_NAME, name)) return 0; + if (!setFloat (jContainer, PATCH_KEY_BPM, bpm)) return 0; + if (!setInt (jContainer, PATCH_KEY_BARS, bars)) return 0; + if (!setInt (jContainer, PATCH_KEY_BEATS, beats)) return 0; + if (!setInt (jContainer, PATCH_KEY_QUANTIZE, quantize)) return 0; + if (!setFloat (jContainer, PATCH_KEY_MASTER_VOL_IN, masterVolIn)) return 0; + if (!setFloat (jContainer, PATCH_KEY_MASTER_VOL_OUT, masterVolOut)) return 0; + if (!setInt (jContainer, PATCH_KEY_METRONOME, metronome)) return 0; + if (!setInt (jContainer, PATCH_KEY_LAST_TAKE_ID, lastTakeId)) return 0; + if (!setInt (jContainer, PATCH_KEY_SAMPLERATE, samplerate)) return 0; + return 1; +} + + +/* -------------------------------------------------------------------------- */ + + +bool Patch::readColumns(json_t *jContainer) +{ + json_t *jColumns = json_object_get(jContainer, PATCH_KEY_COLUMNS); + if (!checkArray(jColumns, PATCH_KEY_COLUMNS)) + return 0; + + size_t columnIndex; + json_t *jColumn; + json_array_foreach(jColumns, columnIndex, jColumn) { + + string columnIndexStr = "column " + gu_itoa(columnIndex); + if (!checkObject(jColumn, columnIndexStr.c_str())) + return 0; + + column_t column; + if (!setInt(jColumn, PATCH_KEY_COLUMN_INDEX, column.index)) return 0; + if (!setInt(jColumn, PATCH_KEY_COLUMN_WIDTH, column.width)) return 0; + + columns.push_back(column); + } + return 1; +} + + +/* -------------------------------------------------------------------------- */ + + +bool Patch::readChannels(json_t *jContainer) +{ + json_t *jChannels = json_object_get(jContainer, PATCH_KEY_CHANNELS); + if (!checkArray(jChannels, PATCH_KEY_CHANNELS)) + return 0; + + size_t channelIndex; + json_t *jChannel; + json_array_foreach(jChannels, channelIndex, jChannel) { + + string channelIndexStr = "channel " + gu_itoa(channelIndex); + if (!checkObject(jChannel, channelIndexStr.c_str())) + return 0; + + channel_t channel; + + if (!setInt (jChannel, PATCH_KEY_CHANNEL_TYPE, channel.type)) return 0; + if (!setInt (jChannel, PATCH_KEY_CHANNEL_INDEX, channel.index)) return 0; + if (!setInt (jChannel, PATCH_KEY_CHANNEL_COLUMN, channel.column)) return 0; + if (!setInt (jChannel, PATCH_KEY_CHANNEL_MUTE, channel.mute)) return 0; + if (!setInt (jChannel, PATCH_KEY_CHANNEL_MUTE_S, channel.mute_s)) return 0; + if (!setInt (jChannel, PATCH_KEY_CHANNEL_SOLO, channel.solo)) return 0; + if (!setFloat (jChannel, PATCH_KEY_CHANNEL_VOLUME, channel.volume)) return 0; + if (!setFloat (jChannel, PATCH_KEY_CHANNEL_PAN_LEFT, channel.panRight)) return 0; + if (!setFloat (jChannel, PATCH_KEY_CHANNEL_PAN_RIGHT, channel.panLeft)) return 0; + if (!setBool (jChannel, PATCH_KEY_CHANNEL_MIDI_IN, channel.midiIn)) return 0; + if (!setUint32(jChannel, PATCH_KEY_CHANNEL_MIDI_IN_KEYPRESS, channel.midiInKeyPress)) return 0; + if (!setUint32(jChannel, PATCH_KEY_CHANNEL_MIDI_IN_KEYREL, channel.midiInKeyRel)) return 0; + if (!setUint32(jChannel, PATCH_KEY_CHANNEL_MIDI_IN_KILL, channel.midiInKill)) return 0; + if (!setUint32(jChannel, PATCH_KEY_CHANNEL_MIDI_IN_ARM, channel.midiInArm)) return 0; + if (!setUint32(jChannel, PATCH_KEY_CHANNEL_MIDI_IN_VOLUME, channel.midiInVolume)) return 0; + if (!setUint32(jChannel, PATCH_KEY_CHANNEL_MIDI_IN_MUTE, channel.midiInMute)) return 0; + if (!setUint32(jChannel, PATCH_KEY_CHANNEL_MIDI_IN_SOLO, channel.midiInSolo)) return 0; + if (!setBool (jChannel, PATCH_KEY_CHANNEL_MIDI_OUT_L, channel.midiOutL)) return 0; + if (!setUint32(jChannel, PATCH_KEY_CHANNEL_MIDI_OUT_L_PLAYING, channel.midiOutLplaying)) return 0; + if (!setUint32(jChannel, PATCH_KEY_CHANNEL_MIDI_OUT_L_MUTE, channel.midiOutLmute)) return 0; + if (!setUint32(jChannel, PATCH_KEY_CHANNEL_MIDI_OUT_L_SOLO, channel.midiOutLsolo)) return 0; + if (!setString(jChannel, PATCH_KEY_CHANNEL_SAMPLE_PATH, channel.samplePath)) return 0; + if (!setInt (jChannel, PATCH_KEY_CHANNEL_KEY, channel.key)) return 0; + if (!setInt (jChannel, PATCH_KEY_CHANNEL_MODE, channel.mode)) return 0; + if (!setInt (jChannel, PATCH_KEY_CHANNEL_BEGIN, channel.begin)) return 0; + if (!setInt (jChannel, PATCH_KEY_CHANNEL_END, channel.end)) return 0; + if (!setFloat (jChannel, PATCH_KEY_CHANNEL_BOOST, channel.boost)) return 0; + if (!setInt (jChannel, PATCH_KEY_CHANNEL_REC_ACTIVE, channel.recActive)) return 0; + if (!setFloat (jChannel, PATCH_KEY_CHANNEL_PITCH, channel.pitch)) return 0; + if (!setUint32(jChannel, PATCH_KEY_CHANNEL_MIDI_IN_READ_ACTIONS, channel.midiInReadActions)) return 0; + if (!setUint32(jChannel, PATCH_KEY_CHANNEL_MIDI_IN_PITCH, channel.midiInPitch)) return 0; + if (!setUint32(jChannel, PATCH_KEY_CHANNEL_MIDI_OUT, channel.midiOut)) return 0; + if (!setUint32(jChannel, PATCH_KEY_CHANNEL_MIDI_OUT_CHAN, channel.midiOutChan)) return 0; + + readActions(jChannel, &channel); + +#ifdef WITH_VST + readPlugins(jChannel, &channel.plugins, PATCH_KEY_CHANNEL_PLUGINS); +#endif + channels.push_back(channel); + } + return 1; +} + + +/* -------------------------------------------------------------------------- */ + + +bool Patch::readActions(json_t *jContainer, channel_t *channel) +{ + json_t *jActions = json_object_get(jContainer, PATCH_KEY_CHANNEL_ACTIONS); + if (!checkArray(jActions, PATCH_KEY_CHANNEL_ACTIONS)) + return 0; + + size_t actionIndex; + json_t *jAction; + json_array_foreach(jActions, actionIndex, jAction) { + + if (!checkObject(jAction, "")) // TODO pass actionIndex as string + return 0; + + action_t action; + if (!setInt (jAction, PATCH_KEY_ACTION_TYPE, action.type)) return 0; + if (!setInt (jAction, PATCH_KEY_ACTION_FRAME, action.frame)) return 0; + if (!setFloat (jAction, PATCH_KEY_ACTION_F_VALUE, action.fValue)) return 0; + if (!setUint32(jAction, PATCH_KEY_ACTION_I_VALUE, action.iValue)) return 0; + channel->actions.push_back(action); + } + return 1; +} + + +/* -------------------------------------------------------------------------- */ + + +#ifdef WITH_VST + +bool Patch::readPlugins(json_t *jContainer, vector *container, const char *key) +{ + json_t *jPlugins = json_object_get(jContainer, key); + if (!checkArray(jPlugins, key)) + return 0; + + size_t pluginIndex; + json_t *jPlugin; + json_array_foreach(jPlugins, pluginIndex, jPlugin) { + + if (!checkObject(jPlugin, "")) // TODO pass pluginIndex as string + return 0; + + plugin_t plugin; + if (!setString(jPlugin, PATCH_KEY_PLUGIN_PATH, plugin.path)) return 0; + if (!setBool (jPlugin, PATCH_KEY_PLUGIN_BYPASS, plugin.bypass)) return 0; + + /* read plugin params */ + + json_t *jParams = json_object_get(jPlugin, PATCH_KEY_PLUGIN_PARAMS); + if (!checkArray(jParams, PATCH_KEY_PLUGIN_PARAMS)) return 0; + + size_t paramIndex; + json_t *jParam; + json_array_foreach(jParams, paramIndex, jParam) + plugin.params.push_back(json_real_value(jParam)); + + /* read midiIn params (midi learning on plugins' parameters) */ + + json_t *jMidiInParams = json_object_get(jPlugin, PATCH_KEY_PLUGIN_MIDI_IN_PARAMS); + if (!checkArray(jMidiInParams, PATCH_KEY_PLUGIN_MIDI_IN_PARAMS)) return 0; + + size_t midiInParamIndex; + json_t *jMidiInParam; + json_array_foreach(jMidiInParams, midiInParamIndex, jMidiInParam) + plugin.midiInParams.push_back(json_integer_value(jMidiInParam)); + + container->push_back(plugin); + } + return 1; +} + +#endif + + +/* -------------------------------------------------------------------------- */ + + +void Patch::sanitize() +{ + bpm = bpm < 20.0f || bpm > 999.0f ? DEFAULT_BPM : bpm; + bars = bars <= 0 || bars > 32 ? DEFAULT_BARS : bars; + beats = beats <= 0 || beats > 32 ? DEFAULT_BEATS : beats; + quantize = quantize < 0 || quantize > 8 ? DEFAULT_QUANTIZE : quantize; + masterVolIn = masterVolIn < 0.0f || masterVolIn > 1.0f ? DEFAULT_VOL : masterVolIn; + masterVolOut = masterVolOut < 0.0f || masterVolOut > 1.0f ? DEFAULT_VOL : masterVolOut; + samplerate = samplerate <= 0 ? DEFAULT_SAMPLERATE : samplerate; + + for (unsigned i=0; iindex = col->index < 0 ? 0 : col->index; + col->width = col->width < MIN_COLUMN_WIDTH ? MIN_COLUMN_WIDTH : col->width; + } + + for (unsigned i=0; ivolume = ch->volume < 0.0f || ch->volume > 1.0f ? DEFAULT_VOL : ch->volume; + ch->panLeft = ch->panLeft < 0.0f || ch->panLeft > 1.0f ? 1.0f : ch->panLeft; + ch->panRight = ch->panRight < 0.0f || ch->panRight > 1.0f ? 1.0f : ch->panRight; + ch->boost = ch->boost < 1.0f ? DEFAULT_BOOST : ch->boost; + ch->pitch = ch->pitch < 0.1f || ch->pitch > 4.0f ? G_DEFAULT_PITCH : ch->pitch; + } +} + + +/* -------------------------------------------------------------------------- */ + + +int Patch::setInvalid() +{ + json_decref(jRoot); + return PATCH_INVALID; +} diff --git a/src/core/patch.h b/src/core/patch.h new file mode 100644 index 0000000..ba17077 --- /dev/null +++ b/src/core/patch.h @@ -0,0 +1,186 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * patch + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License 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 "dataStorageJson.h" + + +using std::string; +using std::vector; + + +class Patch : public DataStorageJson +{ +public: + + struct action_t + { + int type; + int frame; + float fValue; + uint32_t iValue; + }; + +#ifdef WITH_VST + struct plugin_t + { + string path; + bool bypass; + vector params; + vector midiInParams; + }; +#endif + + struct channel_t + { + int type; + int index; + int column; + int mute; + int mute_s; + int solo; + float volume; + float panLeft; + float panRight; + bool midiIn; + uint32_t midiInKeyPress; + uint32_t midiInKeyRel; + uint32_t midiInKill; + uint32_t midiInArm; + uint32_t midiInVolume; + uint32_t midiInMute; + uint32_t midiInSolo; + bool midiOutL; + uint32_t midiOutLplaying; + uint32_t midiOutLmute; + uint32_t midiOutLsolo; + // sample channel + string samplePath; + int key; + int mode; + int begin; + int end; + float boost; + int recActive; + float pitch; + uint32_t midiInReadActions; + uint32_t midiInPitch; + // midi channel + uint32_t midiOut; + uint32_t midiOutChan; + + vector actions; + +#ifdef WITH_VST + vector plugins; +#endif + }; + + struct column_t + { + int index; + int width; + vector channels; + }; + + string header; + string version; + int versionMajor; + int versionMinor; + int versionPatch; + string name; + float bpm; + int bars; + int beats; + int quantize; + float masterVolIn; + float masterVolOut; + int metronome; + int lastTakeId; + int samplerate; // original samplerate when the patch was saved + + vector columns; + vector channels; + +#ifdef WITH_VST + vector masterInPlugins; + vector masterOutPlugins; +#endif + + /* init + * Init Patch with default values. */ + + void init(); + + /* read/write + * Read/write patch to/from file. */ + + int write(const string &file); + int read (const string &file); + +private: + + /* sanitize + * Internal sanity check. */ + + void sanitize(); + + /* setInvalid + * Helper function used to return invalid status while reading. */ + + int setInvalid(); + + /* readers */ + + bool readCommons (json_t *jContainer); + bool readChannels(json_t *jContainer); +#ifdef WITH_VST + bool readPlugins (json_t *jContainer, vector *container, const char* key); +#endif + bool readActions (json_t *jContainer, channel_t *channel); + bool readColumns (json_t *jContainer); + + /* writers */ + + void writeCommons (json_t *jContainer); + void writeChannels(json_t *jContainer, vector *channels); +#ifdef WITH_VST + void writePlugins (json_t *jContainer, vector *plugins, const char* key); +#endif + void writeActions (json_t *jContainer, vector *actions); + void writeColumns (json_t *jContainer, vector *columns); +}; + +#endif diff --git a/src/core/patch_DEPR_.cpp b/src/core/patch_DEPR_.cpp new file mode 100644 index 0000000..5ded87d --- /dev/null +++ b/src/core/patch_DEPR_.cpp @@ -0,0 +1,619 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * patch + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include +#include "../utils/log.h" +#include "../utils/fs.h" +#include "../gui/dialogs/gd_mainWindow.h" +#include "../gui/elems/mainWindow/keyboard/keyboard.h" +#include "patch_DEPR_.h" +#include "init.h" +#include "recorder.h" +#include "conf.h" +#include "pluginHost.h" +#include "plugin.h" +#include "wave.h" +#include "mixer.h" +#include "channel.h" + + +extern Mixer G_Mixer; +extern Conf G_Conf; +extern Recorder G_Recorder; +#ifdef WITH_VST +extern PluginHost G_PluginHost; +#endif +extern gdMainWindow *mainWin; + + +int Patch_DEPR_::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()); + gu_log("[patch_DEPR_] open patch version %f\n", version); + + return PATCH_READ_OK; +} + + +/* -------------------------------------------------------------------------- */ + + +void Patch_DEPR_::setDefault() +{ + name[0] = '\0'; + lastTakeId = 0; + samplerate = DEFAULT_SAMPLERATE; +} + + +/* -------------------------------------------------------------------------- */ + + +int Patch_DEPR_::close() +{ + return fclose(fp); +} + + +/* -------------------------------------------------------------------------- */ + + +void Patch_DEPR_::getName() +{ + std::string out = getValue("patchname"); + strncpy(name, out.c_str(), MAX_PATCHNAME_LEN); +} + + +/* -------------------------------------------------------------------------- */ + + +std::string Patch_DEPR_::getSamplePath(int c) +{ + char tmp[16]; + sprintf(tmp, "samplepath%d", c); + return getValue(tmp); +} + + +/* -------------------------------------------------------------------------- */ + + +float Patch_DEPR_::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_DEPR_::getNumChans() +{ + if (version == 0.0) // backward compatibility with version < 0.6.1 + return 32; + return atoi(getValue("channels").c_str()); +} + + +/* -------------------------------------------------------------------------- */ + + +int Patch_DEPR_::getNumColumns() +{ + return atoi(getValue("columns").c_str()); +} + + +/* -------------------------------------------------------------------------- */ + + +int Patch_DEPR_::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_DEPR_::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_DEPR_::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_DEPR_::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_DEPR_::getMute(int c) +{ + char tmp[16]; + sprintf(tmp, "chanMute%d", c); + return atoi(getValue(tmp).c_str()); +} + + +/* -------------------------------------------------------------------------- */ + + +int Patch_DEPR_::getMute_s(int c) +{ + char tmp[16]; + sprintf(tmp, "chanMute_s%d", c); + return atoi(getValue(tmp).c_str()); +} + + +/* -------------------------------------------------------------------------- */ + + +int Patch_DEPR_::getSolo(int c) +{ + char tmp[16]; + sprintf(tmp, "chanSolo%d", c); + return atoi(getValue(tmp).c_str()); +} + + +/* -------------------------------------------------------------------------- */ + + +int Patch_DEPR_::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_DEPR_::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_DEPR_::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_DEPR_::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_DEPR_::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_DEPR_::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_DEPR_::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_DEPR_::getRecActive(int c) +{ + char tmp[16]; + sprintf(tmp, "chanRecActive%d", c); + return atoi(getValue(tmp).c_str()); +} + + +/* -------------------------------------------------------------------------- */ + + +float Patch_DEPR_::getOutVol() +{ + return atof(getValue("outVol").c_str()); +} + + +/* -------------------------------------------------------------------------- */ + + +float Patch_DEPR_::getInVol() +{ + return atof(getValue("inVol").c_str()); +} + + +/* -------------------------------------------------------------------------- */ + + +float Patch_DEPR_::getBpm() +{ + float out = atof(getValue("bpm").c_str()); + if (out < 20.0f || out > 999.0f) + return DEFAULT_BPM; + return out; +} + + +/* -------------------------------------------------------------------------- */ + + +int Patch_DEPR_::getBars() +{ + int out = atoi(getValue("bars").c_str()); + if (out <= 0 || out > 32) + return DEFAULT_BARS; + return out; +} + + +/* -------------------------------------------------------------------------- */ + + +int Patch_DEPR_::getBeats() +{ + int out = atoi(getValue("beats").c_str()); + if (out <= 0 || out > 32) + return DEFAULT_BEATS; + return out; +} + + +/* -------------------------------------------------------------------------- */ + + +int Patch_DEPR_::getQuantize() +{ + int out = atoi(getValue("quantize").c_str()); + if (out < 0 || out > 8) + return DEFAULT_QUANTIZE; + return out; +} + + +/* -------------------------------------------------------------------------- */ + + +bool Patch_DEPR_::getMetronome() +{ + return atoi(getValue("metronome").c_str()); +} + + +/* -------------------------------------------------------------------------- */ + + +int Patch_DEPR_::getLastTakeId() +{ + return atoi(getValue("lastTakeId").c_str()); +} + + +/* -------------------------------------------------------------------------- */ + + +int Patch_DEPR_::getSamplerate() +{ + int out = atoi(getValue("samplerate").c_str()); + if (out <= 0) + return DEFAULT_SAMPLERATE; + return out; +} + + +/* -------------------------------------------------------------------------- */ + + +uint32_t Patch_DEPR_::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_DEPR_::readRecs() +{ + gu_log("[patch_DEPR_] 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) + G_Recorder.rec(ch->index, type, frame, iValue_fix, fValue); + else + G_Recorder.rec(ch->index, type, frame, iValue, fValue); + ch->hasActions = true; + } + } + } + return 1; +} + + +/* -------------------------------------------------------------------------- */ + + +#ifdef WITH_VST +int Patch_DEPR_::readPlugins() +{ + gu_log("[patch_DEPR_] 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); + Plugin *plugin = G_PluginHost.addPlugin(getValue(tmp).c_str(), PluginHost::CHANNEL, &G_Mixer.mutex_plugins, ch); + if (plugin != NULL) { + 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); + if (pPlugin) { + pPlugin->setBypass(atoi(getValue(tmp).c_str())); + for (int k=0; kindex, j, k); + float pval = atof(getValue(tmp).c_str()); + pPlugin->setParameter(k, pval); + } + } + globalOut &= 1; + } + else + globalOut &= 0; + } + } + return globalOut; +} +#endif + + +/* -------------------------------------------------------------------------- */ + +#ifdef WITH_VST + +int Patch_DEPR_::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; isetBypass(atoi(getValue(tmp).c_str())); + sprintf(tmp, "master%c_p%dnumParams", chr, i); + int nparam = atoi(getValue(tmp).c_str()); + for (int j=0; jsetParameter(j, pval); + } + res &= 1; + } + else + res &= 0; + } + + return res; +} + +#endif diff --git a/src/core/patch_DEPR_.h b/src/core/patch_DEPR_.h new file mode 100644 index 0000000..0f01167 --- /dev/null +++ b/src/core/patch_DEPR_.h @@ -0,0 +1,94 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * patch + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License 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_DEPR_H__ +#define __PATCH_DEPR_H__ + +#include +#include +#include +#include "dataStorageIni.h" +#include "const.h" + + +class Patch_DEPR_ : public DataStorageIni +{ +private: + + int readMasterPlugins(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 readRecs(); +#ifdef WITH_VST + int readPlugins(); +#endif +}; + +#endif diff --git a/src/core/plugin.cpp b/src/core/plugin.cpp new file mode 100644 index 0000000..1ef79b2 --- /dev/null +++ b/src/core/plugin.cpp @@ -0,0 +1,263 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#ifdef WITH_VST + + +#include "../utils/log.h" +#include "plugin.h" + + +using std::string; + + +int Plugin::idGenerator = 1; + + +/* -------------------------------------------------------------------------- */ + + +Plugin::Plugin(juce::AudioPluginInstance *plugin, double samplerate, + int buffersize) + : ui (nullptr), + plugin(plugin), + id (idGenerator++), + bypass(false) +{ + /* Fill midiInParams. All values are empty (0x0): they will be filled during + midi learning process. */ + + for (int i=0; igetNumParameters(); i++) + midiInParams.push_back(0x0); + + /* Try to enable editor (i.e. plugin's UI) */ + + if (plugin->getActiveEditor() != NULL) { + gu_log("[Plugin] plugin has an already active editor!\n"); + return; + } + ui = plugin->createEditorIfNeeded(); + if (ui == NULL) { + gu_log("[Plugin] unable to create editor, the plugin might be GUI-less!\n"); + return; + } + + plugin->prepareToPlay(samplerate, buffersize); + + gu_log("[Plugin] editor initialized and ready\n"); +} + + +/* -------------------------------------------------------------------------- */ + + +Plugin::~Plugin() +{ + plugin->suspendProcessing(true); + plugin->releaseResources(); +} + + +/* -------------------------------------------------------------------------- */ + + +void Plugin::showEditor(void *parent) +{ + if (ui == NULL) { + gu_log("[Plugin::showEditor] can't show editor!\n"); + return; + } + ui->setOpaque(true); + ui->addToDesktop(0, parent); +} + + +/* -------------------------------------------------------------------------- */ + + +bool Plugin::isEditorOpen() +{ + return ui->isVisible() && ui->isOnDesktop(); +} + + +/* -------------------------------------------------------------------------- */ + + +string Plugin::getUniqueId() +{ + return plugin->getPluginDescription().fileOrIdentifier.toStdString(); +} + + +/* -------------------------------------------------------------------------- */ + + +int Plugin::getNumParameters() +{ + return plugin->getNumParameters(); +} + + +/* -------------------------------------------------------------------------- */ + + +float Plugin::getParameter(int paramIndex) +{ + return plugin->getParameter(paramIndex); +} + + +/* -------------------------------------------------------------------------- */ + + +void Plugin::setParameter(int paramIndex, float value) +{ + return plugin->setParameter(paramIndex, value); +} + + +/* -------------------------------------------------------------------------- */ + + +void Plugin::prepareToPlay(double samplerate, int buffersize) +{ + plugin->prepareToPlay(samplerate, buffersize); +} + + +/* -------------------------------------------------------------------------- */ + + +string Plugin::getName() +{ + return plugin->getName().toStdString(); +} + + +/* -------------------------------------------------------------------------- */ + + +bool Plugin::isSuspended() +{ + return plugin->isSuspended(); +} + + +/* -------------------------------------------------------------------------- */ + + +void Plugin::process(juce::AudioBuffer &b, juce::MidiBuffer &m) +{ + plugin->processBlock(b, m); +} + + +/* -------------------------------------------------------------------------- */ + + +int Plugin::getNumPrograms() +{ + return plugin->getNumPrograms(); +} + + +/* -------------------------------------------------------------------------- */ + + +int Plugin::getCurrentProgram() +{ + return plugin->getCurrentProgram(); +} + + +/* -------------------------------------------------------------------------- */ + + +void Plugin::setCurrentProgram(int index) +{ + plugin->setCurrentProgram(index); +} + + +/* -------------------------------------------------------------------------- */ + + +bool Plugin::hasEditor() +{ + return plugin->hasEditor(); +} + + +/* -------------------------------------------------------------------------- */ + + +string Plugin::getProgramName(int index) +{ + return plugin->getProgramName(index).toStdString(); +} + + +/* -------------------------------------------------------------------------- */ + + +string Plugin::getParameterName(int index) +{ + return plugin->getParameterName(index).toStdString(); +} + + +/* -------------------------------------------------------------------------- */ + + +string Plugin::getParameterText(int index) +{ + return plugin->getParameterText(index).toStdString(); +} + + +/* -------------------------------------------------------------------------- */ + + +string Plugin::getParameterLabel(int index) +{ + return plugin->getParameterLabel(index).toStdString(); +} + + +/* -------------------------------------------------------------------------- */ + + +void Plugin::closeEditor() +{ + if (ui == NULL) + return; + if (ui->isOnDesktop()) + ui->removeFromDesktop(); +} + +#endif diff --git a/src/core/plugin.h b/src/core/plugin.h new file mode 100644 index 0000000..d295e50 --- /dev/null +++ b/src/core/plugin.h @@ -0,0 +1,104 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * plugin + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License 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 "../deps/juce-config.h" + + +class Plugin +{ +private: + + static int idGenerator; + + juce::AudioProcessorEditor *ui; // gui + juce::AudioPluginInstance *plugin; // core + + int id; + bool bypass; + +public: + + Plugin(juce::AudioPluginInstance *p, double samplerate, int buffersize); + ~Plugin(); + + void showEditor(void *parent); + + /* closeEditor + * Shut down plugin GUI. */ + + void closeEditor(); + + /* isEditorOpen */ + + bool isEditorOpen(); + + /* getUniqueId + * Return a string-based UID. */ + + std::string getUniqueId(); + + std::string getName(); + + bool hasEditor(); + int getNumParameters(); + float getParameter(int index); + void setParameter(int index, float value); + std::string getParameterName(int index); + std::string getParameterText(int index); + std::string getParameterLabel(int index); + void prepareToPlay(double samplerate, int buffersize); + bool isSuspended(); + void process(juce::AudioBuffer &b, juce::MidiBuffer &m); + int getNumPrograms(); + int getCurrentProgram(); + void setCurrentProgram(int index); + std::string getProgramName(int index); + + int getId() { return id; } + bool isBypassed() { return bypass; } + int getEditorW() { return ui->getWidth(); } + int getEditorH() { return ui->getHeight(); } + void toggleBypass() { bypass = !bypass; } + void setBypass(bool b) { bypass = b; } + + /* midiInParams + A list of midiIn hex values for parameter automation. */ + + std::vector midiInParams; +}; + +#endif + +#endif // #ifdef WITH_VST diff --git a/src/core/pluginHost.cpp b/src/core/pluginHost.cpp new file mode 100644 index 0000000..a23f266 --- /dev/null +++ b/src/core/pluginHost.cpp @@ -0,0 +1,494 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * pluginHost + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#ifdef WITH_VST + + +#include "../utils/log.h" +#include "const.h" +#include "channel.h" +#include "plugin.h" +#include "pluginHost.h" + + +using std::string; + + +PluginHost::~PluginHost() +{ + messageManager->deleteInstance(); +} + + +/* -------------------------------------------------------------------------- */ + + +void PluginHost::init(int _buffersize, int _samplerate) +{ + gu_log("[PluginHost::init] initialize with buffersize=%d, samplerate=%d\n", + _buffersize, _samplerate); + + messageManager = juce::MessageManager::getInstance(); + audioBuffer.setSize(2, _buffersize); + samplerate = _samplerate; + buffersize = _buffersize; + missingPlugins = false; + //unknownPluginList.empty(); + loadList(gu_getHomePath() + G_SLASH + "plugins.xml"); + + pthread_mutex_init(&mutex_midi, NULL); +} + + +/* -------------------------------------------------------------------------- */ + + +int PluginHost::scanDir(const string &dirpath, void (*callback)(float progress, void *p), + void *p) +{ + gu_log("[PluginHost::scanDir] requested directory: '%s'\n", dirpath.c_str()); + gu_log("[PluginHost::scanDir] current plugins: %d\n", knownPluginList.getNumTypes()); + + knownPluginList.clear(); // clear up previous plugins + + juce::VSTPluginFormat format; + juce::FileSearchPath path(dirpath); + juce::PluginDirectoryScanner scanner(knownPluginList, format, path, + true, juce::File::nonexistent); // true: recursive + + bool cont = true; + juce::String name; + while (cont) { + gu_log("[PluginHost::scanDir] scanning '%s'\n", name.toRawUTF8()); + cont = scanner.scanNextFile(false, name); + if (callback) + callback(scanner.getProgress(), p); + } + + gu_log("[PluginHost::scanDir] %d plugin(s) found\n", knownPluginList.getNumTypes()); + return knownPluginList.getNumTypes(); +} + + +/* -------------------------------------------------------------------------- */ + + +int PluginHost::saveList(const string &filepath) +{ + int out = knownPluginList.createXml()->writeToFile(juce::File(filepath), ""); + if (!out) + gu_log("[PluginHost::saveList] unable to save plugin list to %s\n", filepath.c_str()); + return out; +} + + +/* -------------------------------------------------------------------------- */ + + +int PluginHost::loadList(const string &filepath) +{ + juce::XmlElement *elem = juce::XmlDocument::parse(juce::File(filepath)); + if (elem) { + knownPluginList.recreateFromXml(*elem); + delete elem; + return 1; + } + return 0; +} + + +/* -------------------------------------------------------------------------- */ + + +Plugin *PluginHost::addPlugin(const string &fid, int stackType, + pthread_mutex_t *mutex, class Channel *ch) +{ + /* Get the proper stack to add the plugin to */ + + vector *pStack; + pStack = getStack(stackType, ch); + + /* Initialize plugin */ + + juce::PluginDescription *pd = knownPluginList.getTypeForFile(fid); + if (!pd) { + gu_log("[PluginHost::addPlugin] no plugin found with fid=%s!\n", fid.c_str()); + missingPlugins = true; + unknownPluginList.push_back(fid); + return NULL; + } + + juce::AudioPluginInstance *pi = pluginFormat.createInstanceFromDescription(*pd, samplerate, buffersize); + if (!pi) { + gu_log("[PluginHost::addPlugin] unable to create instance with fid=%s!\n", fid.c_str()); + missingPlugins = true; + return NULL; + } + gu_log("[PluginHost::addPlugin] plugin instance with fid=%s created\n", fid.c_str()); + + Plugin *p = new Plugin(pi, samplerate, buffersize); + + /* Try to inject the plugin as soon as possible. */ + + while (true) { + if (pthread_mutex_trylock(mutex) != 0) + continue; + pStack->push_back(p); + pthread_mutex_unlock(mutex); + break; + } + + gu_log("[PluginHost::addPlugin] plugin id=%s loaded (%s), stack type=%d, stack size=%d\n", + fid.c_str(), p->getName().c_str(), stackType, pStack->size()); + + return p; +} + + +/* -------------------------------------------------------------------------- */ + + +Plugin *PluginHost::addPlugin(int index, int stackType, pthread_mutex_t *mutex, + class Channel *ch) +{ + juce::PluginDescription *pd = knownPluginList.getType(index); + if (pd) { + gu_log("[PluginHost::addPlugin] plugin found, uid=%s, name=%s...\n", + pd->fileOrIdentifier.toStdString().c_str(), pd->name.toStdString().c_str()); + return addPlugin(pd->fileOrIdentifier.toStdString(), stackType, mutex, ch); + } + else { + gu_log("[PluginHost::addPlugin] no plugins found at index=%d!\n", index); + return NULL; + } +} + + +/* -------------------------------------------------------------------------- */ + + +vector *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; + } +} + + +/* -------------------------------------------------------------------------- */ + + +unsigned PluginHost::countPlugins(int stackType, Channel *ch) +{ + vector *pStack = getStack(stackType, ch); + return pStack->size(); +} + + +/* -------------------------------------------------------------------------- */ + + +int PluginHost::countAvailablePlugins() +{ + return knownPluginList.getNumTypes(); +} + + +/* -------------------------------------------------------------------------- */ + + +unsigned PluginHost::countUnknownPlugins() +{ + return unknownPluginList.size(); +} + + +/* -------------------------------------------------------------------------- */ + + +PluginHost::PluginInfo PluginHost::getAvailablePluginInfo(int i) +{ + juce::PluginDescription *pd = knownPluginList.getType(i); + PluginInfo pi; + pi.uid = pd->fileOrIdentifier.toStdString(); + pi.name = pd->name.toStdString(); + pi.category = pd->category.toStdString(); + pi.manufacturerName = pd->manufacturerName.toStdString(); + pi.format = pd->pluginFormatName.toStdString(); + pi.isInstrument = pd->isInstrument; +/* + if (!p) { + gu_log("[PluginHost::getAvailablePlugin] unable to create plugin instance!\n"); + return NULL; + } + */ + return pi; +} + + +/* -------------------------------------------------------------------------- */ + + +string PluginHost::getUnknownPluginInfo(int i) +{ + return unknownPluginList.at(i); +} + + +/* -------------------------------------------------------------------------- */ + + +void PluginHost::freeStack(int stackType, pthread_mutex_t *mutex, Channel *ch) +{ + vector *pStack; + pStack = getStack(stackType, ch); + + if (pStack->size() == 0) + return; + + while (true) { + if (pthread_mutex_trylock(mutex) != 0) + continue; + for (unsigned i=0; isize(); i++) + delete pStack->at(i); + pStack->clear(); + pthread_mutex_unlock(mutex); + break; + } + gu_log("[PluginHost::freeStack] stack type=%d freed\n", stackType); +} + + +/* -------------------------------------------------------------------------- */ + + +void PluginHost::processStack(float *buffer, int stackType, Channel *ch) +{ + vector *pStack = getStack(stackType, ch); + + /* empty stack, stack not found or mixer not ready: do nothing */ + + if (pStack == NULL || pStack->size() == 0) + return; + + /* converting buffer from Giada to Juce */ + + for (int i=0; isize(); i++) { + Plugin *plugin = pStack->at(i); + if (plugin->isSuspended() || plugin->isBypassed()) + continue; + if (ch) { // ch might be null if stackType is MASTER_IN/OUT + pthread_mutex_lock(&mutex_midi); + plugin->process(audioBuffer, ch->getPluginMidiEvents()); + ch->clearMidiBuffer(); + pthread_mutex_unlock(&mutex_midi); + } + else { + juce::MidiBuffer midiBuffer; // empty buffer + plugin->process(audioBuffer, midiBuffer); + } + } + + /* converting buffer from Juce to Giada. A note for the future: if we + * overwrite (=) (as we do now) it's SEND, if we add (+) it's INSERT. */ + + for (int i=0; i *pStack = getStack(stackType, ch); + if (pStack->size() == 0) + return NULL; + if ((unsigned) index >= pStack->size()) + return NULL; + return pStack->at(index); +} + + +/* -------------------------------------------------------------------------- */ + + +int PluginHost::getPluginIndex(int id, int stackType, Channel *ch) +{ + vector *pStack = getStack(stackType, ch); + for (unsigned i=0; isize(); i++) + if (pStack->at(i)->getId() == id) + return i; + return -1; +} + + +/* -------------------------------------------------------------------------- */ + + +void PluginHost::swapPlugin(unsigned indexA, unsigned indexB, int stackType, + pthread_mutex_t *mutex, Channel *ch) +{ + vector *pStack = getStack(stackType, ch); + while (true) { + if (pthread_mutex_trylock(mutex) != 0) + continue; + std::swap(pStack->at(indexA), pStack->at(indexB)); + pthread_mutex_unlock(mutex); + gu_log("[pluginHost::swapPlugin] plugin at index %d and %d swapped\n", indexA, indexB); + return; + } +} + + +/* -------------------------------------------------------------------------- */ + + +int PluginHost::freePlugin(int id, int stackType, pthread_mutex_t *mutex, + Channel *ch) +{ + vector *pStack = getStack(stackType, ch); + for (unsigned i=0; isize(); i++) { + Plugin *pPlugin = pStack->at(i); + if (pPlugin->getId() != id) + continue; + while (true) { + if (pthread_mutex_trylock(mutex) != 0) + continue; + delete pPlugin; + pStack->erase(pStack->begin() + i); + pthread_mutex_unlock(mutex); + gu_log("[pluginHost::freePlugin] plugin id=%d removed\n", id); + return i; + } + } + gu_log("[pluginHost::freePlugin] plugin id=%d not found\n", id); + return -1; +} + + +/* -------------------------------------------------------------------------- */ + + +void PluginHost::runDispatchLoop() +{ + messageManager->runDispatchLoopUntil(10); + //gu_log("[PluginHost::runDispatchLoop] %d, hasStopMessageBeenSent=%d\n", r, messageManager->hasStopMessageBeenSent()); +} + + +/* -------------------------------------------------------------------------- */ + + +void PluginHost::freeAllStacks(vector *channels, pthread_mutex_t *mutex) +{ + freeStack(PluginHost::MASTER_OUT, mutex); + freeStack(PluginHost::MASTER_IN, mutex); + for (unsigned i=0; isize(); i++) + freeStack(PluginHost::CHANNEL, mutex, channels->at(i)); + missingPlugins = false; + unknownPluginList.clear(); +} + + +/* -------------------------------------------------------------------------- */ + + +int PluginHost::clonePlugin(Plugin *src, int stackType, pthread_mutex_t *mutex, + Channel *ch) +{ + Plugin *p = addPlugin(src->getUniqueId(), stackType, mutex, ch); + if (!p) { + gu_log("[PluginHost::clonePlugin] unable to add new plugin to stack!\n"); + return 0; + } + + for (int k=0; kgetNumParameters(); k++) + p->setParameter(k, src->getParameter(k)); + + return 1; +} + + +/* -------------------------------------------------------------------------- */ + + +bool PluginHost::doesPluginExist(const string &fid) +{ + return pluginFormat.doesPluginStillExist(*knownPluginList.getTypeForFile(fid)); +} + + +/* -------------------------------------------------------------------------- */ + + +void PluginHost::sortPlugins(int method) +{ + switch (method) { + case sortMethod::NAME: + knownPluginList.sort(juce::KnownPluginList::SortMethod::sortAlphabetically, true); + break; + case sortMethod::CATEGORY: + knownPluginList.sort(juce::KnownPluginList::SortMethod::sortByCategory, true); + break; + case sortMethod::MANUFACTURER: + knownPluginList.sort(juce::KnownPluginList::SortMethod::sortByManufacturer, true); + break; + case sortMethod::FORMAT: + knownPluginList.sort(juce::KnownPluginList::SortMethod::sortByFormat, true); + break; + } +} + + +#endif // #ifdef WITH_VST diff --git a/src/core/pluginHost.h b/src/core/pluginHost.h new file mode 100644 index 0000000..2ddc7a5 --- /dev/null +++ b/src/core/pluginHost.h @@ -0,0 +1,222 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * pluginHost + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License 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_H__ +#define __PLUGIN_HOST_H__ + + +#include +#include "../deps/juce-config.h" + + +using std::string; +using std::vector; + + +class PluginHost +{ +private: + + juce::MessageManager *messageManager; + + /* pluginFormat + * Plugin format manager. */ + + juce::VSTPluginFormat pluginFormat; + + /* knownPuginList + * List of known (i.e. scanned) plugins. */ + + juce::KnownPluginList knownPluginList; + + /* unknownPluginList + * List of unrecognized plugins found in a patch. */ + + vector unknownPluginList; + + vector masterOut; + vector masterIn; + + /* Audio|MidiBuffer + * Dynamic buffers. */ + + juce::AudioBuffer audioBuffer; + + int samplerate; + int buffersize; + + /* missingPlugins + * If some plugins from any stack are missing. */ + + bool missingPlugins; + +public: + + enum stackType { + MASTER_OUT, + MASTER_IN, + CHANNEL + }; + + enum sortMethod { + NAME, + CATEGORY, + MANUFACTURER, + FORMAT + }; + + struct PluginInfo { + string uid; + string name; + string category; + string manufacturerName; + string format; + bool isInstrument; + }; + + ~PluginHost(); + + void init(int bufSize, int frequency); + + /* scanDir + * Parse plugin directory and store list in knownPluginList. The callback is + * called on each plugin found. Used to update the main window from the GUI + * thread. */ + + int scanDir(const string &path, void (*callback)(float progress, void *p)=NULL, + void *p=NULL); + + /* (save|load)List + * (Save|Load) knownPluginList (in|from) an XML file. */ + + int saveList(const string &path); + int loadList(const string &path); + + /* addPlugin + * Add a new plugin to 'stackType' by unique id or by index in knownPluginList + * vector. Requires: + * fid - plugin unique file id (i.e. path to dynamic library) + * stackType - which stack to add plugin to + * mutex - Mixer.mutex_plugin + * freq - current audio frequency + * bufSize - buffer size + * ch - if stackType == CHANNEL. */ + + Plugin *addPlugin(const string &fid, int stackType, pthread_mutex_t *mutex, + class Channel *ch=NULL); + Plugin *addPlugin(int index, int stackType, pthread_mutex_t *mutex, + class Channel *ch=NULL); + + /* countPlugins + * Return size of 'stackType'. */ + + unsigned countPlugins(int stackType, class Channel *ch=NULL); + + /* countAvailablePlugins + * Return size of knownPluginList. */ + + int countAvailablePlugins(); + + /* countUnknownPlugins + * Return size of unknownPluginList. */ + + unsigned countUnknownPlugins(); + + /* getAvailablePluginInfo + * Return the available plugin information (name, type, ...) from + * knownPluginList at index 'index'. */ + + PluginInfo getAvailablePluginInfo(int index); + + string getUnknownPluginInfo(int index); + + /* freeStack + * free plugin stack of type 'stackType'. */ + + void freeStack(int stackType, pthread_mutex_t *mutex, class Channel *ch=NULL); + + /* processStack + * apply the fx list to the buffer. */ + + void processStack(float *buffer, int stackType, class Channel *ch=NULL); + + /* getStack + * Return a vector given the stackType. If stackType == CHANNEL + * a pointer to Channel is also required. */ + + vector *getStack(int stackType, class Channel *ch=NULL); + + /* getPluginByIndex */ + + Plugin *getPluginByIndex(int index, int stackType, class Channel *ch=NULL); + + /* getPluginIndex */ + + int getPluginIndex(int id, int stackType, class Channel *ch=NULL); + + /* swapPlugin */ + + void swapPlugin(unsigned indexA, unsigned indexB, int stackType, + pthread_mutex_t *mutex, class Channel *ch=NULL); + + /* freePlugin. + Returns the internal stack index of the deleted plugin. */ + + int freePlugin(int id, int stackType, pthread_mutex_t *mutex, + class Channel *ch=NULL); + + /* runDispatchLoop + * Wakes up plugins' GUI manager for N milliseconds. */ + + void runDispatchLoop(); + + /* freeAllStacks + * Frees everything. */ + + void freeAllStacks(vector *channels, pthread_mutex_t *mutex); + + /* clonePlugin */ + + int clonePlugin(Plugin *src, int stackType, pthread_mutex_t *mutex, + class Channel *ch); + + /* doesPluginExist */ + + bool doesPluginExist(const string &fid); + + bool hasMissingPlugins() { return missingPlugins; }; + + void sortPlugins(int sortMethod); + + pthread_mutex_t mutex_midi; +}; +#endif + +#endif // #ifdef WITH_VST diff --git a/src/core/recorder.cpp b/src/core/recorder.cpp new file mode 100644 index 0000000..992a98e --- /dev/null +++ b/src/core/recorder.cpp @@ -0,0 +1,629 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * recorder + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include +#include "../utils/log.h" +#include "mixer.h" +#include "patch_DEPR_.h" +#include "sampleChannel.h" +#include "recorder.h" + + +Recorder::Recorder() + : active (false), + sortedActions(false) +{ +} + +/* -------------------------------------------------------------------------- */ + + +void Recorder::init() +{ + sortedActions = false; + active = false; + clearAll(); +} + + +/* -------------------------------------------------------------------------- */ + + +bool Recorder::canRec(Channel *ch, Mixer *mixer) +{ + /* NO recording if: + * recorder is inactive + * mixer is not running + * mixer is recording a take somewhere + * channel is empty */ + + if (!active || + !mixer->running || + mixer->recording || + (ch->type == CHANNEL_SAMPLE && ((SampleChannel*)ch)->wave == NULL) + ) + return false; + return true; +} + + +/* -------------------------------------------------------------------------- */ + + +void Recorder::rec(int index, int type, int frame, uint32_t iValue, float fValue) +{ + /* make sure frame is even */ + + if (frame % 2 != 0) + frame++; + + /* allocating the action */ + + action *a = (action*) malloc(sizeof(action)); + a->chan = index; + a->type = type; + a->frame = frame; + a->iValue = iValue; + a->fValue = fValue; + + /* check if the frame exists in the stack. If it exists, we don't extend + * the stack, but we add (or push) a new action to it. */ + + int frameToExpand = frames.size(); + for (int i=0; ichan == index && + ac->type == type && + ac->frame == frame && + ac->iValue == iValue && + ac->fValue == fValue) + return; + } + + global.at(frameToExpand).push_back(a); // expand array + } + + sortedActions = false; + + gu_log("[REC] action recorded, type=%d frame=%d chan=%d iValue=%d (0x%X) fValue=%f\n", + a->type, a->frame, a->chan, a->iValue, a->iValue, a->fValue); + //print(); +} + + +/* -------------------------------------------------------------------------- */ + + +void Recorder::clearChan(int index) +{ + gu_log("[REC] clearing chan %d...\n", index); + + for (unsigned i=0; ichan == index) { + free(a); + global.at(i).erase(global.at(i).begin() + j); + } + else + j++; + } + } + optimize(); + //print(); +} + + +/* -------------------------------------------------------------------------- */ + + +void Recorder::clearAction(int index, char act) +{ + gu_log("[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).erase(global.at(i).begin() + j); + } + else + j++; + } + } + optimize(); + //print(); +} + + +/* -------------------------------------------------------------------------- */ + + +void Recorder::deleteAction(int chan, int frame, char type, bool checkValues, + pthread_mutex_t *mixerMutex, uint32_t iValue, float fValue) +{ + /* make sure frame is even */ + + if (frame % 2 != 0) + frame++; + + /* find the frame 'frame' */ + + bool found = false; + for (unsigned i=0; ichan == chan && a->type == (type & a->type)); + if (checkValues) + doit &= (a->iValue == iValue && a->fValue == fValue); + + if (!doit) + continue; + + while (true) { + if (pthread_mutex_trylock(mixerMutex)) { + free(a); + global.at(i).erase(global.at(i).begin() + j); + pthread_mutex_unlock(mixerMutex); + found = true; + break; + } + else + gu_log("[REC] delete action: waiting for mutex...\n"); + } + } + } + if (found) { + optimize(); + gu_log("[REC] action deleted, type=%d frame=%d chan=%d iValue=%d (%X) fValue=%f\n", + type, frame, chan, iValue, iValue, fValue); + } + else + gu_log("[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 Recorder::deleteActions(int chan, int frame_a, int frame_b, char type, + pthread_mutex_t *mixerMutex) +{ + sortActions(); + vector dels; + + for (unsigned i=0; i frame_a && frames.at(i) < frame_b) + dels.push_back(frames.at(i)); + + for (unsigned i=0; i 0) { + for (unsigned i=0; i frames.at(i)) { + std::swap(frames.at(j), frames.at(i)); + std::swap(global.at(j), global.at(i)); + } + sortedActions = true; + //print(); +} + + +/* -------------------------------------------------------------------------- */ + + +void Recorder::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 Recorder::updateSamplerate(int systemRate, int patchRate) +{ + /* diff ratio: systemRate / patchRate + * e.g. 44100 / 96000 = 0.4... */ + + if (systemRate == patchRate) + return; + + gu_log("[REC] systemRate (%d) != patchRate (%d), converting...\n", systemRate, patchRate); + + float ratio = systemRate / (float) patchRate; + for (unsigned i=0; iframe = frames.at(i); + } + } +} + + +/* -------------------------------------------------------------------------- */ + + +void Recorder::expand(int old_fpb, int new_fpb) +{ + /* this algorithm requires multiple passages if we expand from e.g. 2 + * to 16 beats, precisely 16 / 2 - 1 = 7 times (-1 is the first group, + * which exists yet). If we expand by a non-multiple, the result is zero, + * due to float->int implicit cast */ + + unsigned pass = (int) (new_fpb / old_fpb) - 1; + if (pass == 0) pass = 1; + + unsigned init_fs = frames.size(); + + for (unsigned z=1; z<=pass; z++) { + for (unsigned i=0; ichan, a->type, newframe, a->iValue, a->fValue); + } + } + } + gu_log("[REC] expanded recs\n"); + //print(); +} + + +/* -------------------------------------------------------------------------- */ + + +void Recorder::shrink(int new_fpb) +{ + /* easier than expand(): here we delete eveything beyond old_framesPerBars. */ + + unsigned i=0; + while (true) { + if (i == frames.size()) break; + + if (frames.at(i) >= new_fpb) { + for (unsigned k=0; kchan == chanIndex) + return true; + } + } + return false; +} + + +/* -------------------------------------------------------------------------- */ + + +int Recorder::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)) { + if (iValue == 0 || (iValue != 0 && (iValue & a->iValue) == a->iValue )) { + *out = global.at(i).at(j); + return 1; + } + } + } + + return -2; // no 'type' actions found +} + + +/* -------------------------------------------------------------------------- */ + + +int Recorder::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 Recorder::startOverdub(int index, char actionMask, int frame, + unsigned bufferSize) +{ + /* 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 - bufferSize; + if (truncFrame < 0) + truncFrame = 0; + gu_log("[REC] add truncation at frame %d, type=%d\n", truncFrame, cmp.a2.type); + rec(index, cmp.a2.type, truncFrame); + } + } +} + + +/* -------------------------------------------------------------------------- */ + + +void Recorder::stopOverdub(Mixer *mixer) +{ + cmp.a2.frame = mixer->currentFrame; + 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; + gu_log("[REC] ring loop! frame1=%d < frame2=%d\n", cmp.a1.frame, cmp.a2.frame); + rec(cmp.a2.chan, cmp.a2.type, mixer->totalFrames); // record at the end of the sequencer + } + else + if (cmp.a2.frame == cmp.a1.frame) { + nullLoop = true; + gu_log("[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, &mixer->mutex_recs); // false == don't check values + } + + /* remove any nested action between keypress----keyrel, then record */ + + if (!nullLoop) { + deleteActions(cmp.a2.chan, cmp.a1.frame, cmp.a2.frame, cmp.a1.type, &mixer->mutex_recs); + deleteActions(cmp.a2.chan, cmp.a1.frame, cmp.a2.frame, cmp.a2.type, &mixer->mutex_recs); + } + + 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 && act->type == cmp.a2.type) { + gu_log("[REC] add truncation at frame %d, type=%d\n", act->frame, act->type); + deleteAction(act->chan, act->frame, act->type, false, &mixer->mutex_recs); // false == don't check values + } + } +} + + +/* -------------------------------------------------------------------------- */ + + +void Recorder::print() +{ + gu_log("[REC] ** print debug **\n"); + for (unsigned i=0; itype, + global.at(i).at(j)->chan, global.at(i).at(j)->frame); + } + } +} diff --git a/src/core/recorder.h b/src/core/recorder.h new file mode 100644 index 0000000..a4f0210 --- /dev/null +++ b/src/core/recorder.h @@ -0,0 +1,191 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * recorder + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License 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 + + +#ifdef __APPLE__ // our compiler still doesn't know about cstdint (c++11 stuff) + #include +#else + #include +#endif +#include +#include + + +using std::vector; + + +class Recorder +{ +public: + + 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 + }; + + /* [global0]-->[vector<_action*>0]-->[a0][a1][a2] 0[frames1] + * [global1]-->[vector<_action*>1]-->[a0][a1][a2] 1[frames2] + * [global2]-->[vector<_action*>2]-->[a0][a1][a2] 2[frames3] + * [global3]-->[vector<_action*>3]-->[a0][a1][a2] 3[frames4] */ + + vector frames; // frame counter (sentinel) frames.size == global.size + vector> global; // container of containers of actions + vector actions; // container of actions + + bool active; + bool sortedActions; // are actions sorted via sortActions()? + + /* init + * everything starts from here. */ + + void init(); + + /* hasActions + Checks if the channel has at least one action recorded. Used after an + action deletion. */ + + bool hasActions(int chanIndex); + + /* canRec + * can a channel rec an action? Call this one BEFORE rec(). */ + + bool canRec(class Channel *ch, class Mixer *m); + + /* rec + * record an action. */ + + void rec(int chan, int action, int frame, uint32_t iValue=0, + float fValue=0.0f); + + /* clearChan + * clear all actions from a channel. */ + + void clearChan(int chan); + + /* clearAction + * clear the 'action' action type from a channel. */ + + void clearAction(int chan, char action); + + /* deleteAction + * delete ONE action. Useful in the action editor. 'type' can be a mask. */ + + void deleteAction(int chan, int frame, char type, bool checkValues, + pthread_mutex_t *mixerMutex, uint32_t iValue=0, float fValue=0.0); + + /* deleteActions + * 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, + pthread_mutex_t *mixerMutex); + + /* clearAll + * delete everything. */ + + void clearAll(); + + /* optimize + * clear frames without actions. */ + + void optimize(); + + /* sortActions + * sorts actions by frame, asc mode. */ + + void sortActions(); + + /* updateBpm + * reassign frames by calculating the new bpm value. */ + + void updateBpm(float oldval, float newval, int oldquanto); + + /* updateSamplerate + * reassign frames taking in account the samplerate. If f_system == + * f_patch nothing changes, otherwise the conversion is mandatory. */ + + void updateSamplerate(int systemRate, int patchRate); + + void expand(int old_fpb, int new_fpb); + void shrink(int new_fpb); + + /* getNextAction + * Return the nearest action in chan 'chan' of type 'action' starting + * from 'frame'. Action can be a bitmask. If iValue != 0 search for + * next action with iValue == iValue: useful for MIDI key_release. iValue + * can be a bitmask. */ + + 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, unsigned bufferSize); + void stopOverdub(class Mixer *m); + +private: + + /* composite + * a group of two actions (keypress+keyrel, muteon+muteoff) used during + * the overdub process */ + + struct composite + { + action a1; + action a2; + } cmp; + + /* print + * debug of the frame stack. */ + + void print(); + +}; + +#endif diff --git a/src/core/sampleChannel.cpp b/src/core/sampleChannel.cpp new file mode 100644 index 0000000..650d36f --- /dev/null +++ b/src/core/sampleChannel.cpp @@ -0,0 +1,1132 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * channel + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include +#include "../utils/log.h" +#include "../utils/string.h" +#include "sampleChannel.h" +#include "patch_DEPR_.h" +#include "patch.h" +#include "conf.h" +#include "mixer.h" +#include "wave.h" +#include "pluginHost.h" +#include "waveFx.h" +#include "mixerHandler.h" +#include "kernelMidi.h" +#include "kernelAudio.h" + + +using std::string; + + +extern Recorder G_Recorder; +extern KernelAudio G_KernelAudio; + + +SampleChannel::SampleChannel(int bufferSize, MidiMapConf *midiMapConf) + : Channel (CHANNEL_SAMPLE, STATUS_EMPTY, bufferSize, midiMapConf), + frameRewind (-1), + wave (NULL), + tracker (0), + begin (0), + end (0), + pitch (G_DEFAULT_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), + midiInReadActions(0x0), + midiInPitch (0x0) +{ + rsmp_state = src_new(SRC_LINEAR, 2, NULL); + pChan = (float *) malloc(G_KernelAudio.realBufsize * 2 * sizeof(float)); +} + + +/* -------------------------------------------------------------------------- */ + + +SampleChannel::~SampleChannel() +{ + if (wave) + delete wave; + src_delete(rsmp_state); + free(pChan); +} + + +/* -------------------------------------------------------------------------- */ + + +void SampleChannel::copy(const Channel *_src, pthread_mutex_t *pluginMutex) +{ + Channel::copy(_src, pluginMutex); + SampleChannel *src = (SampleChannel *) _src; + tracker = src->tracker; + begin = src->begin; + end = src->end; + boost = src->boost; + mode = src->mode; + qWait = src->qWait; + fadeinOn = src->fadeinOn; + fadeinVol = src->fadeinVol; + fadeoutOn = src->fadeoutOn; + fadeoutVol = src->fadeoutVol; + fadeoutTracker = src->fadeoutTracker; + fadeoutStep = src->fadeoutStep; + fadeoutType = src->fadeoutType; + fadeoutEnd = src->fadeoutEnd; + setPitch(src->pitch); + + if (src->wave) { + Wave *w = new Wave(*src->wave); // invoke Wave's copy constructor + pushWave(w); + generateUniqueSampleName(); + } +} + + +/* -------------------------------------------------------------------------- */ + + +void SampleChannel::generateUniqueSampleName() +{ + string oldName = wave->name; + int k = 1; // Start from k = 1, zero is too nerdy + while (!mh_uniqueSampleName(this, wave->name)) { + wave->updateName((oldName + "-" + gu_itoa(k)).c_str()); + k++; + } +} + + +/* -------------------------------------------------------------------------- */ + + +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) { + gu_log("[clear] filling pChan fadeoutTracker=%d\n", fadeoutTracker); + fadeoutTracker = fillChan(pChan, fadeoutTracker, 0); + } + } +} + + +/* -------------------------------------------------------------------------- */ + + +void SampleChannel::calcVolumeEnv(int frame) +{ + /* method: check this frame && next frame, then calculate delta */ + + Recorder::action *a0 = NULL; + Recorder::action *a1 = NULL; + int res; + + /* get this action on frame 'frame'. It's unlikely that the action + * is not found. */ + + res = G_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 = G_Recorder.getNextAction(index, ACTION_VOLUME, frame, &a1); + + if (res == -1) + res = G_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; + sendMidiLplay(); + reset(frame); +} + + +/* -------------------------------------------------------------------------- */ + + +void SampleChannel::onBar(int frame) +{ + ///if (mode == LOOP_REPEAT && status == STATUS_PLAY) + /// //setXFade(frame); + /// reset(frame); + + if (mode == LOOP_REPEAT) { + if (status == STATUS_PLAY) + //setXFade(frame); + reset(frame); + } + else + if (mode == LOOP_ONCE_BAR) { + if (status == STATUS_WAIT) { + status = STATUS_PLAY; + tracker = fillChan(vChan, tracker, frame); + sendMidiLplay(); + } + } +} + + +/* -------------------------------------------------------------------------- */ + + +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, int quantize, bool mixerIsRunning) +{ + if (readActions == false) + return; + + switch (a->type) { + case ACTION_KEYPRESS: + if (mode & SINGLE_ANY) + start(localFrame, false, quantize, mixerIsRunning, false, 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 { // at this point the sample has reached the end */ + + if (mode & (SINGLE_BASIC | SINGLE_PRESS | SINGLE_RETRIG) || + (mode == SINGLE_ENDLESS && status == STATUS_ENDING) || + (mode & LOOP_ANY && !running)) // stop loops when the seq is off + { + status = STATUS_OFF; + sendMidiLplay(); + } + + /* LOOP_ONCE or LOOP_ONCE_BAR: if ending (i.e. the user requested their + * termination), kill 'em. Let them wait otherwise. But don't put back in + * wait mode those already stopped by the conditionals above. */ + + if (mode & (LOOP_ONCE | LOOP_ONCE_BAR)) { + if (status == STATUS_ENDING) + status = STATUS_OFF; + else + if (status != STATUS_OFF) + status = STATUS_WAIT; + } + + /* check for end of samples. SINGLE_ENDLESS runs forever unless + * it's in ENDING mode. */ + + reset(frame); + } +} + + +/* -------------------------------------------------------------------------- */ + + +void SampleChannel::onZero(int frame, bool recsStopOnChanHalt) +{ + if (wave == 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; + sendMidiLplay(); + tracker = fillChan(vChan, tracker, frame); + } + + if (recStatus == REC_ENDING) { + recStatus = REC_STOPPED; + setReadActions(false, recsStopOnChanHalt); // rec stop + } + else + if (recStatus == REC_WAITING) { + recStatus = REC_READING; + setReadActions(true, recsStopOnChanHalt); // rec start + } +} + + +/* -------------------------------------------------------------------------- */ + + +void SampleChannel::quantize(int index, int localFrame, Mixer *mixer) +{ + /* skip if LOOP_ANY or not in quantizer-wait mode */ + + if ((mode & LOOP_ANY) || !qWait) + return; + + /* no fadeout if the sample starts for the first time (from a + * STATUS_OFF), it would be meaningless. */ + + if (status == STATUS_OFF) { + status = STATUS_PLAY; + sendMidiLplay(); + qWait = false; + tracker = fillChan(vChan, tracker, localFrame); /// FIXME: ??? + } + else + //setXFade(localFrame); + reset(localFrame); + + /* this is the moment in which we record the keypress, if the + * quantizer is on. SINGLE_PRESS needs overdub */ + + if (G_Recorder.canRec(this, mixer)) { + if (mode == SINGLE_PRESS) { + G_Recorder.startOverdub(index, ACTION_KEYS, mixer->currentFrame, + G_KernelAudio.realBufsize); + readActions = false; // don't read actions while overdubbing + } + else + G_Recorder.rec(index, ACTION_KEYPRESS, mixer->currentFrame); + hasActions = true; + } +} + + +/* -------------------------------------------------------------------------- */ + + +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; + } + } + + sendMidiLmute(); +} + + +/* -------------------------------------------------------------------------- */ + + +void SampleChannel::unsetMute(bool internal) +{ + if (internal) { + if (mute) + mute_i = false; + else { + if (isPlaying()) + setFadeIn(internal); + else + mute_i = false; + } + } + else { + if (mute_i) + mute = false; + else { + if (isPlaying()) + setFadeIn(internal); + else + mute = false; + } + } + + sendMidiLmute(); +} + + +/* -------------------------------------------------------------------------- */ + + +void SampleChannel::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, bool recsStopOnChanHalt) +{ + readActions = v; + if (!readActions && 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) +{ + gu_log("[xFade] frame=%d tracker=%d\n", frame, tracker); + + calcFadeoutStep(); + fadeoutOn = true; + fadeoutVol = 1.0f; + fadeoutType = XFADE; + fadeoutTracker = fillChan(pChan, tracker, 0, false); + reset(frame); +} + + +/* -------------------------------------------------------------------------- */ + + +/* on reset, if frame > 0 and in play, fill again pChan to create + * something like this: + * + * |abcdefabcdefab*abcdefabcde| + * [old data-----]*[new data--] + * + * */ + +void SampleChannel::reset(int frame) +{ + //fadeoutTracker = tracker; // store old frame number for xfade + tracker = begin; + mute_i = false; + 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; + sendMidiLplay(); +} + + +/* -------------------------------------------------------------------------- */ + + +void SampleChannel::pushWave(Wave *w) +{ + wave = w; + status = STATUS_OFF; + sendMidiLplay(); // FIXME - why here?!?! + begin = 0; + end = wave->size; +} + + +/* -------------------------------------------------------------------------- */ + + +bool SampleChannel::allocEmpty(int frames, int samplerate, int takeId) +{ + Wave *w = new Wave(); + if (!w->allocEmpty(frames, samplerate)) + return false; + + w->name = "TAKE-" + gu_itoa(takeId); + w->pathfile = gu_getCurrentPath() + G_SLASH + w->name; + wave = w; + status = STATUS_OFF; + begin = 0; + end = wave->size; + + sendMidiLplay(); // FIXME - why here?!?! + + return true; +} + + +/* -------------------------------------------------------------------------- */ + + +void SampleChannel::process(float *outBuffer, float *inBuffer) +{ + /* If armed and inbuffer is not null (i.e. input device available), copy input + buffer to vChan: this enables the live recording mode. The vChan will be + overwritten later by PluginHost::processStack, so that you would record "clean" + audio (i.e. not plugin-processed). */ + + if (armed && inBuffer) + for (int i=0; iprocessStack(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)) { + gu_log("[SampleChannel] %s: read error\n", file); + delete w; + return SAMPLE_READ_ERROR; + } + + if (w->channels() > 2) { + gu_log("[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() != samplerate) { + gu_log("[SampleChannel] input rate (%d) != system rate (%d), conversion needed\n", + w->rate(), samplerate); + w->resample(rsmpQuality, samplerate); + } + + pushWave(w); + generateUniqueSampleName(); + + gu_log("[SampleChannel] %s loaded in channel %d\n", file, index); + return SAMPLE_LOADED_OK; +} + + +/* -------------------------------------------------------------------------- */ + + +int SampleChannel::readPatch_DEPR_(const char *f, int i, Patch_DEPR_ *patch, + int samplerate, int rsmpQuality) +{ + int res = load(f, samplerate, rsmpQuality); + + volume = patch->getVol(i); + key = patch->getKey(i); + index = patch->getIndex(i); + mode = patch->getMode(i); + mute = patch->getMute(i); + mute_s = patch->getMute_s(i); + solo = patch->getSolo(i); + boost = patch->getBoost(i); + panLeft = patch->getPanLeft(i); + panRight = patch->getPanRight(i); + readActions = patch->getRecActive(i); + recStatus = readActions ? REC_READING : REC_STOPPED; + + readPatchMidiIn_DEPR_(i, *patch); + midiInReadActions = patch->getMidiValue(i, "InReadActions"); + midiInPitch = patch->getMidiValue(i, "InPitch"); + readPatchMidiOut_DEPR_(i, *patch); + + if (res == SAMPLE_LOADED_OK) { + setBegin(patch->getBegin(i)); + setEnd (patch->getEnd(i, wave->size)); + setPitch(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; + sendMidiLplay(); + } + + return res; +} + + +/* -------------------------------------------------------------------------- */ + + +int SampleChannel::readPatch(const string &basePath, int i, Patch *patch, + pthread_mutex_t *pluginMutex, int samplerate, int rsmpQuality) +{ + /* load channel's data first: if the sample is missing or wrong, the channel + * is not completely blank. */ + + Channel::readPatch("", i, patch, pluginMutex, samplerate, rsmpQuality); + + Patch::channel_t *pch = &patch->channels.at(i); + + mode = pch->mode; + boost = pch->boost; + readActions = pch->recActive; + recStatus = readActions ? REC_READING : REC_STOPPED; + midiInReadActions = pch->midiInReadActions; + midiInPitch = pch->midiInPitch; + + int res = load((basePath + pch->samplePath).c_str(), samplerate, rsmpQuality); + if (res == SAMPLE_LOADED_OK) { + setBegin(pch->begin); + setEnd (pch->end); + setPitch(pch->pitch); + } + else { + if (res == SAMPLE_LEFT_EMPTY) + status = STATUS_EMPTY; + else + if (res == SAMPLE_READ_ERROR) + status = STATUS_MISSING; + sendMidiLplay(); // FIXME - why sending MIDI lightning if sample status is wrong? + } + + return res; +} + + +/* -------------------------------------------------------------------------- */ + + +bool SampleChannel::canInputRec() +{ + return wave == NULL && armed; +} + + +/* -------------------------------------------------------------------------- */ + + +void SampleChannel::start(int frame, bool doQuantize, int quantize, + bool mixerIsRunning, bool forceStart, bool isUserGenerated) +{ + switch (status) { + case STATUS_EMPTY: + case STATUS_MISSING: + case STATUS_WRONG: + { + return; + } + case STATUS_OFF: + { + if (mode & LOOP_ANY) { + if (forceStart) { + status = STATUS_PLAY; + tracker = frame; + } + else + status = STATUS_WAIT; + sendMidiLplay(); + } + else { + if (quantize > 0 && mixerIsRunning && doQuantize) + qWait = true; + else { + status = STATUS_PLAY; + sendMidiLplay(); + + /* Do fillChan only if this is not a user-generated event (i.e. is an + action read by Mixer). Otherwise clear() will take take of calling + fillChan on the next cycle. */ + + if (!isUserGenerated) + tracker = fillChan(vChan, tracker, frame); + } + } + break; + } + case STATUS_PLAY: + { + if (mode == SINGLE_BASIC) + setFadeOut(DO_STOP); + else + if (mode == SINGLE_RETRIG) { + if (quantize > 0 && mixerIsRunning && doQuantize) + qWait = true; + else + reset(frame); + } + else + if (mode & (LOOP_ANY | SINGLE_ENDLESS)) { + status = STATUS_ENDING; + sendMidiLplay(); + } + break; + } + case STATUS_WAIT: + { + status = STATUS_OFF; + sendMidiLplay(); + break; + } + case STATUS_ENDING: + { + status = STATUS_PLAY; + sendMidiLplay(); + break; + } + } +} + + +/* -------------------------------------------------------------------------- */ + + +int SampleChannel::writePatch(int i, bool isProject, Patch *patch) +{ + // TODO - this code belongs to an upper level (glue) + + int pchIndex = Channel::writePatch(i, isProject, patch); + Patch::channel_t *pch = &patch->channels.at(pchIndex); + + if (wave != NULL) { + pch->samplePath = wave->pathfile; + if (isProject) + pch->samplePath = gu_basename(wave->pathfile); // make it portable + } + else + pch->samplePath = ""; + + pch->mode = mode; + pch->begin = begin; + pch->end = end; + pch->boost = boost; + pch->recActive = readActions; + pch->pitch = pitch; + pch->midiInReadActions = midiInReadActions; + pch->midiInPitch = midiInPitch; + + return 0; +} + + +/* -------------------------------------------------------------------------- */ + + +void SampleChannel::clearChan(float *dest, int start) +{ + memset(dest+start, 0, sizeof(float)*(bufferSize-start)); +} + + +/* -------------------------------------------------------------------------- */ + + +int SampleChannel::fillChan(float *dest, int start, int offset, bool rewind) +{ + int position; // return value: the new position + + if (pitch == 1.0f) { + + /* case 1: 'dest' lies within the original sample boundaries (start- + * end) */ + + if (start+bufferSize-offset <= end) { + memcpy(dest+offset, wave->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/core/sampleChannel.h b/src/core/sampleChannel.h new file mode 100644 index 0000000..2eeb574 --- /dev/null +++ b/src/core/sampleChannel.h @@ -0,0 +1,217 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * sampleChannel + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License 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); + + /* generateUniqueSampleName + * Sample name must be unique. Generate a new samplename with the "-[n]" + * suffix. */ + + void generateUniqueSampleName(); + +public: + + SampleChannel(int bufferSize, class MidiMapConf *midiMapConf); + ~SampleChannel(); + + void copy(const Channel *src, pthread_mutex_t *pluginMutex) override; + void clear() override; + void process(float *outBuffer, float *inBuffer) override; + void start(int frame, bool doQuantize, int quantize, bool mixerIsRunning, + bool forceStart, bool isUserGenerated) override; + void kill(int frame) override; + void empty() override; + void stopBySeq(bool chansStopOnSeqHalt) override; + void stop() override; + void rewind() override; + void setMute(bool internal) override; + void unsetMute(bool internal) override; + int readPatch_DEPR_(const char *file, int i, class Patch_DEPR_ *patch, + int samplerate, int rsmpQuality) override; + int readPatch(const string &basePath, int i, class Patch *patch, + pthread_mutex_t *pluginMutex, int samplerate, int rsmpQuality) override; + int writePatch(int i, bool isProject, class Patch *patch) override; + void quantize(int index, int localFrame, class Mixer *m) override; + void onZero(int frame, bool recsStopOnChanHalt) override; + void onBar(int frame) override; + void parseAction(Recorder::action *a, int localFrame, int globalFrame, + int quantize, bool mixerIsRunning) override; + bool canInputRec() override; + + int load(const char *file, int samplerate, int rsmpQuality); + + void reset(int frame); + + /* fade methods + * prepare channel for fade, mixer will take care of the process + * during master play. */ + + void setFadeIn (bool internal); + void setFadeOut (int actionPostFadeout); + void setXFade (int frame); + + /* pushWave + * 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 samplerate, int takeId); + + /* setReadActions + * if enabled (v == true), recorder will read actions from this channel. If + * recsStopOnChanHalt == true, stop reading actions right away. */ + + void setReadActions(bool v, bool recsStopOnChanHalt); + + /* ------------------------------------------------------------------------ */ + + 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 + + /* 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/core/wave.cpp b/src/core/wave.cpp new file mode 100644 index 0000000..7f89e32 --- /dev/null +++ b/src/core/wave.cpp @@ -0,0 +1,269 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * wave + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License 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 "../utils/fs.h" +#include "../utils/log.h" +#include "init.h" +#include "const.h" +#include "wave.h" + + +using std::string; + + +Wave::Wave() +: data (NULL), + size (0), + isLogical(0), + isEdited (0) {} + + +/* -------------------------------------------------------------------------- */ + + +Wave::~Wave() +{ + clear(); +} + + +/* -------------------------------------------------------------------------- */ + + +Wave::Wave(const Wave &other) +: data (NULL), + size (0), + isLogical(false), + isEdited (false) +{ + size = other.size; + data = new float[size]; + memcpy(data, other.data, size * sizeof(float)); + memcpy(&inHeader, &other.inHeader, sizeof(other.inHeader)); + pathfile = other.pathfile; + name = other.name; + isLogical = true; +} + +/* -------------------------------------------------------------------------- */ + + +int Wave::open(const char *f) +{ + pathfile = f; + name = gu_stripExt(gu_basename(f)); + fileIn = sf_open(f, SFM_READ, &inHeader); + + if (fileIn == NULL) { + gu_log("[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) { + gu_log("[wave] unable to allocate memory\n"); + return 0; + } + + if (sf_read_float(fileIn, data, size) != size) + gu_log("[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) { + gu_log("[wave] unable to open %s for exporting\n", f); + return 0; + } + + int out = sf_write_float(fileOut, data, size); + if (out != (int) size) { + gu_log("[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, unsigned samplerate) +{ + /* 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) { + gu_log("[wave] unable to allocate memory\n"); + return 0; + } + + memset(data, 0, sizeof(float) * size); /// FIXME - is it useful? + + inHeader.samplerate = 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) { + gu_log("[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; + + gu_log("[wave] resampling: new size=%d (%d frames)\n", newSize, newSize/2); + + int ret = src_simple(&src_data, quality, 2); + if (ret != 0) { + gu_log("[wave] resampling error: %s\n", src_strerror(ret)); + return 0; + } + + free(data); + data = tmp; + size = newSize; + inHeader.samplerate = newRate; + return 1; +} + + +/* -------------------------------------------------------------------------- */ + + +string Wave::basename(bool ext) const +{ + return ext ? gu_basename(pathfile) : gu_stripExt(gu_basename(pathfile)); +} + +string Wave::extension() const +{ + return gu_getExt(pathfile); +} + + +/* -------------------------------------------------------------------------- */ + + +void Wave::updateName(const char *n) +{ + string ext = gu_getExt(pathfile); + name = gu_stripExt(gu_basename(n)); + pathfile = gu_dirname(pathfile) + G_SLASH + name + "." + ext; + isLogical = true; + + /* a wave with updated name must become logical, since the underlying + * file does not exist yet. */ +} diff --git a/src/core/wave.h b/src/core/wave.h new file mode 100644 index 0000000..f602667 --- /dev/null +++ b/src/core/wave.h @@ -0,0 +1,93 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * wave + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License 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 + + +using std::string; + + +class Wave +{ +private: + + SNDFILE *fileIn; + SNDFILE *fileOut; + SF_INFO inHeader; + SF_INFO outHeader; + + +public: + + Wave(); + ~Wave(); + Wave(const Wave &other); + + string pathfile; // full path + sample name + 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; } + + string basename (bool ext=false) const; + string extension() const; + + 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, unsigned samplerate); + + /* resample + * simple algorithm for one-shot resampling. */ + + int resample(int quality, int newRate); +}; + +#endif diff --git a/src/core/waveFx.cpp b/src/core/waveFx.cpp new file mode 100644 index 0000000..5831968 --- /dev/null +++ b/src/core/waveFx.cpp @@ -0,0 +1,224 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * waveFx + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include +#include "../utils/log.h" +#include "wave.h" +#include "waveFx.h" + + +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) { + gu_log("[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; + + gu_log("[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) { + gu_log("[wfx] unable to allocate memory for cutting\n"); + return 0; + } + + gu_log("[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; + + gu_log("[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) { + gu_log("[wfx] unable to allocate memory for trimming\n"); + return 0; + } + + gu_log("[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; + } + + w->isEdited = true; +} + + +/* -------------------------------------------------------------------------- */ + + +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) { + gu_log("[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/core/waveFx.h b/src/core/waveFx.h new file mode 100644 index 0000000..7eae643 --- /dev/null +++ b/src/core/waveFx.h @@ -0,0 +1,59 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * waveFx + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License 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 diff --git a/src/deps/juce-config.h b/src/deps/juce-config.h new file mode 100644 index 0000000..ea20a6c --- /dev/null +++ b/src/deps/juce-config.h @@ -0,0 +1,21 @@ +#ifndef __JUCE_APPCONFIG_H__ +#define __JUCE_APPCONFIG_H__ + + +#ifdef _WIN32 + #include + #include +#endif + + +#include "juce/modules/juce_audio_basics/juce_audio_basics.h" +#include "juce/modules/juce_audio_processors/juce_audio_processors.h" +#include "juce/modules/juce_core/juce_core.h" +#include "juce/modules/juce_data_structures/juce_data_structures.h" +#include "juce/modules/juce_events/juce_events.h" +#include "juce/modules/juce_graphics/juce_graphics.h" +#include "juce/modules/juce_gui_basics/juce_gui_basics.h" +#include "juce/modules/juce_gui_extra/juce_gui_extra.h" + + +#endif diff --git a/src/deps/rtaudio-mod/RtAudio.cpp b/src/deps/rtaudio-mod/RtAudio.cpp new file mode 100755 index 0000000..1586aaa --- /dev/null +++ b/src/deps/rtaudio-mod/RtAudio.cpp @@ -0,0 +1,10237 @@ +/************************************************************************/ +/*! \class RtAudio + \brief Realtime audio i/o C++ classes. + + RtAudio provides a common API (Application Programming Interface) + for realtime audio input/output across Linux (native ALSA, Jack, + and OSS), Macintosh OS X (CoreAudio and Jack), and Windows + (DirectSound, ASIO and WASAPI) operating systems. + + RtAudio WWW site: http://www.music.mcgill.ca/~gary/rtaudio/ + + RtAudio: realtime audio i/o C++ classes + Copyright (c) 2001-2016 Gary P. Scavone + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + Any person wishing to distribute modifications to the Software is + asked to send the modifications to the original developer so that + they can be incorporated into the canonical version. This is, + however, not a binding provision of this license. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +/************************************************************************/ + +// RtAudio: Version 4.1.2 + +#include "RtAudio.h" +#include +#include +#include +#include +#include + +// Static variable definitions. +const unsigned int RtApi::MAX_SAMPLE_RATES = 14; +const unsigned int RtApi::SAMPLE_RATES[] = { + 4000, 5512, 8000, 9600, 11025, 16000, 22050, + 32000, 44100, 48000, 88200, 96000, 176400, 192000 +}; + +#if defined(__WINDOWS_DS__) || defined(__WINDOWS_ASIO__) || defined(__WINDOWS_WASAPI__) + #define MUTEX_INITIALIZE(A) InitializeCriticalSection(A) + #define MUTEX_DESTROY(A) DeleteCriticalSection(A) + #define MUTEX_LOCK(A) EnterCriticalSection(A) + #define MUTEX_UNLOCK(A) LeaveCriticalSection(A) + + #include "tchar.h" + + static std::string convertCharPointerToStdString(const char *text) + { + return std::string(text); + } + + static std::string convertCharPointerToStdString(const wchar_t *text) + { + int length = WideCharToMultiByte(CP_UTF8, 0, text, -1, NULL, 0, NULL, NULL); + std::string s( length-1, '\0' ); + WideCharToMultiByte(CP_UTF8, 0, text, -1, &s[0], length, NULL, NULL); + return s; + } + +#elif defined(__LINUX_ALSA__) || defined(__LINUX_PULSE__) || defined(__UNIX_JACK__) || defined(__LINUX_OSS__) || defined(__MACOSX_CORE__) + // pthread API + #define MUTEX_INITIALIZE(A) pthread_mutex_init(A, NULL) + #define MUTEX_DESTROY(A) pthread_mutex_destroy(A) + #define MUTEX_LOCK(A) pthread_mutex_lock(A) + #define MUTEX_UNLOCK(A) pthread_mutex_unlock(A) +#else + #define MUTEX_INITIALIZE(A) abs(*A) // dummy definitions + #define MUTEX_DESTROY(A) abs(*A) // dummy definitions +#endif + +// *************************************************** // +// +// RtAudio definitions. +// +// *************************************************** // + +std::string RtAudio :: getVersion( void ) throw() +{ + return RTAUDIO_VERSION; +} + +void RtAudio :: getCompiledApi( std::vector &apis ) throw() +{ + apis.clear(); + + // The order here will control the order of RtAudio's API search in + // the constructor. +#if defined(__UNIX_JACK__) + apis.push_back( UNIX_JACK ); +#endif +#if defined(__LINUX_ALSA__) + apis.push_back( LINUX_ALSA ); +#endif +#if defined(__LINUX_PULSE__) + apis.push_back( LINUX_PULSE ); +#endif +#if defined(__LINUX_OSS__) + apis.push_back( LINUX_OSS ); +#endif +#if defined(__WINDOWS_ASIO__) + apis.push_back( WINDOWS_ASIO ); +#endif +#if defined(__WINDOWS_WASAPI__) + apis.push_back( WINDOWS_WASAPI ); +#endif +#if defined(__WINDOWS_DS__) + apis.push_back( WINDOWS_DS ); +#endif +#if defined(__MACOSX_CORE__) + apis.push_back( MACOSX_CORE ); +#endif +#if defined(__RTAUDIO_DUMMY__) + apis.push_back( RTAUDIO_DUMMY ); +#endif +} + +void RtAudio :: openRtApi( RtAudio::Api api ) +{ + if ( rtapi_ ) + delete rtapi_; + rtapi_ = 0; + +#if defined(__UNIX_JACK__) + if ( api == UNIX_JACK ) + rtapi_ = new RtApiJack(); +#endif +#if defined(__LINUX_ALSA__) + if ( api == LINUX_ALSA ) + rtapi_ = new RtApiAlsa(); +#endif +#if defined(__LINUX_PULSE__) + if ( api == LINUX_PULSE ) + rtapi_ = new RtApiPulse(); +#endif +#if defined(__LINUX_OSS__) + if ( api == LINUX_OSS ) + rtapi_ = new RtApiOss(); +#endif +#if defined(__WINDOWS_ASIO__) + if ( api == WINDOWS_ASIO ) + rtapi_ = new RtApiAsio(); +#endif +#if defined(__WINDOWS_WASAPI__) + if ( api == WINDOWS_WASAPI ) + rtapi_ = new RtApiWasapi(); +#endif +#if defined(__WINDOWS_DS__) + if ( api == WINDOWS_DS ) + rtapi_ = new RtApiDs(); +#endif +#if defined(__MACOSX_CORE__) + if ( api == MACOSX_CORE ) + rtapi_ = new RtApiCore(); +#endif +#if defined(__RTAUDIO_DUMMY__) + if ( api == RTAUDIO_DUMMY ) + rtapi_ = new RtApiDummy(); +#endif +} + +RtAudio :: RtAudio( RtAudio::Api api ) +{ + rtapi_ = 0; + + if ( api != UNSPECIFIED ) { + // Attempt to open the specified API. + openRtApi( api ); + if ( rtapi_ ) return; + + // No compiled support for specified API value. Issue a debug + // warning and continue as if no API was specified. + std::cerr << "\nRtAudio: no compiled support for specified API argument!\n" << std::endl; + } + + // Iterate through the compiled APIs and return as soon as we find + // one with at least one device or we reach the end of the list. + std::vector< RtAudio::Api > apis; + getCompiledApi( apis ); + for ( unsigned int i=0; igetDeviceCount() ) break; + } + + if ( rtapi_ ) return; + + // It should not be possible to get here because the preprocessor + // definition __RTAUDIO_DUMMY__ is automatically defined if no + // API-specific definitions are passed to the compiler. But just in + // case something weird happens, we'll thow an error. + std::string errorText = "\nRtAudio: no compiled API support found ... critical error!!\n\n"; + throw( RtAudioError( errorText, RtAudioError::UNSPECIFIED ) ); +} + +RtAudio :: ~RtAudio() throw() +{ + if ( rtapi_ ) + delete rtapi_; +} + +void RtAudio :: openStream( RtAudio::StreamParameters *outputParameters, + RtAudio::StreamParameters *inputParameters, + RtAudioFormat format, unsigned int sampleRate, + unsigned int *bufferFrames, + RtAudioCallback callback, void *userData, + RtAudio::StreamOptions *options, + RtAudioErrorCallback errorCallback ) +{ + return rtapi_->openStream( outputParameters, inputParameters, format, + sampleRate, bufferFrames, callback, + userData, options, errorCallback ); +} + +// *************************************************** // +// +// Public RtApi definitions (see end of file for +// private or protected utility functions). +// +// *************************************************** // + +RtApi :: RtApi() +{ + stream_.state = STREAM_CLOSED; + stream_.mode = UNINITIALIZED; + stream_.apiHandle = 0; + stream_.userBuffer[0] = 0; + stream_.userBuffer[1] = 0; + MUTEX_INITIALIZE( &stream_.mutex ); + showWarnings_ = true; + firstErrorOccurred_ = false; +} + +RtApi :: ~RtApi() +{ + MUTEX_DESTROY( &stream_.mutex ); +} + +void RtApi :: openStream( RtAudio::StreamParameters *oParams, + RtAudio::StreamParameters *iParams, + RtAudioFormat format, unsigned int sampleRate, + unsigned int *bufferFrames, + RtAudioCallback callback, void *userData, + RtAudio::StreamOptions *options, + RtAudioErrorCallback errorCallback ) +{ + if ( stream_.state != STREAM_CLOSED ) { + errorText_ = "RtApi::openStream: a stream is already open!"; + error( RtAudioError::INVALID_USE ); + return; + } + + // Clear stream information potentially left from a previously open stream. + clearStreamInfo(); + + if ( oParams && oParams->nChannels < 1 ) { + errorText_ = "RtApi::openStream: a non-NULL output StreamParameters structure cannot have an nChannels value less than one."; + error( RtAudioError::INVALID_USE ); + return; + } + + if ( iParams && iParams->nChannels < 1 ) { + errorText_ = "RtApi::openStream: a non-NULL input StreamParameters structure cannot have an nChannels value less than one."; + error( RtAudioError::INVALID_USE ); + return; + } + + if ( oParams == NULL && iParams == NULL ) { + errorText_ = "RtApi::openStream: input and output StreamParameters structures are both NULL!"; + error( RtAudioError::INVALID_USE ); + return; + } + + if ( formatBytes(format) == 0 ) { + errorText_ = "RtApi::openStream: 'format' parameter value is undefined."; + error( RtAudioError::INVALID_USE ); + return; + } + + unsigned int nDevices = getDeviceCount(); + unsigned int oChannels = 0; + if ( oParams ) { + oChannels = oParams->nChannels; + if ( oParams->deviceId >= nDevices ) { + errorText_ = "RtApi::openStream: output device parameter value is invalid."; + error( RtAudioError::INVALID_USE ); + return; + } + } + + unsigned int iChannels = 0; + if ( iParams ) { + iChannels = iParams->nChannels; + if ( iParams->deviceId >= nDevices ) { + errorText_ = "RtApi::openStream: input device parameter value is invalid."; + error( RtAudioError::INVALID_USE ); + return; + } + } + + bool result; + + if ( oChannels > 0 ) { + + result = probeDeviceOpen( oParams->deviceId, OUTPUT, oChannels, oParams->firstChannel, + sampleRate, format, bufferFrames, options ); + if ( result == false ) { + error( RtAudioError::SYSTEM_ERROR ); + return; + } + } + + if ( iChannels > 0 ) { + + result = probeDeviceOpen( iParams->deviceId, INPUT, iChannels, iParams->firstChannel, + sampleRate, format, bufferFrames, options ); + if ( result == false ) { + if ( oChannels > 0 ) closeStream(); + error( RtAudioError::SYSTEM_ERROR ); + return; + } + } + + stream_.callbackInfo.callback = (void *) callback; + stream_.callbackInfo.userData = userData; + stream_.callbackInfo.errorCallback = (void *) errorCallback; + + if ( options ) options->numberOfBuffers = stream_.nBuffers; + stream_.state = STREAM_STOPPED; +} + +unsigned int RtApi :: getDefaultInputDevice( void ) +{ + // Should be implemented in subclasses if possible. + return 0; +} + +unsigned int RtApi :: getDefaultOutputDevice( void ) +{ + // Should be implemented in subclasses if possible. + return 0; +} + +void RtApi :: closeStream( void ) +{ + // MUST be implemented in subclasses! + return; +} + +bool RtApi :: probeDeviceOpen( unsigned int /*device*/, StreamMode /*mode*/, unsigned int /*channels*/, + unsigned int /*firstChannel*/, unsigned int /*sampleRate*/, + RtAudioFormat /*format*/, unsigned int * /*bufferSize*/, + RtAudio::StreamOptions * /*options*/ ) +{ + // MUST be implemented in subclasses! + return FAILURE; +} + +void RtApi :: tickStreamTime( void ) +{ + // Subclasses that do not provide their own implementation of + // getStreamTime should call this function once per buffer I/O to + // provide basic stream time support. + + stream_.streamTime += ( stream_.bufferSize * 1.0 / stream_.sampleRate ); + +#if defined( HAVE_GETTIMEOFDAY ) + gettimeofday( &stream_.lastTickTimestamp, NULL ); +#endif +} + +long RtApi :: getStreamLatency( void ) +{ + verifyStream(); + + long totalLatency = 0; + if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) + totalLatency = stream_.latency[0]; + if ( stream_.mode == INPUT || stream_.mode == DUPLEX ) + totalLatency += stream_.latency[1]; + + return totalLatency; +} + +double RtApi :: getStreamTime( void ) +{ + verifyStream(); + +#if defined( HAVE_GETTIMEOFDAY ) + // Return a very accurate estimate of the stream time by + // adding in the elapsed time since the last tick. + struct timeval then; + struct timeval now; + + if ( stream_.state != STREAM_RUNNING || stream_.streamTime == 0.0 ) + return stream_.streamTime; + + gettimeofday( &now, NULL ); + then = stream_.lastTickTimestamp; + return stream_.streamTime + + ((now.tv_sec + 0.000001 * now.tv_usec) - + (then.tv_sec + 0.000001 * then.tv_usec)); +#else + return stream_.streamTime; +#endif +} + +void RtApi :: setStreamTime( double time ) +{ + verifyStream(); + + if ( time >= 0.0 ) + stream_.streamTime = time; +} + +unsigned int RtApi :: getStreamSampleRate( void ) +{ + verifyStream(); + + return stream_.sampleRate; +} + + +// *************************************************** // +// +// OS/API-specific methods. +// +// *************************************************** // + +#if defined(__MACOSX_CORE__) + +// The OS X CoreAudio API is designed to use a separate callback +// procedure for each of its audio devices. A single RtAudio duplex +// stream using two different devices is supported here, though it +// cannot be guaranteed to always behave correctly because we cannot +// synchronize these two callbacks. +// +// A property listener is installed for over/underrun information. +// However, no functionality is currently provided to allow property +// listeners to trigger user handlers because it is unclear what could +// be done if a critical stream parameter (buffer size, sample rate, +// device disconnect) notification arrived. The listeners entail +// quite a bit of extra code and most likely, a user program wouldn't +// be prepared for the result anyway. However, we do provide a flag +// to the client callback function to inform of an over/underrun. + +// A structure to hold various information related to the CoreAudio API +// implementation. +struct CoreHandle { + AudioDeviceID id[2]; // device ids +#if defined( MAC_OS_X_VERSION_10_5 ) && ( MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 ) + AudioDeviceIOProcID procId[2]; +#endif + UInt32 iStream[2]; // device stream index (or first if using multiple) + UInt32 nStreams[2]; // number of streams to use + bool xrun[2]; + char *deviceBuffer; + pthread_cond_t condition; + int drainCounter; // Tracks callback counts when draining + bool internalDrain; // Indicates if stop is initiated from callback or not. + + CoreHandle() + :deviceBuffer(0), drainCounter(0), internalDrain(false) { nStreams[0] = 1; nStreams[1] = 1; id[0] = 0; id[1] = 0; xrun[0] = false; xrun[1] = false; } +}; + +RtApiCore:: RtApiCore() +{ +#if defined( AVAILABLE_MAC_OS_X_VERSION_10_6_AND_LATER ) + // This is a largely undocumented but absolutely necessary + // requirement starting with OS-X 10.6. If not called, queries and + // updates to various audio device properties are not handled + // correctly. + CFRunLoopRef theRunLoop = NULL; + AudioObjectPropertyAddress property = { kAudioHardwarePropertyRunLoop, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster }; + OSStatus result = AudioObjectSetPropertyData( kAudioObjectSystemObject, &property, 0, NULL, sizeof(CFRunLoopRef), &theRunLoop); + if ( result != noErr ) { + errorText_ = "RtApiCore::RtApiCore: error setting run loop property!"; + error( RtAudioError::WARNING ); + } +#endif +} + +RtApiCore :: ~RtApiCore() +{ + // The subclass destructor gets called before the base class + // destructor, so close an existing stream before deallocating + // apiDeviceId memory. + if ( stream_.state != STREAM_CLOSED ) closeStream(); +} + +unsigned int RtApiCore :: getDeviceCount( void ) +{ + // Find out how many audio devices there are, if any. + UInt32 dataSize; + AudioObjectPropertyAddress propertyAddress = { kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; + OSStatus result = AudioObjectGetPropertyDataSize( kAudioObjectSystemObject, &propertyAddress, 0, NULL, &dataSize ); + if ( result != noErr ) { + errorText_ = "RtApiCore::getDeviceCount: OS-X error getting device info!"; + error( RtAudioError::WARNING ); + return 0; + } + + return dataSize / sizeof( AudioDeviceID ); +} + +unsigned int RtApiCore :: getDefaultInputDevice( void ) +{ + unsigned int nDevices = getDeviceCount(); + if ( nDevices <= 1 ) return 0; + + AudioDeviceID id; + UInt32 dataSize = sizeof( AudioDeviceID ); + AudioObjectPropertyAddress property = { kAudioHardwarePropertyDefaultInputDevice, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; + OSStatus result = AudioObjectGetPropertyData( kAudioObjectSystemObject, &property, 0, NULL, &dataSize, &id ); + if ( result != noErr ) { + errorText_ = "RtApiCore::getDefaultInputDevice: OS-X system error getting device."; + error( RtAudioError::WARNING ); + return 0; + } + + dataSize *= nDevices; + AudioDeviceID deviceList[ nDevices ]; + property.mSelector = kAudioHardwarePropertyDevices; + result = AudioObjectGetPropertyData( kAudioObjectSystemObject, &property, 0, NULL, &dataSize, (void *) &deviceList ); + if ( result != noErr ) { + errorText_ = "RtApiCore::getDefaultInputDevice: OS-X system error getting device IDs."; + error( RtAudioError::WARNING ); + return 0; + } + + for ( unsigned int i=0; i= nDevices ) { + errorText_ = "RtApiCore::getDeviceInfo: device ID is invalid!"; + error( RtAudioError::INVALID_USE ); + return info; + } + + AudioDeviceID deviceList[ nDevices ]; + UInt32 dataSize = sizeof( AudioDeviceID ) * nDevices; + AudioObjectPropertyAddress property = { kAudioHardwarePropertyDevices, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster }; + OSStatus result = AudioObjectGetPropertyData( kAudioObjectSystemObject, &property, + 0, NULL, &dataSize, (void *) &deviceList ); + if ( result != noErr ) { + errorText_ = "RtApiCore::getDeviceInfo: OS-X system error getting device IDs."; + error( RtAudioError::WARNING ); + return info; + } + + AudioDeviceID id = deviceList[ device ]; + + // Get the device name. + info.name.erase(); + CFStringRef cfname; + dataSize = sizeof( CFStringRef ); + property.mSelector = kAudioObjectPropertyManufacturer; + result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, &cfname ); + if ( result != noErr ) { + errorStream_ << "RtApiCore::probeDeviceInfo: system error (" << getErrorCode( result ) << ") getting device manufacturer."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + return info; + } + + //const char *mname = CFStringGetCStringPtr( cfname, CFStringGetSystemEncoding() ); + int length = CFStringGetLength(cfname); + char *mname = (char *)malloc(length * 3 + 1); +#if defined( UNICODE ) || defined( _UNICODE ) + CFStringGetCString(cfname, mname, length * 3 + 1, kCFStringEncodingUTF8); +#else + CFStringGetCString(cfname, mname, length * 3 + 1, CFStringGetSystemEncoding()); +#endif + info.name.append( (const char *)mname, strlen(mname) ); + info.name.append( ": " ); + CFRelease( cfname ); + free(mname); + + property.mSelector = kAudioObjectPropertyName; + result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, &cfname ); + if ( result != noErr ) { + errorStream_ << "RtApiCore::probeDeviceInfo: system error (" << getErrorCode( result ) << ") getting device name."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + return info; + } + + //const char *name = CFStringGetCStringPtr( cfname, CFStringGetSystemEncoding() ); + length = CFStringGetLength(cfname); + char *name = (char *)malloc(length * 3 + 1); +#if defined( UNICODE ) || defined( _UNICODE ) + CFStringGetCString(cfname, name, length * 3 + 1, kCFStringEncodingUTF8); +#else + CFStringGetCString(cfname, name, length * 3 + 1, CFStringGetSystemEncoding()); +#endif + info.name.append( (const char *)name, strlen(name) ); + CFRelease( cfname ); + free(name); + + // Get the output stream "configuration". + AudioBufferList *bufferList = nil; + property.mSelector = kAudioDevicePropertyStreamConfiguration; + property.mScope = kAudioDevicePropertyScopeOutput; + // property.mElement = kAudioObjectPropertyElementWildcard; + dataSize = 0; + result = AudioObjectGetPropertyDataSize( id, &property, 0, NULL, &dataSize ); + if ( result != noErr || dataSize == 0 ) { + errorStream_ << "RtApiCore::getDeviceInfo: system error (" << getErrorCode( result ) << ") getting output stream configuration info for device (" << device << ")."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + return info; + } + + // Allocate the AudioBufferList. + bufferList = (AudioBufferList *) malloc( dataSize ); + if ( bufferList == NULL ) { + errorText_ = "RtApiCore::getDeviceInfo: memory error allocating output AudioBufferList."; + error( RtAudioError::WARNING ); + return info; + } + + result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, bufferList ); + if ( result != noErr || dataSize == 0 ) { + free( bufferList ); + errorStream_ << "RtApiCore::getDeviceInfo: system error (" << getErrorCode( result ) << ") getting output stream configuration for device (" << device << ")."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + return info; + } + + // Get output channel information. + unsigned int i, nStreams = bufferList->mNumberBuffers; + for ( i=0; imBuffers[i].mNumberChannels; + free( bufferList ); + + // Get the input stream "configuration". + property.mScope = kAudioDevicePropertyScopeInput; + result = AudioObjectGetPropertyDataSize( id, &property, 0, NULL, &dataSize ); + if ( result != noErr || dataSize == 0 ) { + errorStream_ << "RtApiCore::getDeviceInfo: system error (" << getErrorCode( result ) << ") getting input stream configuration info for device (" << device << ")."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + return info; + } + + // Allocate the AudioBufferList. + bufferList = (AudioBufferList *) malloc( dataSize ); + if ( bufferList == NULL ) { + errorText_ = "RtApiCore::getDeviceInfo: memory error allocating input AudioBufferList."; + error( RtAudioError::WARNING ); + return info; + } + + result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, bufferList ); + if (result != noErr || dataSize == 0) { + free( bufferList ); + errorStream_ << "RtApiCore::getDeviceInfo: system error (" << getErrorCode( result ) << ") getting input stream configuration for device (" << device << ")."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + return info; + } + + // Get input channel information. + nStreams = bufferList->mNumberBuffers; + for ( i=0; imBuffers[i].mNumberChannels; + free( bufferList ); + + // If device opens for both playback and capture, we determine the channels. + if ( info.outputChannels > 0 && info.inputChannels > 0 ) + info.duplexChannels = (info.outputChannels > info.inputChannels) ? info.inputChannels : info.outputChannels; + + // Probe the device sample rates. + bool isInput = false; + if ( info.outputChannels == 0 ) isInput = true; + + // Determine the supported sample rates. + property.mSelector = kAudioDevicePropertyAvailableNominalSampleRates; + if ( isInput == false ) property.mScope = kAudioDevicePropertyScopeOutput; + result = AudioObjectGetPropertyDataSize( id, &property, 0, NULL, &dataSize ); + if ( result != kAudioHardwareNoError || dataSize == 0 ) { + errorStream_ << "RtApiCore::getDeviceInfo: system error (" << getErrorCode( result ) << ") getting sample rate info."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + return info; + } + + UInt32 nRanges = dataSize / sizeof( AudioValueRange ); + AudioValueRange rangeList[ nRanges ]; + result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, &rangeList ); + if ( result != kAudioHardwareNoError ) { + errorStream_ << "RtApiCore::getDeviceInfo: system error (" << getErrorCode( result ) << ") getting sample rates."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + return info; + } + + // The sample rate reporting mechanism is a bit of a mystery. It + // seems that it can either return individual rates or a range of + // rates. I assume that if the min / max range values are the same, + // then that represents a single supported rate and if the min / max + // range values are different, the device supports an arbitrary + // range of values (though there might be multiple ranges, so we'll + // use the most conservative range). + Float64 minimumRate = 1.0, maximumRate = 10000000000.0; + bool haveValueRange = false; + info.sampleRates.clear(); + for ( UInt32 i=0; i info.preferredSampleRate ) ) + info.preferredSampleRate = tmpSr; + + } else { + haveValueRange = true; + if ( rangeList[i].mMinimum > minimumRate ) minimumRate = rangeList[i].mMinimum; + if ( rangeList[i].mMaximum < maximumRate ) maximumRate = rangeList[i].mMaximum; + } + } + + if ( haveValueRange ) { + for ( unsigned int k=0; k= (unsigned int) minimumRate && SAMPLE_RATES[k] <= (unsigned int) maximumRate ) { + info.sampleRates.push_back( SAMPLE_RATES[k] ); + + if ( !info.preferredSampleRate || ( SAMPLE_RATES[k] <= 48000 && SAMPLE_RATES[k] > info.preferredSampleRate ) ) + info.preferredSampleRate = SAMPLE_RATES[k]; + } + } + } + + // Sort and remove any redundant values + std::sort( info.sampleRates.begin(), info.sampleRates.end() ); + info.sampleRates.erase( unique( info.sampleRates.begin(), info.sampleRates.end() ), info.sampleRates.end() ); + + if ( info.sampleRates.size() == 0 ) { + errorStream_ << "RtApiCore::probeDeviceInfo: No supported sample rates found for device (" << device << ")."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + return info; + } + + // CoreAudio always uses 32-bit floating point data for PCM streams. + // Thus, any other "physical" formats supported by the device are of + // no interest to the client. + info.nativeFormats = RTAUDIO_FLOAT32; + + if ( info.outputChannels > 0 ) + if ( getDefaultOutputDevice() == device ) info.isDefaultOutput = true; + if ( info.inputChannels > 0 ) + if ( getDefaultInputDevice() == device ) info.isDefaultInput = true; + + info.probed = true; + return info; +} + +static OSStatus callbackHandler( AudioDeviceID inDevice, + const AudioTimeStamp* /*inNow*/, + const AudioBufferList* inInputData, + const AudioTimeStamp* /*inInputTime*/, + AudioBufferList* outOutputData, + const AudioTimeStamp* /*inOutputTime*/, + void* infoPointer ) +{ + CallbackInfo *info = (CallbackInfo *) infoPointer; + + RtApiCore *object = (RtApiCore *) info->object; + if ( object->callbackEvent( inDevice, inInputData, outOutputData ) == false ) + return kAudioHardwareUnspecifiedError; + else + return kAudioHardwareNoError; +} + +static OSStatus xrunListener( AudioObjectID /*inDevice*/, + UInt32 nAddresses, + const AudioObjectPropertyAddress properties[], + void* handlePointer ) +{ + CoreHandle *handle = (CoreHandle *) handlePointer; + for ( UInt32 i=0; ixrun[1] = true; + else + handle->xrun[0] = true; + } + } + + return kAudioHardwareNoError; +} + +static OSStatus rateListener( AudioObjectID inDevice, + UInt32 /*nAddresses*/, + const AudioObjectPropertyAddress /*properties*/[], + void* ratePointer ) +{ + Float64 *rate = (Float64 *) ratePointer; + UInt32 dataSize = sizeof( Float64 ); + AudioObjectPropertyAddress property = { kAudioDevicePropertyNominalSampleRate, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster }; + AudioObjectGetPropertyData( inDevice, &property, 0, NULL, &dataSize, rate ); + return kAudioHardwareNoError; +} + +bool RtApiCore :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, + unsigned int firstChannel, unsigned int sampleRate, + RtAudioFormat format, unsigned int *bufferSize, + RtAudio::StreamOptions *options ) +{ + // Get device ID + unsigned int nDevices = getDeviceCount(); + if ( nDevices == 0 ) { + // This should not happen because a check is made before this function is called. + errorText_ = "RtApiCore::probeDeviceOpen: no devices found!"; + return FAILURE; + } + + if ( device >= nDevices ) { + // This should not happen because a check is made before this function is called. + errorText_ = "RtApiCore::probeDeviceOpen: device ID is invalid!"; + return FAILURE; + } + + AudioDeviceID deviceList[ nDevices ]; + UInt32 dataSize = sizeof( AudioDeviceID ) * nDevices; + AudioObjectPropertyAddress property = { kAudioHardwarePropertyDevices, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster }; + OSStatus result = AudioObjectGetPropertyData( kAudioObjectSystemObject, &property, + 0, NULL, &dataSize, (void *) &deviceList ); + if ( result != noErr ) { + errorText_ = "RtApiCore::probeDeviceOpen: OS-X system error getting device IDs."; + return FAILURE; + } + + AudioDeviceID id = deviceList[ device ]; + + // Setup for stream mode. + bool isInput = false; + if ( mode == INPUT ) { + isInput = true; + property.mScope = kAudioDevicePropertyScopeInput; + } + else + property.mScope = kAudioDevicePropertyScopeOutput; + + // Get the stream "configuration". + AudioBufferList *bufferList = nil; + dataSize = 0; + property.mSelector = kAudioDevicePropertyStreamConfiguration; + result = AudioObjectGetPropertyDataSize( id, &property, 0, NULL, &dataSize ); + if ( result != noErr || dataSize == 0 ) { + errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") getting stream configuration info for device (" << device << ")."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Allocate the AudioBufferList. + bufferList = (AudioBufferList *) malloc( dataSize ); + if ( bufferList == NULL ) { + errorText_ = "RtApiCore::probeDeviceOpen: memory error allocating AudioBufferList."; + return FAILURE; + } + + result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, bufferList ); + if (result != noErr || dataSize == 0) { + free( bufferList ); + errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") getting stream configuration for device (" << device << ")."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Search for one or more streams that contain the desired number of + // channels. CoreAudio devices can have an arbitrary number of + // streams and each stream can have an arbitrary number of channels. + // For each stream, a single buffer of interleaved samples is + // provided. RtAudio prefers the use of one stream of interleaved + // data or multiple consecutive single-channel streams. However, we + // now support multiple consecutive multi-channel streams of + // interleaved data as well. + UInt32 iStream, offsetCounter = firstChannel; + UInt32 nStreams = bufferList->mNumberBuffers; + bool monoMode = false; + bool foundStream = false; + + // First check that the device supports the requested number of + // channels. + UInt32 deviceChannels = 0; + for ( iStream=0; iStreammBuffers[iStream].mNumberChannels; + + if ( deviceChannels < ( channels + firstChannel ) ) { + free( bufferList ); + errorStream_ << "RtApiCore::probeDeviceOpen: the device (" << device << ") does not support the requested channel count."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Look for a single stream meeting our needs. + UInt32 firstStream, streamCount = 1, streamChannels = 0, channelOffset = 0; + for ( iStream=0; iStreammBuffers[iStream].mNumberChannels; + if ( streamChannels >= channels + offsetCounter ) { + firstStream = iStream; + channelOffset = offsetCounter; + foundStream = true; + break; + } + if ( streamChannels > offsetCounter ) break; + offsetCounter -= streamChannels; + } + + // If we didn't find a single stream above, then we should be able + // to meet the channel specification with multiple streams. + if ( foundStream == false ) { + monoMode = true; + offsetCounter = firstChannel; + for ( iStream=0; iStreammBuffers[iStream].mNumberChannels; + if ( streamChannels > offsetCounter ) break; + offsetCounter -= streamChannels; + } + + firstStream = iStream; + channelOffset = offsetCounter; + Int32 channelCounter = channels + offsetCounter - streamChannels; + + if ( streamChannels > 1 ) monoMode = false; + while ( channelCounter > 0 ) { + streamChannels = bufferList->mBuffers[++iStream].mNumberChannels; + if ( streamChannels > 1 ) monoMode = false; + channelCounter -= streamChannels; + streamCount++; + } + } + + free( bufferList ); + + // Determine the buffer size. + AudioValueRange bufferRange; + dataSize = sizeof( AudioValueRange ); + property.mSelector = kAudioDevicePropertyBufferFrameSizeRange; + result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, &bufferRange ); + + if ( result != noErr ) { + errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") getting buffer size range for device (" << device << ")."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + if ( bufferRange.mMinimum > *bufferSize ) *bufferSize = (unsigned long) bufferRange.mMinimum; + else if ( bufferRange.mMaximum < *bufferSize ) *bufferSize = (unsigned long) bufferRange.mMaximum; + if ( options && options->flags & RTAUDIO_MINIMIZE_LATENCY ) *bufferSize = (unsigned long) bufferRange.mMinimum; + + // Set the buffer size. For multiple streams, I'm assuming we only + // need to make this setting for the master channel. + UInt32 theSize = (UInt32) *bufferSize; + dataSize = sizeof( UInt32 ); + property.mSelector = kAudioDevicePropertyBufferFrameSize; + result = AudioObjectSetPropertyData( id, &property, 0, NULL, dataSize, &theSize ); + + if ( result != noErr ) { + errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") setting the buffer size for device (" << device << ")."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // If attempting to setup a duplex stream, the bufferSize parameter + // MUST be the same in both directions! + *bufferSize = theSize; + if ( stream_.mode == OUTPUT && mode == INPUT && *bufferSize != stream_.bufferSize ) { + errorStream_ << "RtApiCore::probeDeviceOpen: system error setting buffer size for duplex stream on device (" << device << ")."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + stream_.bufferSize = *bufferSize; + stream_.nBuffers = 1; + + // Try to set "hog" mode ... it's not clear to me this is working. + if ( options && options->flags & RTAUDIO_HOG_DEVICE ) { + pid_t hog_pid; + dataSize = sizeof( hog_pid ); + property.mSelector = kAudioDevicePropertyHogMode; + result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, &hog_pid ); + if ( result != noErr ) { + errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") getting 'hog' state!"; + errorText_ = errorStream_.str(); + return FAILURE; + } + + if ( hog_pid != getpid() ) { + hog_pid = getpid(); + result = AudioObjectSetPropertyData( id, &property, 0, NULL, dataSize, &hog_pid ); + if ( result != noErr ) { + errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") setting 'hog' state!"; + errorText_ = errorStream_.str(); + return FAILURE; + } + } + } + + // Check and if necessary, change the sample rate for the device. + Float64 nominalRate; + dataSize = sizeof( Float64 ); + property.mSelector = kAudioDevicePropertyNominalSampleRate; + result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, &nominalRate ); + if ( result != noErr ) { + errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") getting current sample rate."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Only change the sample rate if off by more than 1 Hz. + if ( fabs( nominalRate - (double)sampleRate ) > 1.0 ) { + + // Set a property listener for the sample rate change + Float64 reportedRate = 0.0; + AudioObjectPropertyAddress tmp = { kAudioDevicePropertyNominalSampleRate, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; + result = AudioObjectAddPropertyListener( id, &tmp, rateListener, (void *) &reportedRate ); + if ( result != noErr ) { + errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") setting sample rate property listener for device (" << device << ")."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + nominalRate = (Float64) sampleRate; + result = AudioObjectSetPropertyData( id, &property, 0, NULL, dataSize, &nominalRate ); + if ( result != noErr ) { + AudioObjectRemovePropertyListener( id, &tmp, rateListener, (void *) &reportedRate ); + errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") setting sample rate for device (" << device << ")."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Now wait until the reported nominal rate is what we just set. + UInt32 microCounter = 0; + while ( reportedRate != nominalRate ) { + microCounter += 5000; + if ( microCounter > 5000000 ) break; + usleep( 5000 ); + } + + // Remove the property listener. + AudioObjectRemovePropertyListener( id, &tmp, rateListener, (void *) &reportedRate ); + + if ( microCounter > 5000000 ) { + errorStream_ << "RtApiCore::probeDeviceOpen: timeout waiting for sample rate update for device (" << device << ")."; + errorText_ = errorStream_.str(); + return FAILURE; + } + } + + // Now set the stream format for all streams. Also, check the + // physical format of the device and change that if necessary. + AudioStreamBasicDescription description; + dataSize = sizeof( AudioStreamBasicDescription ); + property.mSelector = kAudioStreamPropertyVirtualFormat; + result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, &description ); + if ( result != noErr ) { + errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") getting stream format for device (" << device << ")."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Set the sample rate and data format id. However, only make the + // change if the sample rate is not within 1.0 of the desired + // rate and the format is not linear pcm. + bool updateFormat = false; + if ( fabs( description.mSampleRate - (Float64)sampleRate ) > 1.0 ) { + description.mSampleRate = (Float64) sampleRate; + updateFormat = true; + } + + if ( description.mFormatID != kAudioFormatLinearPCM ) { + description.mFormatID = kAudioFormatLinearPCM; + updateFormat = true; + } + + if ( updateFormat ) { + result = AudioObjectSetPropertyData( id, &property, 0, NULL, dataSize, &description ); + if ( result != noErr ) { + errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") setting sample rate or data format for device (" << device << ")."; + errorText_ = errorStream_.str(); + return FAILURE; + } + } + + // Now check the physical format. + property.mSelector = kAudioStreamPropertyPhysicalFormat; + result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, &description ); + if ( result != noErr ) { + errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") getting stream physical format for device (" << device << ")."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + //std::cout << "Current physical stream format:" << std::endl; + //std::cout << " mBitsPerChan = " << description.mBitsPerChannel << std::endl; + //std::cout << " aligned high = " << (description.mFormatFlags & kAudioFormatFlagIsAlignedHigh) << ", isPacked = " << (description.mFormatFlags & kAudioFormatFlagIsPacked) << std::endl; + //std::cout << " bytesPerFrame = " << description.mBytesPerFrame << std::endl; + //std::cout << " sample rate = " << description.mSampleRate << std::endl; + + if ( description.mFormatID != kAudioFormatLinearPCM || description.mBitsPerChannel < 16 ) { + description.mFormatID = kAudioFormatLinearPCM; + //description.mSampleRate = (Float64) sampleRate; + AudioStreamBasicDescription testDescription = description; + UInt32 formatFlags; + + // We'll try higher bit rates first and then work our way down. + std::vector< std::pair > physicalFormats; + formatFlags = (description.mFormatFlags | kLinearPCMFormatFlagIsFloat) & ~kLinearPCMFormatFlagIsSignedInteger; + physicalFormats.push_back( std::pair( 32, formatFlags ) ); + formatFlags = (description.mFormatFlags | kLinearPCMFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked) & ~kLinearPCMFormatFlagIsFloat; + physicalFormats.push_back( std::pair( 32, formatFlags ) ); + physicalFormats.push_back( std::pair( 24, formatFlags ) ); // 24-bit packed + formatFlags &= ~( kAudioFormatFlagIsPacked | kAudioFormatFlagIsAlignedHigh ); + physicalFormats.push_back( std::pair( 24.2, formatFlags ) ); // 24-bit in 4 bytes, aligned low + formatFlags |= kAudioFormatFlagIsAlignedHigh; + physicalFormats.push_back( std::pair( 24.4, formatFlags ) ); // 24-bit in 4 bytes, aligned high + formatFlags = (description.mFormatFlags | kLinearPCMFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked) & ~kLinearPCMFormatFlagIsFloat; + physicalFormats.push_back( std::pair( 16, formatFlags ) ); + physicalFormats.push_back( std::pair( 8, formatFlags ) ); + + bool setPhysicalFormat = false; + for( unsigned int i=0; iflags & RTAUDIO_NONINTERLEAVED ) stream_.userInterleaved = false; + else stream_.userInterleaved = true; + stream_.deviceInterleaved[mode] = true; + if ( monoMode == true ) stream_.deviceInterleaved[mode] = false; + + // Set flags for buffer conversion. + stream_.doConvertBuffer[mode] = false; + if ( stream_.userFormat != stream_.deviceFormat[mode] ) + stream_.doConvertBuffer[mode] = true; + if ( stream_.nUserChannels[mode] < stream_.nDeviceChannels[mode] ) + stream_.doConvertBuffer[mode] = true; + if ( streamCount == 1 ) { + if ( stream_.nUserChannels[mode] > 1 && + stream_.userInterleaved != stream_.deviceInterleaved[mode] ) + stream_.doConvertBuffer[mode] = true; + } + else if ( monoMode && stream_.userInterleaved ) + stream_.doConvertBuffer[mode] = true; + + // Allocate our CoreHandle structure for the stream. + CoreHandle *handle = 0; + if ( stream_.apiHandle == 0 ) { + try { + handle = new CoreHandle; + } + catch ( std::bad_alloc& ) { + errorText_ = "RtApiCore::probeDeviceOpen: error allocating CoreHandle memory."; + goto error; + } + + if ( pthread_cond_init( &handle->condition, NULL ) ) { + errorText_ = "RtApiCore::probeDeviceOpen: error initializing pthread condition variable."; + goto error; + } + stream_.apiHandle = (void *) handle; + } + else + handle = (CoreHandle *) stream_.apiHandle; + handle->iStream[mode] = firstStream; + handle->nStreams[mode] = streamCount; + handle->id[mode] = id; + + // Allocate necessary internal buffers. + unsigned long bufferBytes; + bufferBytes = stream_.nUserChannels[mode] * *bufferSize * formatBytes( stream_.userFormat ); + // stream_.userBuffer[mode] = (char *) calloc( bufferBytes, 1 ); + stream_.userBuffer[mode] = (char *) malloc( bufferBytes * sizeof(char) ); + memset( stream_.userBuffer[mode], 0, bufferBytes * sizeof(char) ); + if ( stream_.userBuffer[mode] == NULL ) { + errorText_ = "RtApiCore::probeDeviceOpen: error allocating user buffer memory."; + goto error; + } + + // If possible, we will make use of the CoreAudio stream buffers as + // "device buffers". However, we can't do this if using multiple + // streams. + if ( stream_.doConvertBuffer[mode] && handle->nStreams[mode] > 1 ) { + + bool makeBuffer = true; + bufferBytes = stream_.nDeviceChannels[mode] * formatBytes( stream_.deviceFormat[mode] ); + if ( mode == INPUT ) { + if ( stream_.mode == OUTPUT && stream_.deviceBuffer ) { + unsigned long bytesOut = stream_.nDeviceChannels[0] * formatBytes( stream_.deviceFormat[0] ); + if ( bufferBytes <= bytesOut ) makeBuffer = false; + } + } + + if ( makeBuffer ) { + bufferBytes *= *bufferSize; + if ( stream_.deviceBuffer ) free( stream_.deviceBuffer ); + stream_.deviceBuffer = (char *) calloc( bufferBytes, 1 ); + if ( stream_.deviceBuffer == NULL ) { + errorText_ = "RtApiCore::probeDeviceOpen: error allocating device buffer memory."; + goto error; + } + } + } + + stream_.sampleRate = sampleRate; + stream_.device[mode] = device; + stream_.state = STREAM_STOPPED; + stream_.callbackInfo.object = (void *) this; + + // Setup the buffer conversion information structure. + if ( stream_.doConvertBuffer[mode] ) { + if ( streamCount > 1 ) setConvertInfo( mode, 0 ); + else setConvertInfo( mode, channelOffset ); + } + + if ( mode == INPUT && stream_.mode == OUTPUT && stream_.device[0] == device ) + // Only one callback procedure per device. + stream_.mode = DUPLEX; + else { +#if defined( MAC_OS_X_VERSION_10_5 ) && ( MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 ) + result = AudioDeviceCreateIOProcID( id, callbackHandler, (void *) &stream_.callbackInfo, &handle->procId[mode] ); +#else + // deprecated in favor of AudioDeviceCreateIOProcID() + result = AudioDeviceAddIOProc( id, callbackHandler, (void *) &stream_.callbackInfo ); +#endif + if ( result != noErr ) { + errorStream_ << "RtApiCore::probeDeviceOpen: system error setting callback for device (" << device << ")."; + errorText_ = errorStream_.str(); + goto error; + } + if ( stream_.mode == OUTPUT && mode == INPUT ) + stream_.mode = DUPLEX; + else + stream_.mode = mode; + } + + // Setup the device property listener for over/underload. + property.mSelector = kAudioDeviceProcessorOverload; + property.mScope = kAudioObjectPropertyScopeGlobal; + result = AudioObjectAddPropertyListener( id, &property, xrunListener, (void *) handle ); + + return SUCCESS; + + error: + if ( handle ) { + pthread_cond_destroy( &handle->condition ); + delete handle; + stream_.apiHandle = 0; + } + + for ( int i=0; i<2; i++ ) { + if ( stream_.userBuffer[i] ) { + free( stream_.userBuffer[i] ); + stream_.userBuffer[i] = 0; + } + } + + if ( stream_.deviceBuffer ) { + free( stream_.deviceBuffer ); + stream_.deviceBuffer = 0; + } + + stream_.state = STREAM_CLOSED; + return FAILURE; +} + +void RtApiCore :: closeStream( void ) +{ + if ( stream_.state == STREAM_CLOSED ) { + errorText_ = "RtApiCore::closeStream(): no open stream to close!"; + error( RtAudioError::WARNING ); + return; + } + + CoreHandle *handle = (CoreHandle *) stream_.apiHandle; + if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { + if (handle) { + AudioObjectPropertyAddress property = { kAudioHardwarePropertyDevices, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster }; + + property.mSelector = kAudioDeviceProcessorOverload; + property.mScope = kAudioObjectPropertyScopeGlobal; + if (AudioObjectRemovePropertyListener( handle->id[0], &property, xrunListener, (void *) handle ) != noErr) { + errorText_ = "RtApiCore::closeStream(): error removing property listener!"; + error( RtAudioError::WARNING ); + } + } + if ( stream_.state == STREAM_RUNNING ) + AudioDeviceStop( handle->id[0], callbackHandler ); +#if defined( MAC_OS_X_VERSION_10_5 ) && ( MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 ) + AudioDeviceDestroyIOProcID( handle->id[0], handle->procId[0] ); +#else + // deprecated in favor of AudioDeviceDestroyIOProcID() + AudioDeviceRemoveIOProc( handle->id[0], callbackHandler ); +#endif + } + + if ( stream_.mode == INPUT || ( stream_.mode == DUPLEX && stream_.device[0] != stream_.device[1] ) ) { + if (handle) { + AudioObjectPropertyAddress property = { kAudioHardwarePropertyDevices, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster }; + + property.mSelector = kAudioDeviceProcessorOverload; + property.mScope = kAudioObjectPropertyScopeGlobal; + if (AudioObjectRemovePropertyListener( handle->id[1], &property, xrunListener, (void *) handle ) != noErr) { + errorText_ = "RtApiCore::closeStream(): error removing property listener!"; + error( RtAudioError::WARNING ); + } + } + if ( stream_.state == STREAM_RUNNING ) + AudioDeviceStop( handle->id[1], callbackHandler ); +#if defined( MAC_OS_X_VERSION_10_5 ) && ( MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 ) + AudioDeviceDestroyIOProcID( handle->id[1], handle->procId[1] ); +#else + // deprecated in favor of AudioDeviceDestroyIOProcID() + AudioDeviceRemoveIOProc( handle->id[1], callbackHandler ); +#endif + } + + for ( int i=0; i<2; i++ ) { + if ( stream_.userBuffer[i] ) { + free( stream_.userBuffer[i] ); + stream_.userBuffer[i] = 0; + } + } + + if ( stream_.deviceBuffer ) { + free( stream_.deviceBuffer ); + stream_.deviceBuffer = 0; + } + + // Destroy pthread condition variable. + pthread_cond_destroy( &handle->condition ); + delete handle; + stream_.apiHandle = 0; + + stream_.mode = UNINITIALIZED; + stream_.state = STREAM_CLOSED; +} + +void RtApiCore :: startStream( void ) +{ + verifyStream(); + if ( stream_.state == STREAM_RUNNING ) { + errorText_ = "RtApiCore::startStream(): the stream is already running!"; + error( RtAudioError::WARNING ); + return; + } + + OSStatus result = noErr; + CoreHandle *handle = (CoreHandle *) stream_.apiHandle; + if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { + + result = AudioDeviceStart( handle->id[0], callbackHandler ); + if ( result != noErr ) { + errorStream_ << "RtApiCore::startStream: system error (" << getErrorCode( result ) << ") starting callback procedure on device (" << stream_.device[0] << ")."; + errorText_ = errorStream_.str(); + goto unlock; + } + } + + if ( stream_.mode == INPUT || + ( stream_.mode == DUPLEX && stream_.device[0] != stream_.device[1] ) ) { + + result = AudioDeviceStart( handle->id[1], callbackHandler ); + if ( result != noErr ) { + errorStream_ << "RtApiCore::startStream: system error starting input callback procedure on device (" << stream_.device[1] << ")."; + errorText_ = errorStream_.str(); + goto unlock; + } + } + + handle->drainCounter = 0; + handle->internalDrain = false; + stream_.state = STREAM_RUNNING; + + unlock: + if ( result == noErr ) return; + error( RtAudioError::SYSTEM_ERROR ); +} + +void RtApiCore :: stopStream( void ) +{ + verifyStream(); + if ( stream_.state == STREAM_STOPPED ) { + errorText_ = "RtApiCore::stopStream(): the stream is already stopped!"; + error( RtAudioError::WARNING ); + return; + } + + OSStatus result = noErr; + CoreHandle *handle = (CoreHandle *) stream_.apiHandle; + if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { + + if ( handle->drainCounter == 0 ) { + handle->drainCounter = 2; + pthread_cond_wait( &handle->condition, &stream_.mutex ); // block until signaled + } + + result = AudioDeviceStop( handle->id[0], callbackHandler ); + if ( result != noErr ) { + errorStream_ << "RtApiCore::stopStream: system error (" << getErrorCode( result ) << ") stopping callback procedure on device (" << stream_.device[0] << ")."; + errorText_ = errorStream_.str(); + goto unlock; + } + } + + if ( stream_.mode == INPUT || ( stream_.mode == DUPLEX && stream_.device[0] != stream_.device[1] ) ) { + + result = AudioDeviceStop( handle->id[1], callbackHandler ); + if ( result != noErr ) { + errorStream_ << "RtApiCore::stopStream: system error (" << getErrorCode( result ) << ") stopping input callback procedure on device (" << stream_.device[1] << ")."; + errorText_ = errorStream_.str(); + goto unlock; + } + } + + stream_.state = STREAM_STOPPED; + + unlock: + if ( result == noErr ) return; + error( RtAudioError::SYSTEM_ERROR ); +} + +void RtApiCore :: abortStream( void ) +{ + verifyStream(); + if ( stream_.state == STREAM_STOPPED ) { + errorText_ = "RtApiCore::abortStream(): the stream is already stopped!"; + error( RtAudioError::WARNING ); + return; + } + + CoreHandle *handle = (CoreHandle *) stream_.apiHandle; + handle->drainCounter = 2; + + stopStream(); +} + +// This function will be called by a spawned thread when the user +// callback function signals that the stream should be stopped or +// aborted. It is better to handle it this way because the +// callbackEvent() function probably should return before the AudioDeviceStop() +// function is called. +static void *coreStopStream( void *ptr ) +{ + CallbackInfo *info = (CallbackInfo *) ptr; + RtApiCore *object = (RtApiCore *) info->object; + + object->stopStream(); + pthread_exit( NULL ); +} + +bool RtApiCore :: callbackEvent( AudioDeviceID deviceId, + const AudioBufferList *inBufferList, + const AudioBufferList *outBufferList ) +{ + if ( stream_.state == STREAM_STOPPED || stream_.state == STREAM_STOPPING ) return SUCCESS; + if ( stream_.state == STREAM_CLOSED ) { + errorText_ = "RtApiCore::callbackEvent(): the stream is closed ... this shouldn't happen!"; + error( RtAudioError::WARNING ); + return FAILURE; + } + + CallbackInfo *info = (CallbackInfo *) &stream_.callbackInfo; + CoreHandle *handle = (CoreHandle *) stream_.apiHandle; + + // Check if we were draining the stream and signal is finished. + if ( handle->drainCounter > 3 ) { + ThreadHandle threadId; + + stream_.state = STREAM_STOPPING; + if ( handle->internalDrain == true ) + pthread_create( &threadId, NULL, coreStopStream, info ); + else // external call to stopStream() + pthread_cond_signal( &handle->condition ); + return SUCCESS; + } + + AudioDeviceID outputDevice = handle->id[0]; + + // Invoke user callback to get fresh output data UNLESS we are + // draining stream or duplex mode AND the input/output devices are + // different AND this function is called for the input device. + if ( handle->drainCounter == 0 && ( stream_.mode != DUPLEX || deviceId == outputDevice ) ) { + RtAudioCallback callback = (RtAudioCallback) info->callback; + double streamTime = getStreamTime(); + RtAudioStreamStatus status = 0; + if ( stream_.mode != INPUT && handle->xrun[0] == true ) { + status |= RTAUDIO_OUTPUT_UNDERFLOW; + handle->xrun[0] = false; + } + if ( stream_.mode != OUTPUT && handle->xrun[1] == true ) { + status |= RTAUDIO_INPUT_OVERFLOW; + handle->xrun[1] = false; + } + + int cbReturnValue = callback( stream_.userBuffer[0], stream_.userBuffer[1], + stream_.bufferSize, streamTime, status, info->userData ); + if ( cbReturnValue == 2 ) { + stream_.state = STREAM_STOPPING; + handle->drainCounter = 2; + abortStream(); + return SUCCESS; + } + else if ( cbReturnValue == 1 ) { + handle->drainCounter = 1; + handle->internalDrain = true; + } + } + + if ( stream_.mode == OUTPUT || ( stream_.mode == DUPLEX && deviceId == outputDevice ) ) { + + if ( handle->drainCounter > 1 ) { // write zeros to the output stream + + if ( handle->nStreams[0] == 1 ) { + memset( outBufferList->mBuffers[handle->iStream[0]].mData, + 0, + outBufferList->mBuffers[handle->iStream[0]].mDataByteSize ); + } + else { // fill multiple streams with zeros + for ( unsigned int i=0; inStreams[0]; i++ ) { + memset( outBufferList->mBuffers[handle->iStream[0]+i].mData, + 0, + outBufferList->mBuffers[handle->iStream[0]+i].mDataByteSize ); + } + } + } + else if ( handle->nStreams[0] == 1 ) { + if ( stream_.doConvertBuffer[0] ) { // convert directly to CoreAudio stream buffer + convertBuffer( (char *) outBufferList->mBuffers[handle->iStream[0]].mData, + stream_.userBuffer[0], stream_.convertInfo[0] ); + } + else { // copy from user buffer + memcpy( outBufferList->mBuffers[handle->iStream[0]].mData, + stream_.userBuffer[0], + outBufferList->mBuffers[handle->iStream[0]].mDataByteSize ); + } + } + else { // fill multiple streams + Float32 *inBuffer = (Float32 *) stream_.userBuffer[0]; + if ( stream_.doConvertBuffer[0] ) { + convertBuffer( stream_.deviceBuffer, stream_.userBuffer[0], stream_.convertInfo[0] ); + inBuffer = (Float32 *) stream_.deviceBuffer; + } + + if ( stream_.deviceInterleaved[0] == false ) { // mono mode + UInt32 bufferBytes = outBufferList->mBuffers[handle->iStream[0]].mDataByteSize; + for ( unsigned int i=0; imBuffers[handle->iStream[0]+i].mData, + (void *)&inBuffer[i*stream_.bufferSize], bufferBytes ); + } + } + else { // fill multiple multi-channel streams with interleaved data + UInt32 streamChannels, channelsLeft, inJump, outJump, inOffset; + Float32 *out, *in; + + bool inInterleaved = ( stream_.userInterleaved ) ? true : false; + UInt32 inChannels = stream_.nUserChannels[0]; + if ( stream_.doConvertBuffer[0] ) { + inInterleaved = true; // device buffer will always be interleaved for nStreams > 1 and not mono mode + inChannels = stream_.nDeviceChannels[0]; + } + + if ( inInterleaved ) inOffset = 1; + else inOffset = stream_.bufferSize; + + channelsLeft = inChannels; + for ( unsigned int i=0; inStreams[0]; i++ ) { + in = inBuffer; + out = (Float32 *) outBufferList->mBuffers[handle->iStream[0]+i].mData; + streamChannels = outBufferList->mBuffers[handle->iStream[0]+i].mNumberChannels; + + outJump = 0; + // Account for possible channel offset in first stream + if ( i == 0 && stream_.channelOffset[0] > 0 ) { + streamChannels -= stream_.channelOffset[0]; + outJump = stream_.channelOffset[0]; + out += outJump; + } + + // Account for possible unfilled channels at end of the last stream + if ( streamChannels > channelsLeft ) { + outJump = streamChannels - channelsLeft; + streamChannels = channelsLeft; + } + + // Determine input buffer offsets and skips + if ( inInterleaved ) { + inJump = inChannels; + in += inChannels - channelsLeft; + } + else { + inJump = 1; + in += (inChannels - channelsLeft) * inOffset; + } + + for ( unsigned int i=0; idrainCounter ) { + handle->drainCounter++; + goto unlock; + } + + AudioDeviceID inputDevice; + inputDevice = handle->id[1]; + if ( stream_.mode == INPUT || ( stream_.mode == DUPLEX && deviceId == inputDevice ) ) { + + if ( handle->nStreams[1] == 1 ) { + if ( stream_.doConvertBuffer[1] ) { // convert directly from CoreAudio stream buffer + convertBuffer( stream_.userBuffer[1], + (char *) inBufferList->mBuffers[handle->iStream[1]].mData, + stream_.convertInfo[1] ); + } + else { // copy to user buffer + memcpy( stream_.userBuffer[1], + inBufferList->mBuffers[handle->iStream[1]].mData, + inBufferList->mBuffers[handle->iStream[1]].mDataByteSize ); + } + } + else { // read from multiple streams + Float32 *outBuffer = (Float32 *) stream_.userBuffer[1]; + if ( stream_.doConvertBuffer[1] ) outBuffer = (Float32 *) stream_.deviceBuffer; + + if ( stream_.deviceInterleaved[1] == false ) { // mono mode + UInt32 bufferBytes = inBufferList->mBuffers[handle->iStream[1]].mDataByteSize; + for ( unsigned int i=0; imBuffers[handle->iStream[1]+i].mData, bufferBytes ); + } + } + else { // read from multiple multi-channel streams + UInt32 streamChannels, channelsLeft, inJump, outJump, outOffset; + Float32 *out, *in; + + bool outInterleaved = ( stream_.userInterleaved ) ? true : false; + UInt32 outChannels = stream_.nUserChannels[1]; + if ( stream_.doConvertBuffer[1] ) { + outInterleaved = true; // device buffer will always be interleaved for nStreams > 1 and not mono mode + outChannels = stream_.nDeviceChannels[1]; + } + + if ( outInterleaved ) outOffset = 1; + else outOffset = stream_.bufferSize; + + channelsLeft = outChannels; + for ( unsigned int i=0; inStreams[1]; i++ ) { + out = outBuffer; + in = (Float32 *) inBufferList->mBuffers[handle->iStream[1]+i].mData; + streamChannels = inBufferList->mBuffers[handle->iStream[1]+i].mNumberChannels; + + inJump = 0; + // Account for possible channel offset in first stream + if ( i == 0 && stream_.channelOffset[1] > 0 ) { + streamChannels -= stream_.channelOffset[1]; + inJump = stream_.channelOffset[1]; + in += inJump; + } + + // Account for possible unread channels at end of the last stream + if ( streamChannels > channelsLeft ) { + inJump = streamChannels - channelsLeft; + streamChannels = channelsLeft; + } + + // Determine output buffer offsets and skips + if ( outInterleaved ) { + outJump = outChannels; + out += outChannels - channelsLeft; + } + else { + outJump = 1; + out += (outChannels - channelsLeft) * outOffset; + } + + for ( unsigned int i=0; i +#include +#include + +// A structure to hold various information related to the Jack API +// implementation. +struct JackHandle { + jack_client_t *client; + jack_port_t **ports[2]; + std::string deviceName[2]; + bool xrun[2]; + pthread_cond_t condition; + int drainCounter; // Tracks callback counts when draining + bool internalDrain; // Indicates if stop is initiated from callback or not. + + JackHandle() + :client(0), drainCounter(0), internalDrain(false) { ports[0] = 0; ports[1] = 0; xrun[0] = false; xrun[1] = false; } +}; + +/* --- Monocasual hack ------------------------------------------------------ */ +#ifdef __linux__ +void *RtApi :: __HACK__getJackClient() { + JackHandle *handle = (JackHandle *) stream_.apiHandle; + return (void*) handle->client; +} +#endif +/* -------------------------------------------------------------------------- */ + +static void jackSilentError( const char * ) {} + +RtApiJack :: RtApiJack() +{ + // Nothing to do here. +#if !defined(__RTAUDIO_DEBUG__) + // Turn off Jack's internal error reporting. + jack_set_error_function( &jackSilentError ); +#endif +} + +RtApiJack :: ~RtApiJack() +{ + if ( stream_.state != STREAM_CLOSED ) closeStream(); +} + +unsigned int RtApiJack :: getDeviceCount( void ) +{ + // See if we can become a jack client. + jack_options_t options = (jack_options_t) ( JackNoStartServer ); //JackNullOption; + jack_status_t *status = NULL; + jack_client_t *client = jack_client_open( "RtApiJackCount", options, status ); + if ( client == 0 ) return 0; + + const char **ports; + std::string port, previousPort; + unsigned int nChannels = 0, nDevices = 0; + ports = jack_get_ports( client, NULL, NULL, 0 ); + if ( ports ) { + // Parse the port names up to the first colon (:). + size_t iColon = 0; + do { + port = (char *) ports[ nChannels ]; + iColon = port.find(":"); + if ( iColon != std::string::npos ) { + port = port.substr( 0, iColon + 1 ); + if ( port != previousPort ) { + nDevices++; + previousPort = port; + } + } + } while ( ports[++nChannels] ); + free( ports ); + } + + jack_client_close( client ); + return nDevices; +} + +RtAudio::DeviceInfo RtApiJack :: getDeviceInfo( unsigned int device ) +{ + RtAudio::DeviceInfo info; + info.probed = false; + + jack_options_t options = (jack_options_t) ( JackNoStartServer ); //JackNullOption + jack_status_t *status = NULL; + jack_client_t *client = jack_client_open( "RtApiJackInfo", options, status ); + if ( client == 0 ) { + errorText_ = "RtApiJack::getDeviceInfo: Jack server not found or connection error!"; + error( RtAudioError::WARNING ); + return info; + } + + const char **ports; + std::string port, previousPort; + unsigned int nPorts = 0, nDevices = 0; + ports = jack_get_ports( client, NULL, NULL, 0 ); + if ( ports ) { + // Parse the port names up to the first colon (:). + size_t iColon = 0; + do { + port = (char *) ports[ nPorts ]; + iColon = port.find(":"); + if ( iColon != std::string::npos ) { + port = port.substr( 0, iColon ); + if ( port != previousPort ) { + if ( nDevices == device ) info.name = port; + nDevices++; + previousPort = port; + } + } + } while ( ports[++nPorts] ); + free( ports ); + } + + if ( device >= nDevices ) { + jack_client_close( client ); + errorText_ = "RtApiJack::getDeviceInfo: device ID is invalid!"; + error( RtAudioError::INVALID_USE ); + return info; + } + + // Get the current jack server sample rate. + info.sampleRates.clear(); + + info.preferredSampleRate = jack_get_sample_rate( client ); + info.sampleRates.push_back( info.preferredSampleRate ); + + // Count the available ports containing the client name as device + // channels. Jack "input ports" equal RtAudio output channels. + unsigned int nChannels = 0; + ports = jack_get_ports( client, info.name.c_str(), NULL, JackPortIsInput ); + if ( ports ) { + while ( ports[ nChannels ] ) nChannels++; + free( ports ); + info.outputChannels = nChannels; + } + + // Jack "output ports" equal RtAudio input channels. + nChannels = 0; + ports = jack_get_ports( client, info.name.c_str(), NULL, JackPortIsOutput ); + if ( ports ) { + while ( ports[ nChannels ] ) nChannels++; + free( ports ); + info.inputChannels = nChannels; + } + + if ( info.outputChannels == 0 && info.inputChannels == 0 ) { + jack_client_close(client); + errorText_ = "RtApiJack::getDeviceInfo: error determining Jack input/output channels!"; + error( RtAudioError::WARNING ); + return info; + } + + // If device opens for both playback and capture, we determine the channels. + if ( info.outputChannels > 0 && info.inputChannels > 0 ) + info.duplexChannels = (info.outputChannels > info.inputChannels) ? info.inputChannels : info.outputChannels; + + // Jack always uses 32-bit floats. + info.nativeFormats = RTAUDIO_FLOAT32; + + // Jack doesn't provide default devices so we'll use the first available one. + if ( device == 0 && info.outputChannels > 0 ) + info.isDefaultOutput = true; + if ( device == 0 && info.inputChannels > 0 ) + info.isDefaultInput = true; + + jack_client_close(client); + info.probed = true; + return info; +} + +static int jackCallbackHandler( jack_nframes_t nframes, void *infoPointer ) +{ + CallbackInfo *info = (CallbackInfo *) infoPointer; + + RtApiJack *object = (RtApiJack *) info->object; + if ( object->callbackEvent( (unsigned long) nframes ) == false ) return 1; + + return 0; +} + +// This function will be called by a spawned thread when the Jack +// server signals that it is shutting down. It is necessary to handle +// it this way because the jackShutdown() function must return before +// the jack_deactivate() function (in closeStream()) will return. +static void *jackCloseStream( void *ptr ) +{ + CallbackInfo *info = (CallbackInfo *) ptr; + RtApiJack *object = (RtApiJack *) info->object; + + object->closeStream(); + + pthread_exit( NULL ); +} +static void jackShutdown( void *infoPointer ) +{ + CallbackInfo *info = (CallbackInfo *) infoPointer; + RtApiJack *object = (RtApiJack *) info->object; + + // Check current stream state. If stopped, then we'll assume this + // was called as a result of a call to RtApiJack::stopStream (the + // deactivation of a client handle causes this function to be called). + // If not, we'll assume the Jack server is shutting down or some + // other problem occurred and we should close the stream. + if ( object->isStreamRunning() == false ) return; + + ThreadHandle threadId; + pthread_create( &threadId, NULL, jackCloseStream, info ); + std::cerr << "\nRtApiJack: the Jack server is shutting down this client ... stream stopped and closed!!\n" << std::endl; +} + +static int jackXrun( void *infoPointer ) +{ + JackHandle *handle = (JackHandle *) infoPointer; + + if ( handle->ports[0] ) handle->xrun[0] = true; + if ( handle->ports[1] ) handle->xrun[1] = true; + + return 0; +} + +bool RtApiJack :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, + unsigned int firstChannel, unsigned int sampleRate, + RtAudioFormat format, unsigned int *bufferSize, + RtAudio::StreamOptions *options ) +{ + JackHandle *handle = (JackHandle *) stream_.apiHandle; + + // Look for jack server and try to become a client (only do once per stream). + jack_client_t *client = 0; + if ( mode == OUTPUT || ( mode == INPUT && stream_.mode != OUTPUT ) ) { + jack_options_t jackoptions = (jack_options_t) ( JackNoStartServer ); //JackNullOption; + jack_status_t *status = NULL; + if ( options && !options->streamName.empty() ) + client = jack_client_open( options->streamName.c_str(), jackoptions, status ); + else + client = jack_client_open( "RtApiJack", jackoptions, status ); + if ( client == 0 ) { + errorText_ = "RtApiJack::probeDeviceOpen: Jack server not found or connection error!"; + error( RtAudioError::WARNING ); + return FAILURE; + } + } + else { + // The handle must have been created on an earlier pass. + client = handle->client; + } + + const char **ports; + std::string port, previousPort, deviceName; + unsigned int nPorts = 0, nDevices = 0; + ports = jack_get_ports( client, NULL, NULL, 0 ); + if ( ports ) { + // Parse the port names up to the first colon (:). + size_t iColon = 0; + do { + port = (char *) ports[ nPorts ]; + iColon = port.find(":"); + if ( iColon != std::string::npos ) { + port = port.substr( 0, iColon ); + if ( port != previousPort ) { + if ( nDevices == device ) deviceName = port; + nDevices++; + previousPort = port; + } + } + } while ( ports[++nPorts] ); + free( ports ); + } + + if ( device >= nDevices ) { + errorText_ = "RtApiJack::probeDeviceOpen: device ID is invalid!"; + return FAILURE; + } + + // Count the available ports containing the client name as device + // channels. Jack "input ports" equal RtAudio output channels. + unsigned int nChannels = 0; + unsigned long flag = JackPortIsInput; + if ( mode == INPUT ) flag = JackPortIsOutput; + ports = jack_get_ports( client, deviceName.c_str(), NULL, flag ); + if ( ports ) { + while ( ports[ nChannels ] ) nChannels++; + free( ports ); + } + + // Compare the jack ports for specified client to the requested number of channels. + if ( nChannels < (channels + firstChannel) ) { + errorStream_ << "RtApiJack::probeDeviceOpen: requested number of channels (" << channels << ") + offset (" << firstChannel << ") not found for specified device (" << device << ":" << deviceName << ")."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Check the jack server sample rate. + unsigned int jackRate = jack_get_sample_rate( client ); + if ( sampleRate != jackRate ) { + jack_client_close( client ); + errorStream_ << "RtApiJack::probeDeviceOpen: the requested sample rate (" << sampleRate << ") is different than the JACK server rate (" << jackRate << ")."; + errorText_ = errorStream_.str(); + return FAILURE; + } + stream_.sampleRate = jackRate; + + // Get the latency of the JACK port. + ports = jack_get_ports( client, deviceName.c_str(), NULL, flag ); + if ( ports[ firstChannel ] ) { + // Added by Ge Wang + jack_latency_callback_mode_t cbmode = (mode == INPUT ? JackCaptureLatency : JackPlaybackLatency); + // the range (usually the min and max are equal) + jack_latency_range_t latrange; latrange.min = latrange.max = 0; + // get the latency range + jack_port_get_latency_range( jack_port_by_name( client, ports[firstChannel] ), cbmode, &latrange ); + // be optimistic, use the min! + stream_.latency[mode] = latrange.min; + //stream_.latency[mode] = jack_port_get_latency( jack_port_by_name( client, ports[ firstChannel ] ) ); + } + free( ports ); + + // The jack server always uses 32-bit floating-point data. + stream_.deviceFormat[mode] = RTAUDIO_FLOAT32; + stream_.userFormat = format; + + if ( options && options->flags & RTAUDIO_NONINTERLEAVED ) stream_.userInterleaved = false; + else stream_.userInterleaved = true; + + // Jack always uses non-interleaved buffers. + stream_.deviceInterleaved[mode] = false; + + // Jack always provides host byte-ordered data. + stream_.doByteSwap[mode] = false; + + // Get the buffer size. The buffer size and number of buffers + // (periods) is set when the jack server is started. + stream_.bufferSize = (int) jack_get_buffer_size( client ); + *bufferSize = stream_.bufferSize; + + stream_.nDeviceChannels[mode] = channels; + stream_.nUserChannels[mode] = channels; + + // Set flags for buffer conversion. + stream_.doConvertBuffer[mode] = false; + if ( stream_.userFormat != stream_.deviceFormat[mode] ) + stream_.doConvertBuffer[mode] = true; + if ( stream_.userInterleaved != stream_.deviceInterleaved[mode] && + stream_.nUserChannels[mode] > 1 ) + stream_.doConvertBuffer[mode] = true; + + // Allocate our JackHandle structure for the stream. + if ( handle == 0 ) { + try { + handle = new JackHandle; + } + catch ( std::bad_alloc& ) { + errorText_ = "RtApiJack::probeDeviceOpen: error allocating JackHandle memory."; + goto error; + } + + if ( pthread_cond_init(&handle->condition, NULL) ) { + errorText_ = "RtApiJack::probeDeviceOpen: error initializing pthread condition variable."; + goto error; + } + stream_.apiHandle = (void *) handle; + handle->client = client; + } + handle->deviceName[mode] = deviceName; + + // Allocate necessary internal buffers. + unsigned long bufferBytes; + bufferBytes = stream_.nUserChannels[mode] * *bufferSize * formatBytes( stream_.userFormat ); + stream_.userBuffer[mode] = (char *) calloc( bufferBytes, 1 ); + if ( stream_.userBuffer[mode] == NULL ) { + errorText_ = "RtApiJack::probeDeviceOpen: error allocating user buffer memory."; + goto error; + } + + if ( stream_.doConvertBuffer[mode] ) { + + bool makeBuffer = true; + if ( mode == OUTPUT ) + bufferBytes = stream_.nDeviceChannels[0] * formatBytes( stream_.deviceFormat[0] ); + else { // mode == INPUT + bufferBytes = stream_.nDeviceChannels[1] * formatBytes( stream_.deviceFormat[1] ); + if ( stream_.mode == OUTPUT && stream_.deviceBuffer ) { + unsigned long bytesOut = stream_.nDeviceChannels[0] * formatBytes(stream_.deviceFormat[0]); + if ( bufferBytes < bytesOut ) makeBuffer = false; + } + } + + if ( makeBuffer ) { + bufferBytes *= *bufferSize; + if ( stream_.deviceBuffer ) free( stream_.deviceBuffer ); + stream_.deviceBuffer = (char *) calloc( bufferBytes, 1 ); + if ( stream_.deviceBuffer == NULL ) { + errorText_ = "RtApiJack::probeDeviceOpen: error allocating device buffer memory."; + goto error; + } + } + } + + // Allocate memory for the Jack ports (channels) identifiers. + handle->ports[mode] = (jack_port_t **) malloc ( sizeof (jack_port_t *) * channels ); + if ( handle->ports[mode] == NULL ) { + errorText_ = "RtApiJack::probeDeviceOpen: error allocating port memory."; + goto error; + } + + stream_.device[mode] = device; + stream_.channelOffset[mode] = firstChannel; + stream_.state = STREAM_STOPPED; + stream_.callbackInfo.object = (void *) this; + + if ( stream_.mode == OUTPUT && mode == INPUT ) + // We had already set up the stream for output. + stream_.mode = DUPLEX; + else { + stream_.mode = mode; + jack_set_process_callback( handle->client, jackCallbackHandler, (void *) &stream_.callbackInfo ); + jack_set_xrun_callback( handle->client, jackXrun, (void *) &handle ); + jack_on_shutdown( handle->client, jackShutdown, (void *) &stream_.callbackInfo ); + } + + // Register our ports. + char label[64]; + if ( mode == OUTPUT ) { + for ( unsigned int i=0; iports[0][i] = jack_port_register( handle->client, (const char *)label, + JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0 ); + } + } + else { + for ( unsigned int i=0; iports[1][i] = jack_port_register( handle->client, (const char *)label, + JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0 ); + } + } + + // Setup the buffer conversion information structure. We don't use + // buffers to do channel offsets, so we override that parameter + // here. + if ( stream_.doConvertBuffer[mode] ) setConvertInfo( mode, 0 ); + + return SUCCESS; + + error: + if ( handle ) { + pthread_cond_destroy( &handle->condition ); + jack_client_close( handle->client ); + + if ( handle->ports[0] ) free( handle->ports[0] ); + if ( handle->ports[1] ) free( handle->ports[1] ); + + delete handle; + stream_.apiHandle = 0; + } + + for ( int i=0; i<2; i++ ) { + if ( stream_.userBuffer[i] ) { + free( stream_.userBuffer[i] ); + stream_.userBuffer[i] = 0; + } + } + + if ( stream_.deviceBuffer ) { + free( stream_.deviceBuffer ); + stream_.deviceBuffer = 0; + } + + return FAILURE; +} + +void RtApiJack :: closeStream( void ) +{ + if ( stream_.state == STREAM_CLOSED ) { + errorText_ = "RtApiJack::closeStream(): no open stream to close!"; + error( RtAudioError::WARNING ); + return; + } + + JackHandle *handle = (JackHandle *) stream_.apiHandle; + if ( handle ) { + + if ( stream_.state == STREAM_RUNNING ) + jack_deactivate( handle->client ); + + jack_client_close( handle->client ); + } + + if ( handle ) { + if ( handle->ports[0] ) free( handle->ports[0] ); + if ( handle->ports[1] ) free( handle->ports[1] ); + pthread_cond_destroy( &handle->condition ); + delete handle; + stream_.apiHandle = 0; + } + + for ( int i=0; i<2; i++ ) { + if ( stream_.userBuffer[i] ) { + free( stream_.userBuffer[i] ); + stream_.userBuffer[i] = 0; + } + } + + if ( stream_.deviceBuffer ) { + free( stream_.deviceBuffer ); + stream_.deviceBuffer = 0; + } + + stream_.mode = UNINITIALIZED; + stream_.state = STREAM_CLOSED; +} + +void RtApiJack :: startStream( void ) +{ + verifyStream(); + if ( stream_.state == STREAM_RUNNING ) { + errorText_ = "RtApiJack::startStream(): the stream is already running!"; + error( RtAudioError::WARNING ); + return; + } + + JackHandle *handle = (JackHandle *) stream_.apiHandle; + int result = jack_activate( handle->client ); + if ( result ) { + errorText_ = "RtApiJack::startStream(): unable to activate JACK client!"; + goto unlock; + } + + const char **ports; + + // Get the list of available ports. + if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { + result = 1; + ports = jack_get_ports( handle->client, handle->deviceName[0].c_str(), NULL, JackPortIsInput); + if ( ports == NULL) { + errorText_ = "RtApiJack::startStream(): error determining available JACK input ports!"; + goto unlock; + } + + // Now make the port connections. Since RtAudio wasn't designed to + // allow the user to select particular channels of a device, we'll + // just open the first "nChannels" ports with offset. + for ( unsigned int i=0; iclient, jack_port_name( handle->ports[0][i] ), ports[ stream_.channelOffset[0] + i ] ); + if ( result ) { + free( ports ); + errorText_ = "RtApiJack::startStream(): error connecting output ports!"; + goto unlock; + } + } + free(ports); + } + + if ( stream_.mode == INPUT || stream_.mode == DUPLEX ) { + result = 1; + ports = jack_get_ports( handle->client, handle->deviceName[1].c_str(), NULL, JackPortIsOutput ); + if ( ports == NULL) { + errorText_ = "RtApiJack::startStream(): error determining available JACK output ports!"; + goto unlock; + } + + // Now make the port connections. See note above. + for ( unsigned int i=0; iclient, ports[ stream_.channelOffset[1] + i ], jack_port_name( handle->ports[1][i] ) ); + if ( result ) { + free( ports ); + errorText_ = "RtApiJack::startStream(): error connecting input ports!"; + goto unlock; + } + } + free(ports); + } + + handle->drainCounter = 0; + handle->internalDrain = false; + stream_.state = STREAM_RUNNING; + + unlock: + if ( result == 0 ) return; + error( RtAudioError::SYSTEM_ERROR ); +} + +void RtApiJack :: stopStream( void ) +{ + verifyStream(); + if ( stream_.state == STREAM_STOPPED ) { + errorText_ = "RtApiJack::stopStream(): the stream is already stopped!"; + error( RtAudioError::WARNING ); + return; + } + + JackHandle *handle = (JackHandle *) stream_.apiHandle; + if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { + + if ( handle->drainCounter == 0 ) { + handle->drainCounter = 2; + pthread_cond_wait( &handle->condition, &stream_.mutex ); // block until signaled + } + } + + jack_deactivate( handle->client ); + stream_.state = STREAM_STOPPED; +} + +void RtApiJack :: abortStream( void ) +{ + verifyStream(); + if ( stream_.state == STREAM_STOPPED ) { + errorText_ = "RtApiJack::abortStream(): the stream is already stopped!"; + error( RtAudioError::WARNING ); + return; + } + + JackHandle *handle = (JackHandle *) stream_.apiHandle; + handle->drainCounter = 2; + + stopStream(); +} + +// This function will be called by a spawned thread when the user +// callback function signals that the stream should be stopped or +// aborted. It is necessary to handle it this way because the +// callbackEvent() function must return before the jack_deactivate() +// function will return. +static void *jackStopStream( void *ptr ) +{ + CallbackInfo *info = (CallbackInfo *) ptr; + RtApiJack *object = (RtApiJack *) info->object; + + object->stopStream(); + pthread_exit( NULL ); +} + +bool RtApiJack :: callbackEvent( unsigned long nframes ) +{ + if ( stream_.state == STREAM_STOPPED || stream_.state == STREAM_STOPPING ) return SUCCESS; + if ( stream_.state == STREAM_CLOSED ) { + errorText_ = "RtApiCore::callbackEvent(): the stream is closed ... this shouldn't happen!"; + error( RtAudioError::WARNING ); + return FAILURE; + } + if ( stream_.bufferSize != nframes ) { + errorText_ = "RtApiCore::callbackEvent(): the JACK buffer size has changed ... cannot process!"; + error( RtAudioError::WARNING ); + return FAILURE; + } + + CallbackInfo *info = (CallbackInfo *) &stream_.callbackInfo; + JackHandle *handle = (JackHandle *) stream_.apiHandle; + + // Check if we were draining the stream and signal is finished. + if ( handle->drainCounter > 3 ) { + ThreadHandle threadId; + + stream_.state = STREAM_STOPPING; + if ( handle->internalDrain == true ) + pthread_create( &threadId, NULL, jackStopStream, info ); + else + pthread_cond_signal( &handle->condition ); + return SUCCESS; + } + + // Invoke user callback first, to get fresh output data. + if ( handle->drainCounter == 0 ) { + RtAudioCallback callback = (RtAudioCallback) info->callback; + double streamTime = getStreamTime(); + RtAudioStreamStatus status = 0; + if ( stream_.mode != INPUT && handle->xrun[0] == true ) { + status |= RTAUDIO_OUTPUT_UNDERFLOW; + handle->xrun[0] = false; + } + if ( stream_.mode != OUTPUT && handle->xrun[1] == true ) { + status |= RTAUDIO_INPUT_OVERFLOW; + handle->xrun[1] = false; + } + int cbReturnValue = callback( stream_.userBuffer[0], stream_.userBuffer[1], + stream_.bufferSize, streamTime, status, info->userData ); + if ( cbReturnValue == 2 ) { + stream_.state = STREAM_STOPPING; + handle->drainCounter = 2; + ThreadHandle id; + pthread_create( &id, NULL, jackStopStream, info ); + return SUCCESS; + } + else if ( cbReturnValue == 1 ) { + handle->drainCounter = 1; + handle->internalDrain = true; + } + } + + jack_default_audio_sample_t *jackbuffer; + unsigned long bufferBytes = nframes * sizeof( jack_default_audio_sample_t ); + if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { + + if ( handle->drainCounter > 1 ) { // write zeros to the output stream + + for ( unsigned int i=0; iports[0][i], (jack_nframes_t) nframes ); + memset( jackbuffer, 0, bufferBytes ); + } + + } + else if ( stream_.doConvertBuffer[0] ) { + + convertBuffer( stream_.deviceBuffer, stream_.userBuffer[0], stream_.convertInfo[0] ); + + for ( unsigned int i=0; iports[0][i], (jack_nframes_t) nframes ); + memcpy( jackbuffer, &stream_.deviceBuffer[i*bufferBytes], bufferBytes ); + } + } + else { // no buffer conversion + for ( unsigned int i=0; iports[0][i], (jack_nframes_t) nframes ); + memcpy( jackbuffer, &stream_.userBuffer[0][i*bufferBytes], bufferBytes ); + } + } + } + + // Don't bother draining input + if ( handle->drainCounter ) { + handle->drainCounter++; + goto unlock; + } + + if ( stream_.mode == INPUT || stream_.mode == DUPLEX ) { + + if ( stream_.doConvertBuffer[1] ) { + for ( unsigned int i=0; iports[1][i], (jack_nframes_t) nframes ); + memcpy( &stream_.deviceBuffer[i*bufferBytes], jackbuffer, bufferBytes ); + } + convertBuffer( stream_.userBuffer[1], stream_.deviceBuffer, stream_.convertInfo[1] ); + } + else { // no buffer conversion + for ( unsigned int i=0; iports[1][i], (jack_nframes_t) nframes ); + memcpy( &stream_.userBuffer[1][i*bufferBytes], jackbuffer, bufferBytes ); + } + } + } + + unlock: + RtApi::tickStreamTime(); + return SUCCESS; +} + //******************** End of __UNIX_JACK__ *********************// +#endif + +#if defined(__WINDOWS_ASIO__) // ASIO API on Windows + +// The ASIO API is designed around a callback scheme, so this +// implementation is similar to that used for OS-X CoreAudio and Linux +// Jack. The primary constraint with ASIO is that it only allows +// access to a single driver at a time. Thus, it is not possible to +// have more than one simultaneous RtAudio stream. +// +// This implementation also requires a number of external ASIO files +// and a few global variables. The ASIO callback scheme does not +// allow for the passing of user data, so we must create a global +// pointer to our callbackInfo structure. +// +// On unix systems, we make use of a pthread condition variable. +// Since there is no equivalent in Windows, I hacked something based +// on information found in +// http://www.cs.wustl.edu/~schmidt/win32-cv-1.html. + +#include "asiosys.h" +#include "asio.h" +#include "iasiothiscallresolver.h" +#include "asiodrivers.h" +#include + +static AsioDrivers drivers; +static ASIOCallbacks asioCallbacks; +static ASIODriverInfo driverInfo; +static CallbackInfo *asioCallbackInfo; +static bool asioXRun; + +struct AsioHandle { + int drainCounter; // Tracks callback counts when draining + bool internalDrain; // Indicates if stop is initiated from callback or not. + ASIOBufferInfo *bufferInfos; + HANDLE condition; + + AsioHandle() + :drainCounter(0), internalDrain(false), bufferInfos(0) {} +}; + +// Function declarations (definitions at end of section) +static const char* getAsioErrorString( ASIOError result ); +static void sampleRateChanged( ASIOSampleRate sRate ); +static long asioMessages( long selector, long value, void* message, double* opt ); + +RtApiAsio :: RtApiAsio() +{ + // ASIO cannot run on a multi-threaded appartment. You can call + // CoInitialize beforehand, but it must be for appartment threading + // (in which case, CoInitilialize will return S_FALSE here). + coInitialized_ = false; + HRESULT hr = CoInitialize( NULL ); + if ( FAILED(hr) ) { + errorText_ = "RtApiAsio::ASIO requires a single-threaded appartment. Call CoInitializeEx(0,COINIT_APARTMENTTHREADED)"; + error( RtAudioError::WARNING ); + } + coInitialized_ = true; + + drivers.removeCurrentDriver(); + driverInfo.asioVersion = 2; + + // See note in DirectSound implementation about GetDesktopWindow(). + driverInfo.sysRef = GetForegroundWindow(); +} + +RtApiAsio :: ~RtApiAsio() +{ + if ( stream_.state != STREAM_CLOSED ) closeStream(); + if ( coInitialized_ ) CoUninitialize(); +} + +unsigned int RtApiAsio :: getDeviceCount( void ) +{ + return (unsigned int) drivers.asioGetNumDev(); +} + +RtAudio::DeviceInfo RtApiAsio :: getDeviceInfo( unsigned int device ) +{ + RtAudio::DeviceInfo info; + info.probed = false; + + // Get device ID + unsigned int nDevices = getDeviceCount(); + if ( nDevices == 0 ) { + errorText_ = "RtApiAsio::getDeviceInfo: no devices found!"; + error( RtAudioError::INVALID_USE ); + return info; + } + + if ( device >= nDevices ) { + errorText_ = "RtApiAsio::getDeviceInfo: device ID is invalid!"; + error( RtAudioError::INVALID_USE ); + return info; + } + + // If a stream is already open, we cannot probe other devices. Thus, use the saved results. + if ( stream_.state != STREAM_CLOSED ) { + if ( device >= devices_.size() ) { + errorText_ = "RtApiAsio::getDeviceInfo: device ID was not present before stream was opened."; + error( RtAudioError::WARNING ); + return info; + } + return devices_[ device ]; + } + + char driverName[32]; + ASIOError result = drivers.asioGetDriverName( (int) device, driverName, 32 ); + if ( result != ASE_OK ) { + errorStream_ << "RtApiAsio::getDeviceInfo: unable to get driver name (" << getAsioErrorString( result ) << ")."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + return info; + } + + info.name = driverName; + + if ( !drivers.loadDriver( driverName ) ) { + errorStream_ << "RtApiAsio::getDeviceInfo: unable to load driver (" << driverName << ")."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + return info; + } + + result = ASIOInit( &driverInfo ); + if ( result != ASE_OK ) { + errorStream_ << "RtApiAsio::getDeviceInfo: error (" << getAsioErrorString( result ) << ") initializing driver (" << driverName << ")."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + return info; + } + + // Determine the device channel information. + long inputChannels, outputChannels; + result = ASIOGetChannels( &inputChannels, &outputChannels ); + if ( result != ASE_OK ) { + drivers.removeCurrentDriver(); + errorStream_ << "RtApiAsio::getDeviceInfo: error (" << getAsioErrorString( result ) << ") getting channel count (" << driverName << ")."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + return info; + } + + info.outputChannels = outputChannels; + info.inputChannels = inputChannels; + if ( info.outputChannels > 0 && info.inputChannels > 0 ) + info.duplexChannels = (info.outputChannels > info.inputChannels) ? info.inputChannels : info.outputChannels; + + // Determine the supported sample rates. + info.sampleRates.clear(); + for ( unsigned int i=0; i info.preferredSampleRate ) ) + info.preferredSampleRate = SAMPLE_RATES[i]; + } + } + + // Determine supported data types ... just check first channel and assume rest are the same. + ASIOChannelInfo channelInfo; + channelInfo.channel = 0; + channelInfo.isInput = true; + if ( info.inputChannels <= 0 ) channelInfo.isInput = false; + result = ASIOGetChannelInfo( &channelInfo ); + if ( result != ASE_OK ) { + drivers.removeCurrentDriver(); + errorStream_ << "RtApiAsio::getDeviceInfo: error (" << getAsioErrorString( result ) << ") getting driver channel info (" << driverName << ")."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + return info; + } + + info.nativeFormats = 0; + if ( channelInfo.type == ASIOSTInt16MSB || channelInfo.type == ASIOSTInt16LSB ) + info.nativeFormats |= RTAUDIO_SINT16; + else if ( channelInfo.type == ASIOSTInt32MSB || channelInfo.type == ASIOSTInt32LSB ) + info.nativeFormats |= RTAUDIO_SINT32; + else if ( channelInfo.type == ASIOSTFloat32MSB || channelInfo.type == ASIOSTFloat32LSB ) + info.nativeFormats |= RTAUDIO_FLOAT32; + else if ( channelInfo.type == ASIOSTFloat64MSB || channelInfo.type == ASIOSTFloat64LSB ) + info.nativeFormats |= RTAUDIO_FLOAT64; + else if ( channelInfo.type == ASIOSTInt24MSB || channelInfo.type == ASIOSTInt24LSB ) + info.nativeFormats |= RTAUDIO_SINT24; + + if ( info.outputChannels > 0 ) + if ( getDefaultOutputDevice() == device ) info.isDefaultOutput = true; + if ( info.inputChannels > 0 ) + if ( getDefaultInputDevice() == device ) info.isDefaultInput = true; + + info.probed = true; + drivers.removeCurrentDriver(); + return info; +} + +static void bufferSwitch( long index, ASIOBool /*processNow*/ ) +{ + RtApiAsio *object = (RtApiAsio *) asioCallbackInfo->object; + object->callbackEvent( index ); +} + +void RtApiAsio :: saveDeviceInfo( void ) +{ + devices_.clear(); + + unsigned int nDevices = getDeviceCount(); + devices_.resize( nDevices ); + for ( unsigned int i=0; isaveDeviceInfo(); + + if ( !drivers.loadDriver( driverName ) ) { + errorStream_ << "RtApiAsio::probeDeviceOpen: unable to load driver (" << driverName << ")."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + result = ASIOInit( &driverInfo ); + if ( result != ASE_OK ) { + errorStream_ << "RtApiAsio::probeDeviceOpen: error (" << getAsioErrorString( result ) << ") initializing driver (" << driverName << ")."; + errorText_ = errorStream_.str(); + return FAILURE; + } + } + + // keep them before any "goto error", they are used for error cleanup + goto device boundary checks + bool buffersAllocated = false; + AsioHandle *handle = (AsioHandle *) stream_.apiHandle; + unsigned int nChannels; + + + // Check the device channel count. + long inputChannels, outputChannels; + result = ASIOGetChannels( &inputChannels, &outputChannels ); + if ( result != ASE_OK ) { + errorStream_ << "RtApiAsio::probeDeviceOpen: error (" << getAsioErrorString( result ) << ") getting channel count (" << driverName << ")."; + errorText_ = errorStream_.str(); + goto error; + } + + if ( ( mode == OUTPUT && (channels+firstChannel) > (unsigned int) outputChannels) || + ( mode == INPUT && (channels+firstChannel) > (unsigned int) inputChannels) ) { + errorStream_ << "RtApiAsio::probeDeviceOpen: driver (" << driverName << ") does not support requested channel count (" << channels << ") + offset (" << firstChannel << ")."; + errorText_ = errorStream_.str(); + goto error; + } + stream_.nDeviceChannels[mode] = channels; + stream_.nUserChannels[mode] = channels; + stream_.channelOffset[mode] = firstChannel; + + // Verify the sample rate is supported. + result = ASIOCanSampleRate( (ASIOSampleRate) sampleRate ); + if ( result != ASE_OK ) { + errorStream_ << "RtApiAsio::probeDeviceOpen: driver (" << driverName << ") does not support requested sample rate (" << sampleRate << ")."; + errorText_ = errorStream_.str(); + goto error; + } + + // Get the current sample rate + ASIOSampleRate currentRate; + result = ASIOGetSampleRate( ¤tRate ); + if ( result != ASE_OK ) { + errorStream_ << "RtApiAsio::probeDeviceOpen: driver (" << driverName << ") error getting sample rate."; + errorText_ = errorStream_.str(); + goto error; + } + + // Set the sample rate only if necessary + if ( currentRate != sampleRate ) { + result = ASIOSetSampleRate( (ASIOSampleRate) sampleRate ); + if ( result != ASE_OK ) { + errorStream_ << "RtApiAsio::probeDeviceOpen: driver (" << driverName << ") error setting sample rate (" << sampleRate << ")."; + errorText_ = errorStream_.str(); + goto error; + } + } + + // Determine the driver data type. + ASIOChannelInfo channelInfo; + channelInfo.channel = 0; + if ( mode == OUTPUT ) channelInfo.isInput = false; + else channelInfo.isInput = true; + result = ASIOGetChannelInfo( &channelInfo ); + if ( result != ASE_OK ) { + errorStream_ << "RtApiAsio::probeDeviceOpen: driver (" << driverName << ") error (" << getAsioErrorString( result ) << ") getting data format."; + errorText_ = errorStream_.str(); + goto error; + } + + // Assuming WINDOWS host is always little-endian. + stream_.doByteSwap[mode] = false; + stream_.userFormat = format; + stream_.deviceFormat[mode] = 0; + if ( channelInfo.type == ASIOSTInt16MSB || channelInfo.type == ASIOSTInt16LSB ) { + stream_.deviceFormat[mode] = RTAUDIO_SINT16; + if ( channelInfo.type == ASIOSTInt16MSB ) stream_.doByteSwap[mode] = true; + } + else if ( channelInfo.type == ASIOSTInt32MSB || channelInfo.type == ASIOSTInt32LSB ) { + stream_.deviceFormat[mode] = RTAUDIO_SINT32; + if ( channelInfo.type == ASIOSTInt32MSB ) stream_.doByteSwap[mode] = true; + } + else if ( channelInfo.type == ASIOSTFloat32MSB || channelInfo.type == ASIOSTFloat32LSB ) { + stream_.deviceFormat[mode] = RTAUDIO_FLOAT32; + if ( channelInfo.type == ASIOSTFloat32MSB ) stream_.doByteSwap[mode] = true; + } + else if ( channelInfo.type == ASIOSTFloat64MSB || channelInfo.type == ASIOSTFloat64LSB ) { + stream_.deviceFormat[mode] = RTAUDIO_FLOAT64; + if ( channelInfo.type == ASIOSTFloat64MSB ) stream_.doByteSwap[mode] = true; + } + else if ( channelInfo.type == ASIOSTInt24MSB || channelInfo.type == ASIOSTInt24LSB ) { + stream_.deviceFormat[mode] = RTAUDIO_SINT24; + if ( channelInfo.type == ASIOSTInt24MSB ) stream_.doByteSwap[mode] = true; + } + + if ( stream_.deviceFormat[mode] == 0 ) { + errorStream_ << "RtApiAsio::probeDeviceOpen: driver (" << driverName << ") data format not supported by RtAudio."; + errorText_ = errorStream_.str(); + goto error; + } + + // Set the buffer size. For a duplex stream, this will end up + // setting the buffer size based on the input constraints, which + // should be ok. + long minSize, maxSize, preferSize, granularity; + result = ASIOGetBufferSize( &minSize, &maxSize, &preferSize, &granularity ); + if ( result != ASE_OK ) { + errorStream_ << "RtApiAsio::probeDeviceOpen: driver (" << driverName << ") error (" << getAsioErrorString( result ) << ") getting buffer size."; + errorText_ = errorStream_.str(); + goto error; + } + + if ( isDuplexInput ) { + // When this is the duplex input (output was opened before), then we have to use the same + // buffersize as the output, because it might use the preferred buffer size, which most + // likely wasn't passed as input to this. The buffer sizes have to be identically anyway, + // So instead of throwing an error, make them equal. The caller uses the reference + // to the "bufferSize" param as usual to set up processing buffers. + + *bufferSize = stream_.bufferSize; + + } else { + if ( *bufferSize == 0 ) *bufferSize = preferSize; + else if ( *bufferSize < (unsigned int) minSize ) *bufferSize = (unsigned int) minSize; + else if ( *bufferSize > (unsigned int) maxSize ) *bufferSize = (unsigned int) maxSize; + else if ( granularity == -1 ) { + // Make sure bufferSize is a power of two. + int log2_of_min_size = 0; + int log2_of_max_size = 0; + + for ( unsigned int i = 0; i < sizeof(long) * 8; i++ ) { + if ( minSize & ((long)1 << i) ) log2_of_min_size = i; + if ( maxSize & ((long)1 << i) ) log2_of_max_size = i; + } + + long min_delta = std::abs( (long)*bufferSize - ((long)1 << log2_of_min_size) ); + int min_delta_num = log2_of_min_size; + + for (int i = log2_of_min_size + 1; i <= log2_of_max_size; i++) { + long current_delta = std::abs( (long)*bufferSize - ((long)1 << i) ); + if (current_delta < min_delta) { + min_delta = current_delta; + min_delta_num = i; + } + } + + *bufferSize = ( (unsigned int)1 << min_delta_num ); + if ( *bufferSize < (unsigned int) minSize ) *bufferSize = (unsigned int) minSize; + else if ( *bufferSize > (unsigned int) maxSize ) *bufferSize = (unsigned int) maxSize; + } + else if ( granularity != 0 ) { + // Set to an even multiple of granularity, rounding up. + *bufferSize = (*bufferSize + granularity-1) / granularity * granularity; + } + } + + /* + // we don't use it anymore, see above! + // Just left it here for the case... + if ( isDuplexInput && stream_.bufferSize != *bufferSize ) { + errorText_ = "RtApiAsio::probeDeviceOpen: input/output buffersize discrepancy!"; + goto error; + } + */ + + stream_.bufferSize = *bufferSize; + stream_.nBuffers = 2; + + if ( options && options->flags & RTAUDIO_NONINTERLEAVED ) stream_.userInterleaved = false; + else stream_.userInterleaved = true; + + // ASIO always uses non-interleaved buffers. + stream_.deviceInterleaved[mode] = false; + + // Allocate, if necessary, our AsioHandle structure for the stream. + if ( handle == 0 ) { + try { + handle = new AsioHandle; + } + catch ( std::bad_alloc& ) { + errorText_ = "RtApiAsio::probeDeviceOpen: error allocating AsioHandle memory."; + goto error; + } + handle->bufferInfos = 0; + + // Create a manual-reset event. + handle->condition = CreateEvent( NULL, // no security + TRUE, // manual-reset + FALSE, // non-signaled initially + NULL ); // unnamed + stream_.apiHandle = (void *) handle; + } + + // Create the ASIO internal buffers. Since RtAudio sets up input + // and output separately, we'll have to dispose of previously + // created output buffers for a duplex stream. + if ( mode == INPUT && stream_.mode == OUTPUT ) { + ASIODisposeBuffers(); + if ( handle->bufferInfos ) free( handle->bufferInfos ); + } + + // Allocate, initialize, and save the bufferInfos in our stream callbackInfo structure. + unsigned int i; + nChannels = stream_.nDeviceChannels[0] + stream_.nDeviceChannels[1]; + handle->bufferInfos = (ASIOBufferInfo *) malloc( nChannels * sizeof(ASIOBufferInfo) ); + if ( handle->bufferInfos == NULL ) { + errorStream_ << "RtApiAsio::probeDeviceOpen: error allocating bufferInfo memory for driver (" << driverName << ")."; + errorText_ = errorStream_.str(); + goto error; + } + + ASIOBufferInfo *infos; + infos = handle->bufferInfos; + for ( i=0; iisInput = ASIOFalse; + infos->channelNum = i + stream_.channelOffset[0]; + infos->buffers[0] = infos->buffers[1] = 0; + } + for ( i=0; iisInput = ASIOTrue; + infos->channelNum = i + stream_.channelOffset[1]; + infos->buffers[0] = infos->buffers[1] = 0; + } + + // prepare for callbacks + stream_.sampleRate = sampleRate; + stream_.device[mode] = device; + stream_.mode = isDuplexInput ? DUPLEX : mode; + + // store this class instance before registering callbacks, that are going to use it + asioCallbackInfo = &stream_.callbackInfo; + stream_.callbackInfo.object = (void *) this; + + // Set up the ASIO callback structure and create the ASIO data buffers. + asioCallbacks.bufferSwitch = &bufferSwitch; + asioCallbacks.sampleRateDidChange = &sampleRateChanged; + asioCallbacks.asioMessage = &asioMessages; + asioCallbacks.bufferSwitchTimeInfo = NULL; + result = ASIOCreateBuffers( handle->bufferInfos, nChannels, stream_.bufferSize, &asioCallbacks ); + if ( result != ASE_OK ) { + // Standard method failed. This can happen with strict/misbehaving drivers that return valid buffer size ranges + // but only accept the preferred buffer size as parameter for ASIOCreateBuffers. eg. Creatives ASIO driver + // in that case, let's be naïve and try that instead + *bufferSize = preferSize; + stream_.bufferSize = *bufferSize; + result = ASIOCreateBuffers( handle->bufferInfos, nChannels, stream_.bufferSize, &asioCallbacks ); + } + + if ( result != ASE_OK ) { + errorStream_ << "RtApiAsio::probeDeviceOpen: driver (" << driverName << ") error (" << getAsioErrorString( result ) << ") creating buffers."; + errorText_ = errorStream_.str(); + goto error; + } + buffersAllocated = true; + stream_.state = STREAM_STOPPED; + + // Set flags for buffer conversion. + stream_.doConvertBuffer[mode] = false; + if ( stream_.userFormat != stream_.deviceFormat[mode] ) + stream_.doConvertBuffer[mode] = true; + if ( stream_.userInterleaved != stream_.deviceInterleaved[mode] && + stream_.nUserChannels[mode] > 1 ) + stream_.doConvertBuffer[mode] = true; + + // Allocate necessary internal buffers + unsigned long bufferBytes; + bufferBytes = stream_.nUserChannels[mode] * *bufferSize * formatBytes( stream_.userFormat ); + stream_.userBuffer[mode] = (char *) calloc( bufferBytes, 1 ); + if ( stream_.userBuffer[mode] == NULL ) { + errorText_ = "RtApiAsio::probeDeviceOpen: error allocating user buffer memory."; + goto error; + } + + if ( stream_.doConvertBuffer[mode] ) { + + bool makeBuffer = true; + bufferBytes = stream_.nDeviceChannels[mode] * formatBytes( stream_.deviceFormat[mode] ); + if ( isDuplexInput && stream_.deviceBuffer ) { + unsigned long bytesOut = stream_.nDeviceChannels[0] * formatBytes( stream_.deviceFormat[0] ); + if ( bufferBytes <= bytesOut ) makeBuffer = false; + } + + if ( makeBuffer ) { + bufferBytes *= *bufferSize; + if ( stream_.deviceBuffer ) free( stream_.deviceBuffer ); + stream_.deviceBuffer = (char *) calloc( bufferBytes, 1 ); + if ( stream_.deviceBuffer == NULL ) { + errorText_ = "RtApiAsio::probeDeviceOpen: error allocating device buffer memory."; + goto error; + } + } + } + + // Determine device latencies + long inputLatency, outputLatency; + result = ASIOGetLatencies( &inputLatency, &outputLatency ); + if ( result != ASE_OK ) { + errorStream_ << "RtApiAsio::probeDeviceOpen: driver (" << driverName << ") error (" << getAsioErrorString( result ) << ") getting latency."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING); // warn but don't fail + } + else { + stream_.latency[0] = outputLatency; + stream_.latency[1] = inputLatency; + } + + // Setup the buffer conversion information structure. We don't use + // buffers to do channel offsets, so we override that parameter + // here. + if ( stream_.doConvertBuffer[mode] ) setConvertInfo( mode, 0 ); + + return SUCCESS; + + error: + if ( !isDuplexInput ) { + // the cleanup for error in the duplex input, is done by RtApi::openStream + // So we clean up for single channel only + + if ( buffersAllocated ) + ASIODisposeBuffers(); + + drivers.removeCurrentDriver(); + + if ( handle ) { + CloseHandle( handle->condition ); + if ( handle->bufferInfos ) + free( handle->bufferInfos ); + + delete handle; + stream_.apiHandle = 0; + } + + + if ( stream_.userBuffer[mode] ) { + free( stream_.userBuffer[mode] ); + stream_.userBuffer[mode] = 0; + } + + if ( stream_.deviceBuffer ) { + free( stream_.deviceBuffer ); + stream_.deviceBuffer = 0; + } + } + + return FAILURE; +}//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +void RtApiAsio :: closeStream() +{ + if ( stream_.state == STREAM_CLOSED ) { + errorText_ = "RtApiAsio::closeStream(): no open stream to close!"; + error( RtAudioError::WARNING ); + return; + } + + if ( stream_.state == STREAM_RUNNING ) { + stream_.state = STREAM_STOPPED; + ASIOStop(); + } + ASIODisposeBuffers(); + drivers.removeCurrentDriver(); + + AsioHandle *handle = (AsioHandle *) stream_.apiHandle; + if ( handle ) { + CloseHandle( handle->condition ); + if ( handle->bufferInfos ) + free( handle->bufferInfos ); + delete handle; + stream_.apiHandle = 0; + } + + for ( int i=0; i<2; i++ ) { + if ( stream_.userBuffer[i] ) { + free( stream_.userBuffer[i] ); + stream_.userBuffer[i] = 0; + } + } + + if ( stream_.deviceBuffer ) { + free( stream_.deviceBuffer ); + stream_.deviceBuffer = 0; + } + + stream_.mode = UNINITIALIZED; + stream_.state = STREAM_CLOSED; +} + +bool stopThreadCalled = false; + +void RtApiAsio :: startStream() +{ + verifyStream(); + if ( stream_.state == STREAM_RUNNING ) { + errorText_ = "RtApiAsio::startStream(): the stream is already running!"; + error( RtAudioError::WARNING ); + return; + } + + AsioHandle *handle = (AsioHandle *) stream_.apiHandle; + ASIOError result = ASIOStart(); + if ( result != ASE_OK ) { + errorStream_ << "RtApiAsio::startStream: error (" << getAsioErrorString( result ) << ") starting device."; + errorText_ = errorStream_.str(); + goto unlock; + } + + handle->drainCounter = 0; + handle->internalDrain = false; + ResetEvent( handle->condition ); + stream_.state = STREAM_RUNNING; + asioXRun = false; + + unlock: + stopThreadCalled = false; + + if ( result == ASE_OK ) return; + error( RtAudioError::SYSTEM_ERROR ); +} + +void RtApiAsio :: stopStream() +{ + verifyStream(); + if ( stream_.state == STREAM_STOPPED ) { + errorText_ = "RtApiAsio::stopStream(): the stream is already stopped!"; + error( RtAudioError::WARNING ); + return; + } + + AsioHandle *handle = (AsioHandle *) stream_.apiHandle; + if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { + if ( handle->drainCounter == 0 ) { + handle->drainCounter = 2; + WaitForSingleObject( handle->condition, INFINITE ); // block until signaled + } + } + + stream_.state = STREAM_STOPPED; + + ASIOError result = ASIOStop(); + if ( result != ASE_OK ) { + errorStream_ << "RtApiAsio::stopStream: error (" << getAsioErrorString( result ) << ") stopping device."; + errorText_ = errorStream_.str(); + } + + if ( result == ASE_OK ) return; + error( RtAudioError::SYSTEM_ERROR ); +} + +void RtApiAsio :: abortStream() +{ + verifyStream(); + if ( stream_.state == STREAM_STOPPED ) { + errorText_ = "RtApiAsio::abortStream(): the stream is already stopped!"; + error( RtAudioError::WARNING ); + return; + } + + // The following lines were commented-out because some behavior was + // noted where the device buffers need to be zeroed to avoid + // continuing sound, even when the device buffers are completely + // disposed. So now, calling abort is the same as calling stop. + // AsioHandle *handle = (AsioHandle *) stream_.apiHandle; + // handle->drainCounter = 2; + stopStream(); +} + +// This function will be called by a spawned thread when the user +// callback function signals that the stream should be stopped or +// aborted. It is necessary to handle it this way because the +// callbackEvent() function must return before the ASIOStop() +// function will return. +static unsigned __stdcall asioStopStream( void *ptr ) +{ + CallbackInfo *info = (CallbackInfo *) ptr; + RtApiAsio *object = (RtApiAsio *) info->object; + + object->stopStream(); + _endthreadex( 0 ); + return 0; +} + +bool RtApiAsio :: callbackEvent( long bufferIndex ) +{ + if ( stream_.state == STREAM_STOPPED || stream_.state == STREAM_STOPPING ) return SUCCESS; + if ( stream_.state == STREAM_CLOSED ) { + errorText_ = "RtApiAsio::callbackEvent(): the stream is closed ... this shouldn't happen!"; + error( RtAudioError::WARNING ); + return FAILURE; + } + + CallbackInfo *info = (CallbackInfo *) &stream_.callbackInfo; + AsioHandle *handle = (AsioHandle *) stream_.apiHandle; + + // Check if we were draining the stream and signal if finished. + if ( handle->drainCounter > 3 ) { + + stream_.state = STREAM_STOPPING; + if ( handle->internalDrain == false ) + SetEvent( handle->condition ); + else { // spawn a thread to stop the stream + unsigned threadId; + stream_.callbackInfo.thread = _beginthreadex( NULL, 0, &asioStopStream, + &stream_.callbackInfo, 0, &threadId ); + } + return SUCCESS; + } + + // Invoke user callback to get fresh output data UNLESS we are + // draining stream. + if ( handle->drainCounter == 0 ) { + RtAudioCallback callback = (RtAudioCallback) info->callback; + double streamTime = getStreamTime(); + RtAudioStreamStatus status = 0; + if ( stream_.mode != INPUT && asioXRun == true ) { + status |= RTAUDIO_OUTPUT_UNDERFLOW; + asioXRun = false; + } + if ( stream_.mode != OUTPUT && asioXRun == true ) { + status |= RTAUDIO_INPUT_OVERFLOW; + asioXRun = false; + } + int cbReturnValue = callback( stream_.userBuffer[0], stream_.userBuffer[1], + stream_.bufferSize, streamTime, status, info->userData ); + if ( cbReturnValue == 2 ) { + stream_.state = STREAM_STOPPING; + handle->drainCounter = 2; + unsigned threadId; + stream_.callbackInfo.thread = _beginthreadex( NULL, 0, &asioStopStream, + &stream_.callbackInfo, 0, &threadId ); + return SUCCESS; + } + else if ( cbReturnValue == 1 ) { + handle->drainCounter = 1; + handle->internalDrain = true; + } + } + + unsigned int nChannels, bufferBytes, i, j; + nChannels = stream_.nDeviceChannels[0] + stream_.nDeviceChannels[1]; + if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { + + bufferBytes = stream_.bufferSize * formatBytes( stream_.deviceFormat[0] ); + + if ( handle->drainCounter > 1 ) { // write zeros to the output stream + + for ( i=0, j=0; ibufferInfos[i].isInput != ASIOTrue ) + memset( handle->bufferInfos[i].buffers[bufferIndex], 0, bufferBytes ); + } + + } + else if ( stream_.doConvertBuffer[0] ) { + + convertBuffer( stream_.deviceBuffer, stream_.userBuffer[0], stream_.convertInfo[0] ); + if ( stream_.doByteSwap[0] ) + byteSwapBuffer( stream_.deviceBuffer, + stream_.bufferSize * stream_.nDeviceChannels[0], + stream_.deviceFormat[0] ); + + for ( i=0, j=0; ibufferInfos[i].isInput != ASIOTrue ) + memcpy( handle->bufferInfos[i].buffers[bufferIndex], + &stream_.deviceBuffer[j++*bufferBytes], bufferBytes ); + } + + } + else { + + if ( stream_.doByteSwap[0] ) + byteSwapBuffer( stream_.userBuffer[0], + stream_.bufferSize * stream_.nUserChannels[0], + stream_.userFormat ); + + for ( i=0, j=0; ibufferInfos[i].isInput != ASIOTrue ) + memcpy( handle->bufferInfos[i].buffers[bufferIndex], + &stream_.userBuffer[0][bufferBytes*j++], bufferBytes ); + } + + } + } + + // Don't bother draining input + if ( handle->drainCounter ) { + handle->drainCounter++; + goto unlock; + } + + if ( stream_.mode == INPUT || stream_.mode == DUPLEX ) { + + bufferBytes = stream_.bufferSize * formatBytes(stream_.deviceFormat[1]); + + if (stream_.doConvertBuffer[1]) { + + // Always interleave ASIO input data. + for ( i=0, j=0; ibufferInfos[i].isInput == ASIOTrue ) + memcpy( &stream_.deviceBuffer[j++*bufferBytes], + handle->bufferInfos[i].buffers[bufferIndex], + bufferBytes ); + } + + if ( stream_.doByteSwap[1] ) + byteSwapBuffer( stream_.deviceBuffer, + stream_.bufferSize * stream_.nDeviceChannels[1], + stream_.deviceFormat[1] ); + convertBuffer( stream_.userBuffer[1], stream_.deviceBuffer, stream_.convertInfo[1] ); + + } + else { + for ( i=0, j=0; ibufferInfos[i].isInput == ASIOTrue ) { + memcpy( &stream_.userBuffer[1][bufferBytes*j++], + handle->bufferInfos[i].buffers[bufferIndex], + bufferBytes ); + } + } + + if ( stream_.doByteSwap[1] ) + byteSwapBuffer( stream_.userBuffer[1], + stream_.bufferSize * stream_.nUserChannels[1], + stream_.userFormat ); + } + } + + unlock: + // The following call was suggested by Malte Clasen. While the API + // documentation indicates it should not be required, some device + // drivers apparently do not function correctly without it. + ASIOOutputReady(); + + RtApi::tickStreamTime(); + return SUCCESS; +} + +static void sampleRateChanged( ASIOSampleRate sRate ) +{ + // The ASIO documentation says that this usually only happens during + // external sync. Audio processing is not stopped by the driver, + // actual sample rate might not have even changed, maybe only the + // sample rate status of an AES/EBU or S/PDIF digital input at the + // audio device. + + RtApi *object = (RtApi *) asioCallbackInfo->object; + try { + object->stopStream(); + } + catch ( RtAudioError &exception ) { + std::cerr << "\nRtApiAsio: sampleRateChanged() error (" << exception.getMessage() << ")!\n" << std::endl; + return; + } + + std::cerr << "\nRtApiAsio: driver reports sample rate changed to " << sRate << " ... stream stopped!!!\n" << std::endl; +} + +static long asioMessages( long selector, long value, void* /*message*/, double* /*opt*/ ) +{ + long ret = 0; + + switch( selector ) { + case kAsioSelectorSupported: + if ( value == kAsioResetRequest + || value == kAsioEngineVersion + || value == kAsioResyncRequest + || value == kAsioLatenciesChanged + // The following three were added for ASIO 2.0, you don't + // necessarily have to support them. + || value == kAsioSupportsTimeInfo + || value == kAsioSupportsTimeCode + || value == kAsioSupportsInputMonitor) + ret = 1L; + break; + case kAsioResetRequest: + // Defer the task and perform the reset of the driver during the + // next "safe" situation. You cannot reset the driver right now, + // as this code is called from the driver. Reset the driver is + // done by completely destruct is. I.e. ASIOStop(), + // ASIODisposeBuffers(), Destruction Afterwards you initialize the + // driver again. + std::cerr << "\nRtApiAsio: driver reset requested!!!" << std::endl; + ret = 1L; + break; + case kAsioResyncRequest: + // This informs the application that the driver encountered some + // non-fatal data loss. It is used for synchronization purposes + // of different media. Added mainly to work around the Win16Mutex + // problems in Windows 95/98 with the Windows Multimedia system, + // which could lose data because the Mutex was held too long by + // another thread. However a driver can issue it in other + // situations, too. + // std::cerr << "\nRtApiAsio: driver resync requested!!!" << std::endl; + asioXRun = true; + ret = 1L; + break; + case kAsioLatenciesChanged: + // This will inform the host application that the drivers were + // latencies changed. Beware, it this does not mean that the + // buffer sizes have changed! You might need to update internal + // delay data. + std::cerr << "\nRtApiAsio: driver latency may have changed!!!" << std::endl; + ret = 1L; + break; + case kAsioEngineVersion: + // Return the supported ASIO version of the host application. If + // a host application does not implement this selector, ASIO 1.0 + // is assumed by the driver. + ret = 2L; + break; + case kAsioSupportsTimeInfo: + // Informs the driver whether the + // asioCallbacks.bufferSwitchTimeInfo() callback is supported. + // For compatibility with ASIO 1.0 drivers the host application + // should always support the "old" bufferSwitch method, too. + ret = 0; + break; + case kAsioSupportsTimeCode: + // Informs the driver whether application is interested in time + // code info. If an application does not need to know about time + // code, the driver has less work to do. + ret = 0; + break; + } + return ret; +} + +static const char* getAsioErrorString( ASIOError result ) +{ + struct Messages + { + ASIOError value; + const char*message; + }; + + static const Messages m[] = + { + { ASE_NotPresent, "Hardware input or output is not present or available." }, + { ASE_HWMalfunction, "Hardware is malfunctioning." }, + { ASE_InvalidParameter, "Invalid input parameter." }, + { ASE_InvalidMode, "Invalid mode." }, + { ASE_SPNotAdvancing, "Sample position not advancing." }, + { ASE_NoClock, "Sample clock or rate cannot be determined or is not present." }, + { ASE_NoMemory, "Not enough memory to complete the request." } + }; + + for ( unsigned int i = 0; i < sizeof(m)/sizeof(m[0]); ++i ) + if ( m[i].value == result ) return m[i].message; + + return "Unknown error."; +} + +//******************** End of __WINDOWS_ASIO__ *********************// +#endif + + +#if defined(__WINDOWS_WASAPI__) // Windows WASAPI API + +// Authored by Marcus Tomlinson , April 2014 +// - Introduces support for the Windows WASAPI API +// - Aims to deliver bit streams to and from hardware at the lowest possible latency, via the absolute minimum buffer sizes required +// - Provides flexible stream configuration to an otherwise strict and inflexible WASAPI interface +// - Includes automatic internal conversion of sample rate and buffer size between hardware and the user + +#ifndef INITGUID + #define INITGUID +#endif +#include +#include +#include +#include + +//============================================================================= + +#define SAFE_RELEASE( objectPtr )\ +if ( objectPtr )\ +{\ + objectPtr->Release();\ + objectPtr = NULL;\ +} + +typedef HANDLE ( __stdcall *TAvSetMmThreadCharacteristicsPtr )( LPCWSTR TaskName, LPDWORD TaskIndex ); + +//----------------------------------------------------------------------------- + +// WASAPI dictates stream sample rate, format, channel count, and in some cases, buffer size. +// Therefore we must perform all necessary conversions to user buffers in order to satisfy these +// requirements. WasapiBuffer ring buffers are used between HwIn->UserIn and UserOut->HwOut to +// provide intermediate storage for read / write synchronization. +class WasapiBuffer +{ +public: + WasapiBuffer() + : buffer_( NULL ), + bufferSize_( 0 ), + inIndex_( 0 ), + outIndex_( 0 ) {} + + ~WasapiBuffer() { + free( buffer_ ); + } + + // sets the length of the internal ring buffer + void setBufferSize( unsigned int bufferSize, unsigned int formatBytes ) { + free( buffer_ ); + + buffer_ = ( char* ) calloc( bufferSize, formatBytes ); + + bufferSize_ = bufferSize; + inIndex_ = 0; + outIndex_ = 0; + } + + // attempt to push a buffer into the ring buffer at the current "in" index + bool pushBuffer( char* buffer, unsigned int bufferSize, RtAudioFormat format ) + { + if ( !buffer || // incoming buffer is NULL + bufferSize == 0 || // incoming buffer has no data + bufferSize > bufferSize_ ) // incoming buffer too large + { + return false; + } + + unsigned int relOutIndex = outIndex_; + unsigned int inIndexEnd = inIndex_ + bufferSize; + if ( relOutIndex < inIndex_ && inIndexEnd >= bufferSize_ ) { + relOutIndex += bufferSize_; + } + + // "in" index can end on the "out" index but cannot begin at it + if ( inIndex_ <= relOutIndex && inIndexEnd > relOutIndex ) { + return false; // not enough space between "in" index and "out" index + } + + // copy buffer from external to internal + int fromZeroSize = inIndex_ + bufferSize - bufferSize_; + fromZeroSize = fromZeroSize < 0 ? 0 : fromZeroSize; + int fromInSize = bufferSize - fromZeroSize; + + switch( format ) + { + case RTAUDIO_SINT8: + memcpy( &( ( char* ) buffer_ )[inIndex_], buffer, fromInSize * sizeof( char ) ); + memcpy( buffer_, &( ( char* ) buffer )[fromInSize], fromZeroSize * sizeof( char ) ); + break; + case RTAUDIO_SINT16: + memcpy( &( ( short* ) buffer_ )[inIndex_], buffer, fromInSize * sizeof( short ) ); + memcpy( buffer_, &( ( short* ) buffer )[fromInSize], fromZeroSize * sizeof( short ) ); + break; + case RTAUDIO_SINT24: + memcpy( &( ( S24* ) buffer_ )[inIndex_], buffer, fromInSize * sizeof( S24 ) ); + memcpy( buffer_, &( ( S24* ) buffer )[fromInSize], fromZeroSize * sizeof( S24 ) ); + break; + case RTAUDIO_SINT32: + memcpy( &( ( int* ) buffer_ )[inIndex_], buffer, fromInSize * sizeof( int ) ); + memcpy( buffer_, &( ( int* ) buffer )[fromInSize], fromZeroSize * sizeof( int ) ); + break; + case RTAUDIO_FLOAT32: + memcpy( &( ( float* ) buffer_ )[inIndex_], buffer, fromInSize * sizeof( float ) ); + memcpy( buffer_, &( ( float* ) buffer )[fromInSize], fromZeroSize * sizeof( float ) ); + break; + case RTAUDIO_FLOAT64: + memcpy( &( ( double* ) buffer_ )[inIndex_], buffer, fromInSize * sizeof( double ) ); + memcpy( buffer_, &( ( double* ) buffer )[fromInSize], fromZeroSize * sizeof( double ) ); + break; + } + + // update "in" index + inIndex_ += bufferSize; + inIndex_ %= bufferSize_; + + return true; + } + + // attempt to pull a buffer from the ring buffer from the current "out" index + bool pullBuffer( char* buffer, unsigned int bufferSize, RtAudioFormat format ) + { + if ( !buffer || // incoming buffer is NULL + bufferSize == 0 || // incoming buffer has no data + bufferSize > bufferSize_ ) // incoming buffer too large + { + return false; + } + + unsigned int relInIndex = inIndex_; + unsigned int outIndexEnd = outIndex_ + bufferSize; + if ( relInIndex < outIndex_ && outIndexEnd >= bufferSize_ ) { + relInIndex += bufferSize_; + } + + // "out" index can begin at and end on the "in" index + if ( outIndex_ < relInIndex && outIndexEnd > relInIndex ) { + return false; // not enough space between "out" index and "in" index + } + + // copy buffer from internal to external + int fromZeroSize = outIndex_ + bufferSize - bufferSize_; + fromZeroSize = fromZeroSize < 0 ? 0 : fromZeroSize; + int fromOutSize = bufferSize - fromZeroSize; + + switch( format ) + { + case RTAUDIO_SINT8: + memcpy( buffer, &( ( char* ) buffer_ )[outIndex_], fromOutSize * sizeof( char ) ); + memcpy( &( ( char* ) buffer )[fromOutSize], buffer_, fromZeroSize * sizeof( char ) ); + break; + case RTAUDIO_SINT16: + memcpy( buffer, &( ( short* ) buffer_ )[outIndex_], fromOutSize * sizeof( short ) ); + memcpy( &( ( short* ) buffer )[fromOutSize], buffer_, fromZeroSize * sizeof( short ) ); + break; + case RTAUDIO_SINT24: + memcpy( buffer, &( ( S24* ) buffer_ )[outIndex_], fromOutSize * sizeof( S24 ) ); + memcpy( &( ( S24* ) buffer )[fromOutSize], buffer_, fromZeroSize * sizeof( S24 ) ); + break; + case RTAUDIO_SINT32: + memcpy( buffer, &( ( int* ) buffer_ )[outIndex_], fromOutSize * sizeof( int ) ); + memcpy( &( ( int* ) buffer )[fromOutSize], buffer_, fromZeroSize * sizeof( int ) ); + break; + case RTAUDIO_FLOAT32: + memcpy( buffer, &( ( float* ) buffer_ )[outIndex_], fromOutSize * sizeof( float ) ); + memcpy( &( ( float* ) buffer )[fromOutSize], buffer_, fromZeroSize * sizeof( float ) ); + break; + case RTAUDIO_FLOAT64: + memcpy( buffer, &( ( double* ) buffer_ )[outIndex_], fromOutSize * sizeof( double ) ); + memcpy( &( ( double* ) buffer )[fromOutSize], buffer_, fromZeroSize * sizeof( double ) ); + break; + } + + // update "out" index + outIndex_ += bufferSize; + outIndex_ %= bufferSize_; + + return true; + } + +private: + char* buffer_; + unsigned int bufferSize_; + unsigned int inIndex_; + unsigned int outIndex_; +}; + +//----------------------------------------------------------------------------- + +// In order to satisfy WASAPI's buffer requirements, we need a means of converting sample rate +// between HW and the user. The convertBufferWasapi function is used to perform this conversion +// between HwIn->UserIn and UserOut->HwOut during the stream callback loop. +// This sample rate converter favors speed over quality, and works best with conversions between +// one rate and its multiple. +void convertBufferWasapi( char* outBuffer, + const char* inBuffer, + const unsigned int& channelCount, + const unsigned int& inSampleRate, + const unsigned int& outSampleRate, + const unsigned int& inSampleCount, + unsigned int& outSampleCount, + const RtAudioFormat& format ) +{ + // calculate the new outSampleCount and relative sampleStep + float sampleRatio = ( float ) outSampleRate / inSampleRate; + float sampleStep = 1.0f / sampleRatio; + float inSampleFraction = 0.0f; + + outSampleCount = ( unsigned int ) roundf( inSampleCount * sampleRatio ); + + // frame-by-frame, copy each relative input sample into it's corresponding output sample + for ( unsigned int outSample = 0; outSample < outSampleCount; outSample++ ) + { + unsigned int inSample = ( unsigned int ) inSampleFraction; + + switch ( format ) + { + case RTAUDIO_SINT8: + memcpy( &( ( char* ) outBuffer )[ outSample * channelCount ], &( ( char* ) inBuffer )[ inSample * channelCount ], channelCount * sizeof( char ) ); + break; + case RTAUDIO_SINT16: + memcpy( &( ( short* ) outBuffer )[ outSample * channelCount ], &( ( short* ) inBuffer )[ inSample * channelCount ], channelCount * sizeof( short ) ); + break; + case RTAUDIO_SINT24: + memcpy( &( ( S24* ) outBuffer )[ outSample * channelCount ], &( ( S24* ) inBuffer )[ inSample * channelCount ], channelCount * sizeof( S24 ) ); + break; + case RTAUDIO_SINT32: + memcpy( &( ( int* ) outBuffer )[ outSample * channelCount ], &( ( int* ) inBuffer )[ inSample * channelCount ], channelCount * sizeof( int ) ); + break; + case RTAUDIO_FLOAT32: + memcpy( &( ( float* ) outBuffer )[ outSample * channelCount ], &( ( float* ) inBuffer )[ inSample * channelCount ], channelCount * sizeof( float ) ); + break; + case RTAUDIO_FLOAT64: + memcpy( &( ( double* ) outBuffer )[ outSample * channelCount ], &( ( double* ) inBuffer )[ inSample * channelCount ], channelCount * sizeof( double ) ); + break; + } + + // jump to next in sample + inSampleFraction += sampleStep; + } +} + +//----------------------------------------------------------------------------- + +// A structure to hold various information related to the WASAPI implementation. +struct WasapiHandle +{ + IAudioClient* captureAudioClient; + IAudioClient* renderAudioClient; + IAudioCaptureClient* captureClient; + IAudioRenderClient* renderClient; + HANDLE captureEvent; + HANDLE renderEvent; + + WasapiHandle() + : captureAudioClient( NULL ), + renderAudioClient( NULL ), + captureClient( NULL ), + renderClient( NULL ), + captureEvent( NULL ), + renderEvent( NULL ) {} +}; + +//============================================================================= + +RtApiWasapi::RtApiWasapi() + : coInitialized_( false ), deviceEnumerator_( NULL ) +{ + // WASAPI can run either apartment or multi-threaded + HRESULT hr = CoInitialize( NULL ); + if ( !FAILED( hr ) ) + coInitialized_ = true; + + // Instantiate device enumerator + hr = CoCreateInstance( __uuidof( MMDeviceEnumerator ), NULL, + CLSCTX_ALL, __uuidof( IMMDeviceEnumerator ), + ( void** ) &deviceEnumerator_ ); + + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::RtApiWasapi: Unable to instantiate device enumerator"; + error( RtAudioError::DRIVER_ERROR ); + } +} + +//----------------------------------------------------------------------------- + +RtApiWasapi::~RtApiWasapi() +{ + if ( stream_.state != STREAM_CLOSED ) + closeStream(); + + SAFE_RELEASE( deviceEnumerator_ ); + + // If this object previously called CoInitialize() + if ( coInitialized_ ) + CoUninitialize(); +} + +//============================================================================= + +unsigned int RtApiWasapi::getDeviceCount( void ) +{ + unsigned int captureDeviceCount = 0; + unsigned int renderDeviceCount = 0; + + IMMDeviceCollection* captureDevices = NULL; + IMMDeviceCollection* renderDevices = NULL; + + // Count capture devices + errorText_.clear(); + HRESULT hr = deviceEnumerator_->EnumAudioEndpoints( eCapture, DEVICE_STATE_ACTIVE, &captureDevices ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::getDeviceCount: Unable to retrieve capture device collection."; + goto Exit; + } + + hr = captureDevices->GetCount( &captureDeviceCount ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::getDeviceCount: Unable to retrieve capture device count."; + goto Exit; + } + + // Count render devices + hr = deviceEnumerator_->EnumAudioEndpoints( eRender, DEVICE_STATE_ACTIVE, &renderDevices ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::getDeviceCount: Unable to retrieve render device collection."; + goto Exit; + } + + hr = renderDevices->GetCount( &renderDeviceCount ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::getDeviceCount: Unable to retrieve render device count."; + goto Exit; + } + +Exit: + // release all references + SAFE_RELEASE( captureDevices ); + SAFE_RELEASE( renderDevices ); + + if ( errorText_.empty() ) + return captureDeviceCount + renderDeviceCount; + + error( RtAudioError::DRIVER_ERROR ); + return 0; +} + +//----------------------------------------------------------------------------- + +RtAudio::DeviceInfo RtApiWasapi::getDeviceInfo( unsigned int device ) +{ + RtAudio::DeviceInfo info; + unsigned int captureDeviceCount = 0; + unsigned int renderDeviceCount = 0; + std::string defaultDeviceName; + bool isCaptureDevice = false; + + PROPVARIANT deviceNameProp; + PROPVARIANT defaultDeviceNameProp; + + IMMDeviceCollection* captureDevices = NULL; + IMMDeviceCollection* renderDevices = NULL; + IMMDevice* devicePtr = NULL; + IMMDevice* defaultDevicePtr = NULL; + IAudioClient* audioClient = NULL; + IPropertyStore* devicePropStore = NULL; + IPropertyStore* defaultDevicePropStore = NULL; + + WAVEFORMATEX* deviceFormat = NULL; + WAVEFORMATEX* closestMatchFormat = NULL; + + // probed + info.probed = false; + + // Count capture devices + errorText_.clear(); + RtAudioError::Type errorType = RtAudioError::DRIVER_ERROR; + HRESULT hr = deviceEnumerator_->EnumAudioEndpoints( eCapture, DEVICE_STATE_ACTIVE, &captureDevices ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve capture device collection."; + goto Exit; + } + + hr = captureDevices->GetCount( &captureDeviceCount ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve capture device count."; + goto Exit; + } + + // Count render devices + hr = deviceEnumerator_->EnumAudioEndpoints( eRender, DEVICE_STATE_ACTIVE, &renderDevices ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve render device collection."; + goto Exit; + } + + hr = renderDevices->GetCount( &renderDeviceCount ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve render device count."; + goto Exit; + } + + // validate device index + if ( device >= captureDeviceCount + renderDeviceCount ) { + errorText_ = "RtApiWasapi::getDeviceInfo: Invalid device index."; + errorType = RtAudioError::INVALID_USE; + goto Exit; + } + + // determine whether index falls within capture or render devices + if ( device >= renderDeviceCount ) { + hr = captureDevices->Item( device - renderDeviceCount, &devicePtr ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve capture device handle."; + goto Exit; + } + isCaptureDevice = true; + } + else { + hr = renderDevices->Item( device, &devicePtr ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve render device handle."; + goto Exit; + } + isCaptureDevice = false; + } + + // get default device name + if ( isCaptureDevice ) { + hr = deviceEnumerator_->GetDefaultAudioEndpoint( eCapture, eConsole, &defaultDevicePtr ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve default capture device handle."; + goto Exit; + } + } + else { + hr = deviceEnumerator_->GetDefaultAudioEndpoint( eRender, eConsole, &defaultDevicePtr ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve default render device handle."; + goto Exit; + } + } + + hr = defaultDevicePtr->OpenPropertyStore( STGM_READ, &defaultDevicePropStore ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::getDeviceInfo: Unable to open default device property store."; + goto Exit; + } + PropVariantInit( &defaultDeviceNameProp ); + + hr = defaultDevicePropStore->GetValue( PKEY_Device_FriendlyName, &defaultDeviceNameProp ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve default device property: PKEY_Device_FriendlyName."; + goto Exit; + } + + defaultDeviceName = convertCharPointerToStdString(defaultDeviceNameProp.pwszVal); + + // name + hr = devicePtr->OpenPropertyStore( STGM_READ, &devicePropStore ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::getDeviceInfo: Unable to open device property store."; + goto Exit; + } + + PropVariantInit( &deviceNameProp ); + + hr = devicePropStore->GetValue( PKEY_Device_FriendlyName, &deviceNameProp ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve device property: PKEY_Device_FriendlyName."; + goto Exit; + } + + info.name =convertCharPointerToStdString(deviceNameProp.pwszVal); + + // is default + if ( isCaptureDevice ) { + info.isDefaultInput = info.name == defaultDeviceName; + info.isDefaultOutput = false; + } + else { + info.isDefaultInput = false; + info.isDefaultOutput = info.name == defaultDeviceName; + } + + // channel count + hr = devicePtr->Activate( __uuidof( IAudioClient ), CLSCTX_ALL, NULL, ( void** ) &audioClient ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve device audio client."; + goto Exit; + } + + hr = audioClient->GetMixFormat( &deviceFormat ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve device mix format."; + goto Exit; + } + + if ( isCaptureDevice ) { + info.inputChannels = deviceFormat->nChannels; + info.outputChannels = 0; + info.duplexChannels = 0; + } + else { + info.inputChannels = 0; + info.outputChannels = deviceFormat->nChannels; + info.duplexChannels = 0; + } + + // sample rates + info.sampleRates.clear(); + + // allow support for all sample rates as we have a built-in sample rate converter + for ( unsigned int i = 0; i < MAX_SAMPLE_RATES; i++ ) { + info.sampleRates.push_back( SAMPLE_RATES[i] ); + } + info.preferredSampleRate = deviceFormat->nSamplesPerSec; + + // native format + info.nativeFormats = 0; + + if ( deviceFormat->wFormatTag == WAVE_FORMAT_IEEE_FLOAT || + ( deviceFormat->wFormatTag == WAVE_FORMAT_EXTENSIBLE && + ( ( WAVEFORMATEXTENSIBLE* ) deviceFormat )->SubFormat == KSDATAFORMAT_SUBTYPE_IEEE_FLOAT ) ) + { + if ( deviceFormat->wBitsPerSample == 32 ) { + info.nativeFormats |= RTAUDIO_FLOAT32; + } + else if ( deviceFormat->wBitsPerSample == 64 ) { + info.nativeFormats |= RTAUDIO_FLOAT64; + } + } + else if ( deviceFormat->wFormatTag == WAVE_FORMAT_PCM || + ( deviceFormat->wFormatTag == WAVE_FORMAT_EXTENSIBLE && + ( ( WAVEFORMATEXTENSIBLE* ) deviceFormat )->SubFormat == KSDATAFORMAT_SUBTYPE_PCM ) ) + { + if ( deviceFormat->wBitsPerSample == 8 ) { + info.nativeFormats |= RTAUDIO_SINT8; + } + else if ( deviceFormat->wBitsPerSample == 16 ) { + info.nativeFormats |= RTAUDIO_SINT16; + } + else if ( deviceFormat->wBitsPerSample == 24 ) { + info.nativeFormats |= RTAUDIO_SINT24; + } + else if ( deviceFormat->wBitsPerSample == 32 ) { + info.nativeFormats |= RTAUDIO_SINT32; + } + } + + // probed + info.probed = true; + +Exit: + // release all references + PropVariantClear( &deviceNameProp ); + PropVariantClear( &defaultDeviceNameProp ); + + SAFE_RELEASE( captureDevices ); + SAFE_RELEASE( renderDevices ); + SAFE_RELEASE( devicePtr ); + SAFE_RELEASE( defaultDevicePtr ); + SAFE_RELEASE( audioClient ); + SAFE_RELEASE( devicePropStore ); + SAFE_RELEASE( defaultDevicePropStore ); + + CoTaskMemFree( deviceFormat ); + CoTaskMemFree( closestMatchFormat ); + + if ( !errorText_.empty() ) + error( errorType ); + return info; +} + +//----------------------------------------------------------------------------- + +unsigned int RtApiWasapi::getDefaultOutputDevice( void ) +{ + for ( unsigned int i = 0; i < getDeviceCount(); i++ ) { + if ( getDeviceInfo( i ).isDefaultOutput ) { + return i; + } + } + + return 0; +} + +//----------------------------------------------------------------------------- + +unsigned int RtApiWasapi::getDefaultInputDevice( void ) +{ + for ( unsigned int i = 0; i < getDeviceCount(); i++ ) { + if ( getDeviceInfo( i ).isDefaultInput ) { + return i; + } + } + + return 0; +} + +//----------------------------------------------------------------------------- + +void RtApiWasapi::closeStream( void ) +{ + if ( stream_.state == STREAM_CLOSED ) { + errorText_ = "RtApiWasapi::closeStream: No open stream to close."; + error( RtAudioError::WARNING ); + return; + } + + if ( stream_.state != STREAM_STOPPED ) + stopStream(); + + // clean up stream memory + SAFE_RELEASE( ( ( WasapiHandle* ) stream_.apiHandle )->captureAudioClient ) + SAFE_RELEASE( ( ( WasapiHandle* ) stream_.apiHandle )->renderAudioClient ) + + SAFE_RELEASE( ( ( WasapiHandle* ) stream_.apiHandle )->captureClient ) + SAFE_RELEASE( ( ( WasapiHandle* ) stream_.apiHandle )->renderClient ) + + if ( ( ( WasapiHandle* ) stream_.apiHandle )->captureEvent ) + CloseHandle( ( ( WasapiHandle* ) stream_.apiHandle )->captureEvent ); + + if ( ( ( WasapiHandle* ) stream_.apiHandle )->renderEvent ) + CloseHandle( ( ( WasapiHandle* ) stream_.apiHandle )->renderEvent ); + + delete ( WasapiHandle* ) stream_.apiHandle; + stream_.apiHandle = NULL; + + for ( int i = 0; i < 2; i++ ) { + if ( stream_.userBuffer[i] ) { + free( stream_.userBuffer[i] ); + stream_.userBuffer[i] = 0; + } + } + + if ( stream_.deviceBuffer ) { + free( stream_.deviceBuffer ); + stream_.deviceBuffer = 0; + } + + // update stream state + stream_.state = STREAM_CLOSED; +} + +//----------------------------------------------------------------------------- + +void RtApiWasapi::startStream( void ) +{ + verifyStream(); + + if ( stream_.state == STREAM_RUNNING ) { + errorText_ = "RtApiWasapi::startStream: The stream is already running."; + error( RtAudioError::WARNING ); + return; + } + + // update stream state + stream_.state = STREAM_RUNNING; + + // create WASAPI stream thread + stream_.callbackInfo.thread = ( ThreadHandle ) CreateThread( NULL, 0, runWasapiThread, this, CREATE_SUSPENDED, NULL ); + + if ( !stream_.callbackInfo.thread ) { + errorText_ = "RtApiWasapi::startStream: Unable to instantiate callback thread."; + error( RtAudioError::THREAD_ERROR ); + } + else { + SetThreadPriority( ( void* ) stream_.callbackInfo.thread, stream_.callbackInfo.priority ); + ResumeThread( ( void* ) stream_.callbackInfo.thread ); + } +} + +//----------------------------------------------------------------------------- + +void RtApiWasapi::stopStream( void ) +{ + verifyStream(); + + if ( stream_.state == STREAM_STOPPED ) { + errorText_ = "RtApiWasapi::stopStream: The stream is already stopped."; + error( RtAudioError::WARNING ); + return; + } + + // inform stream thread by setting stream state to STREAM_STOPPING + stream_.state = STREAM_STOPPING; + + // wait until stream thread is stopped + while( stream_.state != STREAM_STOPPED ) { + Sleep( 1 ); + } + + // Wait for the last buffer to play before stopping. + Sleep( 1000 * stream_.bufferSize / stream_.sampleRate ); + + // stop capture client if applicable + if ( ( ( WasapiHandle* ) stream_.apiHandle )->captureAudioClient ) { + HRESULT hr = ( ( WasapiHandle* ) stream_.apiHandle )->captureAudioClient->Stop(); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::stopStream: Unable to stop capture stream."; + error( RtAudioError::DRIVER_ERROR ); + return; + } + } + + // stop render client if applicable + if ( ( ( WasapiHandle* ) stream_.apiHandle )->renderAudioClient ) { + HRESULT hr = ( ( WasapiHandle* ) stream_.apiHandle )->renderAudioClient->Stop(); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::stopStream: Unable to stop render stream."; + error( RtAudioError::DRIVER_ERROR ); + return; + } + } + + // close thread handle + if ( stream_.callbackInfo.thread && !CloseHandle( ( void* ) stream_.callbackInfo.thread ) ) { + errorText_ = "RtApiWasapi::stopStream: Unable to close callback thread."; + error( RtAudioError::THREAD_ERROR ); + return; + } + + stream_.callbackInfo.thread = (ThreadHandle) NULL; +} + +//----------------------------------------------------------------------------- + +void RtApiWasapi::abortStream( void ) +{ + verifyStream(); + + if ( stream_.state == STREAM_STOPPED ) { + errorText_ = "RtApiWasapi::abortStream: The stream is already stopped."; + error( RtAudioError::WARNING ); + return; + } + + // inform stream thread by setting stream state to STREAM_STOPPING + stream_.state = STREAM_STOPPING; + + // wait until stream thread is stopped + while ( stream_.state != STREAM_STOPPED ) { + Sleep( 1 ); + } + + // stop capture client if applicable + if ( ( ( WasapiHandle* ) stream_.apiHandle )->captureAudioClient ) { + HRESULT hr = ( ( WasapiHandle* ) stream_.apiHandle )->captureAudioClient->Stop(); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::abortStream: Unable to stop capture stream."; + error( RtAudioError::DRIVER_ERROR ); + return; + } + } + + // stop render client if applicable + if ( ( ( WasapiHandle* ) stream_.apiHandle )->renderAudioClient ) { + HRESULT hr = ( ( WasapiHandle* ) stream_.apiHandle )->renderAudioClient->Stop(); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::abortStream: Unable to stop render stream."; + error( RtAudioError::DRIVER_ERROR ); + return; + } + } + + // close thread handle + if ( stream_.callbackInfo.thread && !CloseHandle( ( void* ) stream_.callbackInfo.thread ) ) { + errorText_ = "RtApiWasapi::abortStream: Unable to close callback thread."; + error( RtAudioError::THREAD_ERROR ); + return; + } + + stream_.callbackInfo.thread = (ThreadHandle) NULL; +} + +//----------------------------------------------------------------------------- + +bool RtApiWasapi::probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, + unsigned int firstChannel, unsigned int sampleRate, + RtAudioFormat format, unsigned int* bufferSize, + RtAudio::StreamOptions* options ) +{ + bool methodResult = FAILURE; + unsigned int captureDeviceCount = 0; + unsigned int renderDeviceCount = 0; + + IMMDeviceCollection* captureDevices = NULL; + IMMDeviceCollection* renderDevices = NULL; + IMMDevice* devicePtr = NULL; + WAVEFORMATEX* deviceFormat = NULL; + unsigned int bufferBytes; + stream_.state = STREAM_STOPPED; + + // create API Handle if not already created + if ( !stream_.apiHandle ) + stream_.apiHandle = ( void* ) new WasapiHandle(); + + // Count capture devices + errorText_.clear(); + RtAudioError::Type errorType = RtAudioError::DRIVER_ERROR; + HRESULT hr = deviceEnumerator_->EnumAudioEndpoints( eCapture, DEVICE_STATE_ACTIVE, &captureDevices ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve capture device collection."; + goto Exit; + } + + hr = captureDevices->GetCount( &captureDeviceCount ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve capture device count."; + goto Exit; + } + + // Count render devices + hr = deviceEnumerator_->EnumAudioEndpoints( eRender, DEVICE_STATE_ACTIVE, &renderDevices ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve render device collection."; + goto Exit; + } + + hr = renderDevices->GetCount( &renderDeviceCount ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve render device count."; + goto Exit; + } + + // validate device index + if ( device >= captureDeviceCount + renderDeviceCount ) { + errorType = RtAudioError::INVALID_USE; + errorText_ = "RtApiWasapi::probeDeviceOpen: Invalid device index."; + goto Exit; + } + + // determine whether index falls within capture or render devices + if ( device >= renderDeviceCount ) { + if ( mode != INPUT ) { + errorType = RtAudioError::INVALID_USE; + errorText_ = "RtApiWasapi::probeDeviceOpen: Capture device selected as output device."; + goto Exit; + } + + // retrieve captureAudioClient from devicePtr + IAudioClient*& captureAudioClient = ( ( WasapiHandle* ) stream_.apiHandle )->captureAudioClient; + + hr = captureDevices->Item( device - renderDeviceCount, &devicePtr ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve capture device handle."; + goto Exit; + } + + hr = devicePtr->Activate( __uuidof( IAudioClient ), CLSCTX_ALL, + NULL, ( void** ) &captureAudioClient ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve device audio client."; + goto Exit; + } + + hr = captureAudioClient->GetMixFormat( &deviceFormat ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve device mix format."; + goto Exit; + } + + stream_.nDeviceChannels[mode] = deviceFormat->nChannels; + captureAudioClient->GetStreamLatency( ( long long* ) &stream_.latency[mode] ); + } + else { + if ( mode != OUTPUT ) { + errorType = RtAudioError::INVALID_USE; + errorText_ = "RtApiWasapi::probeDeviceOpen: Render device selected as input device."; + goto Exit; + } + + // retrieve renderAudioClient from devicePtr + IAudioClient*& renderAudioClient = ( ( WasapiHandle* ) stream_.apiHandle )->renderAudioClient; + + hr = renderDevices->Item( device, &devicePtr ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve render device handle."; + goto Exit; + } + + hr = devicePtr->Activate( __uuidof( IAudioClient ), CLSCTX_ALL, + NULL, ( void** ) &renderAudioClient ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve device audio client."; + goto Exit; + } + + hr = renderAudioClient->GetMixFormat( &deviceFormat ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve device mix format."; + goto Exit; + } + + stream_.nDeviceChannels[mode] = deviceFormat->nChannels; + renderAudioClient->GetStreamLatency( ( long long* ) &stream_.latency[mode] ); + } + + // fill stream data + if ( ( stream_.mode == OUTPUT && mode == INPUT ) || + ( stream_.mode == INPUT && mode == OUTPUT ) ) { + stream_.mode = DUPLEX; + } + else { + stream_.mode = mode; + } + + stream_.device[mode] = device; + stream_.doByteSwap[mode] = false; + stream_.sampleRate = sampleRate; + stream_.bufferSize = *bufferSize; + stream_.nBuffers = 1; + stream_.nUserChannels[mode] = channels; + stream_.channelOffset[mode] = firstChannel; + stream_.userFormat = format; + stream_.deviceFormat[mode] = getDeviceInfo( device ).nativeFormats; + + if ( options && options->flags & RTAUDIO_NONINTERLEAVED ) + stream_.userInterleaved = false; + else + stream_.userInterleaved = true; + stream_.deviceInterleaved[mode] = true; + + // Set flags for buffer conversion. + stream_.doConvertBuffer[mode] = false; + if ( stream_.userFormat != stream_.deviceFormat[mode] || + stream_.nUserChannels != stream_.nDeviceChannels ) + stream_.doConvertBuffer[mode] = true; + else if ( stream_.userInterleaved != stream_.deviceInterleaved[mode] && + stream_.nUserChannels[mode] > 1 ) + stream_.doConvertBuffer[mode] = true; + + if ( stream_.doConvertBuffer[mode] ) + setConvertInfo( mode, 0 ); + + // Allocate necessary internal buffers + bufferBytes = stream_.nUserChannels[mode] * stream_.bufferSize * formatBytes( stream_.userFormat ); + + stream_.userBuffer[mode] = ( char* ) calloc( bufferBytes, 1 ); + if ( !stream_.userBuffer[mode] ) { + errorType = RtAudioError::MEMORY_ERROR; + errorText_ = "RtApiWasapi::probeDeviceOpen: Error allocating user buffer memory."; + goto Exit; + } + + if ( options && options->flags & RTAUDIO_SCHEDULE_REALTIME ) + stream_.callbackInfo.priority = 15; + else + stream_.callbackInfo.priority = 0; + + ///! TODO: RTAUDIO_MINIMIZE_LATENCY // Provide stream buffers directly to callback + ///! TODO: RTAUDIO_HOG_DEVICE // Exclusive mode + + methodResult = SUCCESS; + +Exit: + //clean up + SAFE_RELEASE( captureDevices ); + SAFE_RELEASE( renderDevices ); + SAFE_RELEASE( devicePtr ); + CoTaskMemFree( deviceFormat ); + + // if method failed, close the stream + if ( methodResult == FAILURE ) + closeStream(); + + if ( !errorText_.empty() ) + error( errorType ); + return methodResult; +} + +//============================================================================= + +DWORD WINAPI RtApiWasapi::runWasapiThread( void* wasapiPtr ) +{ + if ( wasapiPtr ) + ( ( RtApiWasapi* ) wasapiPtr )->wasapiThread(); + + return 0; +} + +DWORD WINAPI RtApiWasapi::stopWasapiThread( void* wasapiPtr ) +{ + if ( wasapiPtr ) + ( ( RtApiWasapi* ) wasapiPtr )->stopStream(); + + return 0; +} + +DWORD WINAPI RtApiWasapi::abortWasapiThread( void* wasapiPtr ) +{ + if ( wasapiPtr ) + ( ( RtApiWasapi* ) wasapiPtr )->abortStream(); + + return 0; +} + +//----------------------------------------------------------------------------- + +void RtApiWasapi::wasapiThread() +{ + // as this is a new thread, we must CoInitialize it + CoInitialize( NULL ); + + HRESULT hr; + + IAudioClient* captureAudioClient = ( ( WasapiHandle* ) stream_.apiHandle )->captureAudioClient; + IAudioClient* renderAudioClient = ( ( WasapiHandle* ) stream_.apiHandle )->renderAudioClient; + IAudioCaptureClient* captureClient = ( ( WasapiHandle* ) stream_.apiHandle )->captureClient; + IAudioRenderClient* renderClient = ( ( WasapiHandle* ) stream_.apiHandle )->renderClient; + HANDLE captureEvent = ( ( WasapiHandle* ) stream_.apiHandle )->captureEvent; + HANDLE renderEvent = ( ( WasapiHandle* ) stream_.apiHandle )->renderEvent; + + WAVEFORMATEX* captureFormat = NULL; + WAVEFORMATEX* renderFormat = NULL; + float captureSrRatio = 0.0f; + float renderSrRatio = 0.0f; + WasapiBuffer captureBuffer; + WasapiBuffer renderBuffer; + + // declare local stream variables + RtAudioCallback callback = ( RtAudioCallback ) stream_.callbackInfo.callback; + BYTE* streamBuffer = NULL; + unsigned long captureFlags = 0; + unsigned int bufferFrameCount = 0; + unsigned int numFramesPadding = 0; + unsigned int convBufferSize = 0; + bool callbackPushed = false; + bool callbackPulled = false; + bool callbackStopped = false; + int callbackResult = 0; + + // convBuffer is used to store converted buffers between WASAPI and the user + char* convBuffer = NULL; + unsigned int convBuffSize = 0; + unsigned int deviceBuffSize = 0; + + errorText_.clear(); + RtAudioError::Type errorType = RtAudioError::DRIVER_ERROR; + + // Attempt to assign "Pro Audio" characteristic to thread + HMODULE AvrtDll = LoadLibrary( (LPCTSTR) "AVRT.dll" ); + if ( AvrtDll ) { + DWORD taskIndex = 0; + TAvSetMmThreadCharacteristicsPtr AvSetMmThreadCharacteristicsPtr = ( TAvSetMmThreadCharacteristicsPtr ) GetProcAddress( AvrtDll, "AvSetMmThreadCharacteristicsW" ); + AvSetMmThreadCharacteristicsPtr( L"Pro Audio", &taskIndex ); + FreeLibrary( AvrtDll ); + } + + // start capture stream if applicable + if ( captureAudioClient ) { + hr = captureAudioClient->GetMixFormat( &captureFormat ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::wasapiThread: Unable to retrieve device mix format."; + goto Exit; + } + + captureSrRatio = ( ( float ) captureFormat->nSamplesPerSec / stream_.sampleRate ); + + // initialize capture stream according to desire buffer size + float desiredBufferSize = stream_.bufferSize * captureSrRatio; + REFERENCE_TIME desiredBufferPeriod = ( REFERENCE_TIME ) ( ( float ) desiredBufferSize * 10000000 / captureFormat->nSamplesPerSec ); + + if ( !captureClient ) { + hr = captureAudioClient->Initialize( AUDCLNT_SHAREMODE_SHARED, + AUDCLNT_STREAMFLAGS_EVENTCALLBACK, + desiredBufferPeriod, + desiredBufferPeriod, + captureFormat, + NULL ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::wasapiThread: Unable to initialize capture audio client."; + goto Exit; + } + + hr = captureAudioClient->GetService( __uuidof( IAudioCaptureClient ), + ( void** ) &captureClient ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::wasapiThread: Unable to retrieve capture client handle."; + goto Exit; + } + + // configure captureEvent to trigger on every available capture buffer + captureEvent = CreateEvent( NULL, FALSE, FALSE, NULL ); + if ( !captureEvent ) { + errorType = RtAudioError::SYSTEM_ERROR; + errorText_ = "RtApiWasapi::wasapiThread: Unable to create capture event."; + goto Exit; + } + + hr = captureAudioClient->SetEventHandle( captureEvent ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::wasapiThread: Unable to set capture event handle."; + goto Exit; + } + + ( ( WasapiHandle* ) stream_.apiHandle )->captureClient = captureClient; + ( ( WasapiHandle* ) stream_.apiHandle )->captureEvent = captureEvent; + } + + unsigned int inBufferSize = 0; + hr = captureAudioClient->GetBufferSize( &inBufferSize ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::wasapiThread: Unable to get capture buffer size."; + goto Exit; + } + + // scale outBufferSize according to stream->user sample rate ratio + unsigned int outBufferSize = ( unsigned int ) ( stream_.bufferSize * captureSrRatio ) * stream_.nDeviceChannels[INPUT]; + inBufferSize *= stream_.nDeviceChannels[INPUT]; + + // set captureBuffer size + captureBuffer.setBufferSize( inBufferSize + outBufferSize, formatBytes( stream_.deviceFormat[INPUT] ) ); + + // reset the capture stream + hr = captureAudioClient->Reset(); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::wasapiThread: Unable to reset capture stream."; + goto Exit; + } + + // start the capture stream + hr = captureAudioClient->Start(); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::wasapiThread: Unable to start capture stream."; + goto Exit; + } + } + + // start render stream if applicable + if ( renderAudioClient ) { + hr = renderAudioClient->GetMixFormat( &renderFormat ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::wasapiThread: Unable to retrieve device mix format."; + goto Exit; + } + + renderSrRatio = ( ( float ) renderFormat->nSamplesPerSec / stream_.sampleRate ); + + // initialize render stream according to desire buffer size + float desiredBufferSize = stream_.bufferSize * renderSrRatio; + REFERENCE_TIME desiredBufferPeriod = ( REFERENCE_TIME ) ( ( float ) desiredBufferSize * 10000000 / renderFormat->nSamplesPerSec ); + + if ( !renderClient ) { + hr = renderAudioClient->Initialize( AUDCLNT_SHAREMODE_SHARED, + AUDCLNT_STREAMFLAGS_EVENTCALLBACK, + desiredBufferPeriod, + desiredBufferPeriod, + renderFormat, + NULL ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::wasapiThread: Unable to initialize render audio client."; + goto Exit; + } + + hr = renderAudioClient->GetService( __uuidof( IAudioRenderClient ), + ( void** ) &renderClient ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::wasapiThread: Unable to retrieve render client handle."; + goto Exit; + } + + // configure renderEvent to trigger on every available render buffer + renderEvent = CreateEvent( NULL, FALSE, FALSE, NULL ); + if ( !renderEvent ) { + errorType = RtAudioError::SYSTEM_ERROR; + errorText_ = "RtApiWasapi::wasapiThread: Unable to create render event."; + goto Exit; + } + + hr = renderAudioClient->SetEventHandle( renderEvent ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::wasapiThread: Unable to set render event handle."; + goto Exit; + } + + ( ( WasapiHandle* ) stream_.apiHandle )->renderClient = renderClient; + ( ( WasapiHandle* ) stream_.apiHandle )->renderEvent = renderEvent; + } + + unsigned int outBufferSize = 0; + hr = renderAudioClient->GetBufferSize( &outBufferSize ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::wasapiThread: Unable to get render buffer size."; + goto Exit; + } + + // scale inBufferSize according to user->stream sample rate ratio + unsigned int inBufferSize = ( unsigned int ) ( stream_.bufferSize * renderSrRatio ) * stream_.nDeviceChannels[OUTPUT]; + outBufferSize *= stream_.nDeviceChannels[OUTPUT]; + + // set renderBuffer size + renderBuffer.setBufferSize( inBufferSize + outBufferSize, formatBytes( stream_.deviceFormat[OUTPUT] ) ); + + // reset the render stream + hr = renderAudioClient->Reset(); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::wasapiThread: Unable to reset render stream."; + goto Exit; + } + + // start the render stream + hr = renderAudioClient->Start(); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::wasapiThread: Unable to start render stream."; + goto Exit; + } + } + + if ( stream_.mode == INPUT ) { + convBuffSize = ( size_t ) ( stream_.bufferSize * captureSrRatio ) * stream_.nDeviceChannels[INPUT] * formatBytes( stream_.deviceFormat[INPUT] ); + deviceBuffSize = stream_.bufferSize * stream_.nDeviceChannels[INPUT] * formatBytes( stream_.deviceFormat[INPUT] ); + } + else if ( stream_.mode == OUTPUT ) { + convBuffSize = ( size_t ) ( stream_.bufferSize * renderSrRatio ) * stream_.nDeviceChannels[OUTPUT] * formatBytes( stream_.deviceFormat[OUTPUT] ); + deviceBuffSize = stream_.bufferSize * stream_.nDeviceChannels[OUTPUT] * formatBytes( stream_.deviceFormat[OUTPUT] ); + } + else if ( stream_.mode == DUPLEX ) { + convBuffSize = std::max( ( size_t ) ( stream_.bufferSize * captureSrRatio ) * stream_.nDeviceChannels[INPUT] * formatBytes( stream_.deviceFormat[INPUT] ), + ( size_t ) ( stream_.bufferSize * renderSrRatio ) * stream_.nDeviceChannels[OUTPUT] * formatBytes( stream_.deviceFormat[OUTPUT] ) ); + deviceBuffSize = std::max( stream_.bufferSize * stream_.nDeviceChannels[INPUT] * formatBytes( stream_.deviceFormat[INPUT] ), + stream_.bufferSize * stream_.nDeviceChannels[OUTPUT] * formatBytes( stream_.deviceFormat[OUTPUT] ) ); + } + + convBuffer = ( char* ) malloc( convBuffSize ); + stream_.deviceBuffer = ( char* ) malloc( deviceBuffSize ); + if ( !convBuffer || !stream_.deviceBuffer ) { + errorType = RtAudioError::MEMORY_ERROR; + errorText_ = "RtApiWasapi::wasapiThread: Error allocating device buffer memory."; + goto Exit; + } + + // stream process loop + while ( stream_.state != STREAM_STOPPING ) { + if ( !callbackPulled ) { + // Callback Input + // ============== + // 1. Pull callback buffer from inputBuffer + // 2. If 1. was successful: Convert callback buffer to user sample rate and channel count + // Convert callback buffer to user format + + if ( captureAudioClient ) { + // Pull callback buffer from inputBuffer + callbackPulled = captureBuffer.pullBuffer( convBuffer, + ( unsigned int ) ( stream_.bufferSize * captureSrRatio ) * stream_.nDeviceChannels[INPUT], + stream_.deviceFormat[INPUT] ); + + if ( callbackPulled ) { + // Convert callback buffer to user sample rate + convertBufferWasapi( stream_.deviceBuffer, + convBuffer, + stream_.nDeviceChannels[INPUT], + captureFormat->nSamplesPerSec, + stream_.sampleRate, + ( unsigned int ) ( stream_.bufferSize * captureSrRatio ), + convBufferSize, + stream_.deviceFormat[INPUT] ); + + if ( stream_.doConvertBuffer[INPUT] ) { + // Convert callback buffer to user format + convertBuffer( stream_.userBuffer[INPUT], + stream_.deviceBuffer, + stream_.convertInfo[INPUT] ); + } + else { + // no further conversion, simple copy deviceBuffer to userBuffer + memcpy( stream_.userBuffer[INPUT], + stream_.deviceBuffer, + stream_.bufferSize * stream_.nUserChannels[INPUT] * formatBytes( stream_.userFormat ) ); + } + } + } + else { + // if there is no capture stream, set callbackPulled flag + callbackPulled = true; + } + + // Execute Callback + // ================ + // 1. Execute user callback method + // 2. Handle return value from callback + + // if callback has not requested the stream to stop + if ( callbackPulled && !callbackStopped ) { + // Execute user callback method + callbackResult = callback( stream_.userBuffer[OUTPUT], + stream_.userBuffer[INPUT], + stream_.bufferSize, + getStreamTime(), + captureFlags & AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY ? RTAUDIO_INPUT_OVERFLOW : 0, + stream_.callbackInfo.userData ); + + // Handle return value from callback + if ( callbackResult == 1 ) { + // instantiate a thread to stop this thread + HANDLE threadHandle = CreateThread( NULL, 0, stopWasapiThread, this, 0, NULL ); + if ( !threadHandle ) { + errorType = RtAudioError::THREAD_ERROR; + errorText_ = "RtApiWasapi::wasapiThread: Unable to instantiate stream stop thread."; + goto Exit; + } + else if ( !CloseHandle( threadHandle ) ) { + errorType = RtAudioError::THREAD_ERROR; + errorText_ = "RtApiWasapi::wasapiThread: Unable to close stream stop thread handle."; + goto Exit; + } + + callbackStopped = true; + } + else if ( callbackResult == 2 ) { + // instantiate a thread to stop this thread + HANDLE threadHandle = CreateThread( NULL, 0, abortWasapiThread, this, 0, NULL ); + if ( !threadHandle ) { + errorType = RtAudioError::THREAD_ERROR; + errorText_ = "RtApiWasapi::wasapiThread: Unable to instantiate stream abort thread."; + goto Exit; + } + else if ( !CloseHandle( threadHandle ) ) { + errorType = RtAudioError::THREAD_ERROR; + errorText_ = "RtApiWasapi::wasapiThread: Unable to close stream abort thread handle."; + goto Exit; + } + + callbackStopped = true; + } + } + } + + // Callback Output + // =============== + // 1. Convert callback buffer to stream format + // 2. Convert callback buffer to stream sample rate and channel count + // 3. Push callback buffer into outputBuffer + + if ( renderAudioClient && callbackPulled ) { + if ( stream_.doConvertBuffer[OUTPUT] ) { + // Convert callback buffer to stream format + convertBuffer( stream_.deviceBuffer, + stream_.userBuffer[OUTPUT], + stream_.convertInfo[OUTPUT] ); + + } + + // Convert callback buffer to stream sample rate + convertBufferWasapi( convBuffer, + stream_.deviceBuffer, + stream_.nDeviceChannels[OUTPUT], + stream_.sampleRate, + renderFormat->nSamplesPerSec, + stream_.bufferSize, + convBufferSize, + stream_.deviceFormat[OUTPUT] ); + + // Push callback buffer into outputBuffer + callbackPushed = renderBuffer.pushBuffer( convBuffer, + convBufferSize * stream_.nDeviceChannels[OUTPUT], + stream_.deviceFormat[OUTPUT] ); + } + else { + // if there is no render stream, set callbackPushed flag + callbackPushed = true; + } + + // Stream Capture + // ============== + // 1. Get capture buffer from stream + // 2. Push capture buffer into inputBuffer + // 3. If 2. was successful: Release capture buffer + + if ( captureAudioClient ) { + // if the callback input buffer was not pulled from captureBuffer, wait for next capture event + if ( !callbackPulled ) { + WaitForSingleObject( captureEvent, INFINITE ); + } + + // Get capture buffer from stream + hr = captureClient->GetBuffer( &streamBuffer, + &bufferFrameCount, + &captureFlags, NULL, NULL ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::wasapiThread: Unable to retrieve capture buffer."; + goto Exit; + } + + if ( bufferFrameCount != 0 ) { + // Push capture buffer into inputBuffer + if ( captureBuffer.pushBuffer( ( char* ) streamBuffer, + bufferFrameCount * stream_.nDeviceChannels[INPUT], + stream_.deviceFormat[INPUT] ) ) + { + // Release capture buffer + hr = captureClient->ReleaseBuffer( bufferFrameCount ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::wasapiThread: Unable to release capture buffer."; + goto Exit; + } + } + else + { + // Inform WASAPI that capture was unsuccessful + hr = captureClient->ReleaseBuffer( 0 ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::wasapiThread: Unable to release capture buffer."; + goto Exit; + } + } + } + else + { + // Inform WASAPI that capture was unsuccessful + hr = captureClient->ReleaseBuffer( 0 ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::wasapiThread: Unable to release capture buffer."; + goto Exit; + } + } + } + + // Stream Render + // ============= + // 1. Get render buffer from stream + // 2. Pull next buffer from outputBuffer + // 3. If 2. was successful: Fill render buffer with next buffer + // Release render buffer + + if ( renderAudioClient ) { + // if the callback output buffer was not pushed to renderBuffer, wait for next render event + if ( callbackPulled && !callbackPushed ) { + WaitForSingleObject( renderEvent, INFINITE ); + } + + // Get render buffer from stream + hr = renderAudioClient->GetBufferSize( &bufferFrameCount ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::wasapiThread: Unable to retrieve render buffer size."; + goto Exit; + } + + hr = renderAudioClient->GetCurrentPadding( &numFramesPadding ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::wasapiThread: Unable to retrieve render buffer padding."; + goto Exit; + } + + bufferFrameCount -= numFramesPadding; + + if ( bufferFrameCount != 0 ) { + hr = renderClient->GetBuffer( bufferFrameCount, &streamBuffer ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::wasapiThread: Unable to retrieve render buffer."; + goto Exit; + } + + // Pull next buffer from outputBuffer + // Fill render buffer with next buffer + if ( renderBuffer.pullBuffer( ( char* ) streamBuffer, + bufferFrameCount * stream_.nDeviceChannels[OUTPUT], + stream_.deviceFormat[OUTPUT] ) ) + { + // Release render buffer + hr = renderClient->ReleaseBuffer( bufferFrameCount, 0 ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::wasapiThread: Unable to release render buffer."; + goto Exit; + } + } + else + { + // Inform WASAPI that render was unsuccessful + hr = renderClient->ReleaseBuffer( 0, 0 ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::wasapiThread: Unable to release render buffer."; + goto Exit; + } + } + } + else + { + // Inform WASAPI that render was unsuccessful + hr = renderClient->ReleaseBuffer( 0, 0 ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::wasapiThread: Unable to release render buffer."; + goto Exit; + } + } + } + + // if the callback buffer was pushed renderBuffer reset callbackPulled flag + if ( callbackPushed ) { + callbackPulled = false; + // tick stream time + RtApi::tickStreamTime(); + } + + } + +Exit: + // clean up + CoTaskMemFree( captureFormat ); + CoTaskMemFree( renderFormat ); + + free ( convBuffer ); + + CoUninitialize(); + + // update stream state + stream_.state = STREAM_STOPPED; + + if ( errorText_.empty() ) + return; + else + error( errorType ); +} + +//******************** End of __WINDOWS_WASAPI__ *********************// +#endif + + +#if defined(__WINDOWS_DS__) // Windows DirectSound API + +// Modified by Robin Davies, October 2005 +// - Improvements to DirectX pointer chasing. +// - Bug fix for non-power-of-two Asio granularity used by Edirol PCR-A30. +// - Auto-call CoInitialize for DSOUND and ASIO platforms. +// Various revisions for RtAudio 4.0 by Gary Scavone, April 2007 +// Changed device query structure for RtAudio 4.0.7, January 2010 + +#include +#include +#include + +#if defined(__MINGW32__) + // missing from latest mingw winapi +#define WAVE_FORMAT_96M08 0x00010000 /* 96 kHz, Mono, 8-bit */ +#define WAVE_FORMAT_96S08 0x00020000 /* 96 kHz, Stereo, 8-bit */ +#define WAVE_FORMAT_96M16 0x00040000 /* 96 kHz, Mono, 16-bit */ +#define WAVE_FORMAT_96S16 0x00080000 /* 96 kHz, Stereo, 16-bit */ +#endif + +#define MINIMUM_DEVICE_BUFFER_SIZE 32768 + +#ifdef _MSC_VER // if Microsoft Visual C++ +#pragma comment( lib, "winmm.lib" ) // then, auto-link winmm.lib. Otherwise, it has to be added manually. +#endif + +static inline DWORD dsPointerBetween( DWORD pointer, DWORD laterPointer, DWORD earlierPointer, DWORD bufferSize ) +{ + if ( pointer > bufferSize ) pointer -= bufferSize; + if ( laterPointer < earlierPointer ) laterPointer += bufferSize; + if ( pointer < earlierPointer ) pointer += bufferSize; + return pointer >= earlierPointer && pointer < laterPointer; +} + +// A structure to hold various information related to the DirectSound +// API implementation. +struct DsHandle { + unsigned int drainCounter; // Tracks callback counts when draining + bool internalDrain; // Indicates if stop is initiated from callback or not. + void *id[2]; + void *buffer[2]; + bool xrun[2]; + UINT bufferPointer[2]; + DWORD dsBufferSize[2]; + DWORD dsPointerLeadTime[2]; // the number of bytes ahead of the safe pointer to lead by. + HANDLE condition; + + DsHandle() + :drainCounter(0), internalDrain(false) { id[0] = 0; id[1] = 0; buffer[0] = 0; buffer[1] = 0; xrun[0] = false; xrun[1] = false; bufferPointer[0] = 0; bufferPointer[1] = 0; } +}; + +// Declarations for utility functions, callbacks, and structures +// specific to the DirectSound implementation. +static BOOL CALLBACK deviceQueryCallback( LPGUID lpguid, + LPCTSTR description, + LPCTSTR module, + LPVOID lpContext ); + +static const char* getErrorString( int code ); + +static unsigned __stdcall callbackHandler( void *ptr ); + +struct DsDevice { + LPGUID id[2]; + bool validId[2]; + bool found; + std::string name; + + DsDevice() + : found(false) { validId[0] = false; validId[1] = false; } +}; + +struct DsProbeData { + bool isInput; + std::vector* dsDevices; +}; + +RtApiDs :: RtApiDs() +{ + // Dsound will run both-threaded. If CoInitialize fails, then just + // accept whatever the mainline chose for a threading model. + coInitialized_ = false; + HRESULT hr = CoInitialize( NULL ); + if ( !FAILED( hr ) ) coInitialized_ = true; +} + +RtApiDs :: ~RtApiDs() +{ + if ( coInitialized_ ) CoUninitialize(); // balanced call. + if ( stream_.state != STREAM_CLOSED ) closeStream(); +} + +// The DirectSound default output is always the first device. +unsigned int RtApiDs :: getDefaultOutputDevice( void ) +{ + return 0; +} + +// The DirectSound default input is always the first input device, +// which is the first capture device enumerated. +unsigned int RtApiDs :: getDefaultInputDevice( void ) +{ + return 0; +} + +unsigned int RtApiDs :: getDeviceCount( void ) +{ + // Set query flag for previously found devices to false, so that we + // can check for any devices that have disappeared. + for ( unsigned int i=0; i(dsDevices.size()); +} + +RtAudio::DeviceInfo RtApiDs :: getDeviceInfo( unsigned int device ) +{ + RtAudio::DeviceInfo info; + info.probed = false; + + if ( dsDevices.size() == 0 ) { + // Force a query of all devices + getDeviceCount(); + if ( dsDevices.size() == 0 ) { + errorText_ = "RtApiDs::getDeviceInfo: no devices found!"; + error( RtAudioError::INVALID_USE ); + return info; + } + } + + if ( device >= dsDevices.size() ) { + errorText_ = "RtApiDs::getDeviceInfo: device ID is invalid!"; + error( RtAudioError::INVALID_USE ); + return info; + } + + HRESULT result; + if ( dsDevices[ device ].validId[0] == false ) goto probeInput; + + LPDIRECTSOUND output; + DSCAPS outCaps; + result = DirectSoundCreate( dsDevices[ device ].id[0], &output, NULL ); + if ( FAILED( result ) ) { + errorStream_ << "RtApiDs::getDeviceInfo: error (" << getErrorString( result ) << ") opening output device (" << dsDevices[ device ].name << ")!"; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + goto probeInput; + } + + outCaps.dwSize = sizeof( outCaps ); + result = output->GetCaps( &outCaps ); + if ( FAILED( result ) ) { + output->Release(); + errorStream_ << "RtApiDs::getDeviceInfo: error (" << getErrorString( result ) << ") getting capabilities!"; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + goto probeInput; + } + + // Get output channel information. + info.outputChannels = ( outCaps.dwFlags & DSCAPS_PRIMARYSTEREO ) ? 2 : 1; + + // Get sample rate information. + info.sampleRates.clear(); + for ( unsigned int k=0; k= (unsigned int) outCaps.dwMinSecondarySampleRate && + SAMPLE_RATES[k] <= (unsigned int) outCaps.dwMaxSecondarySampleRate ) { + info.sampleRates.push_back( SAMPLE_RATES[k] ); + + if ( !info.preferredSampleRate || ( SAMPLE_RATES[k] <= 48000 && SAMPLE_RATES[k] > info.preferredSampleRate ) ) + info.preferredSampleRate = SAMPLE_RATES[k]; + } + } + + // Get format information. + if ( outCaps.dwFlags & DSCAPS_PRIMARY16BIT ) info.nativeFormats |= RTAUDIO_SINT16; + if ( outCaps.dwFlags & DSCAPS_PRIMARY8BIT ) info.nativeFormats |= RTAUDIO_SINT8; + + output->Release(); + + if ( getDefaultOutputDevice() == device ) + info.isDefaultOutput = true; + + if ( dsDevices[ device ].validId[1] == false ) { + info.name = dsDevices[ device ].name; + info.probed = true; + return info; + } + + probeInput: + + LPDIRECTSOUNDCAPTURE input; + result = DirectSoundCaptureCreate( dsDevices[ device ].id[1], &input, NULL ); + if ( FAILED( result ) ) { + errorStream_ << "RtApiDs::getDeviceInfo: error (" << getErrorString( result ) << ") opening input device (" << dsDevices[ device ].name << ")!"; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + return info; + } + + DSCCAPS inCaps; + inCaps.dwSize = sizeof( inCaps ); + result = input->GetCaps( &inCaps ); + if ( FAILED( result ) ) { + input->Release(); + errorStream_ << "RtApiDs::getDeviceInfo: error (" << getErrorString( result ) << ") getting object capabilities (" << dsDevices[ device ].name << ")!"; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + return info; + } + + // Get input channel information. + info.inputChannels = inCaps.dwChannels; + + // Get sample rate and format information. + std::vector rates; + if ( inCaps.dwChannels >= 2 ) { + if ( inCaps.dwFormats & WAVE_FORMAT_1S16 ) info.nativeFormats |= RTAUDIO_SINT16; + if ( inCaps.dwFormats & WAVE_FORMAT_2S16 ) info.nativeFormats |= RTAUDIO_SINT16; + if ( inCaps.dwFormats & WAVE_FORMAT_4S16 ) info.nativeFormats |= RTAUDIO_SINT16; + if ( inCaps.dwFormats & WAVE_FORMAT_96S16 ) info.nativeFormats |= RTAUDIO_SINT16; + if ( inCaps.dwFormats & WAVE_FORMAT_1S08 ) info.nativeFormats |= RTAUDIO_SINT8; + if ( inCaps.dwFormats & WAVE_FORMAT_2S08 ) info.nativeFormats |= RTAUDIO_SINT8; + if ( inCaps.dwFormats & WAVE_FORMAT_4S08 ) info.nativeFormats |= RTAUDIO_SINT8; + if ( inCaps.dwFormats & WAVE_FORMAT_96S08 ) info.nativeFormats |= RTAUDIO_SINT8; + + if ( info.nativeFormats & RTAUDIO_SINT16 ) { + if ( inCaps.dwFormats & WAVE_FORMAT_1S16 ) rates.push_back( 11025 ); + if ( inCaps.dwFormats & WAVE_FORMAT_2S16 ) rates.push_back( 22050 ); + if ( inCaps.dwFormats & WAVE_FORMAT_4S16 ) rates.push_back( 44100 ); + if ( inCaps.dwFormats & WAVE_FORMAT_96S16 ) rates.push_back( 96000 ); + } + else if ( info.nativeFormats & RTAUDIO_SINT8 ) { + if ( inCaps.dwFormats & WAVE_FORMAT_1S08 ) rates.push_back( 11025 ); + if ( inCaps.dwFormats & WAVE_FORMAT_2S08 ) rates.push_back( 22050 ); + if ( inCaps.dwFormats & WAVE_FORMAT_4S08 ) rates.push_back( 44100 ); + if ( inCaps.dwFormats & WAVE_FORMAT_96S08 ) rates.push_back( 96000 ); + } + } + else if ( inCaps.dwChannels == 1 ) { + if ( inCaps.dwFormats & WAVE_FORMAT_1M16 ) info.nativeFormats |= RTAUDIO_SINT16; + if ( inCaps.dwFormats & WAVE_FORMAT_2M16 ) info.nativeFormats |= RTAUDIO_SINT16; + if ( inCaps.dwFormats & WAVE_FORMAT_4M16 ) info.nativeFormats |= RTAUDIO_SINT16; + if ( inCaps.dwFormats & WAVE_FORMAT_96M16 ) info.nativeFormats |= RTAUDIO_SINT16; + if ( inCaps.dwFormats & WAVE_FORMAT_1M08 ) info.nativeFormats |= RTAUDIO_SINT8; + if ( inCaps.dwFormats & WAVE_FORMAT_2M08 ) info.nativeFormats |= RTAUDIO_SINT8; + if ( inCaps.dwFormats & WAVE_FORMAT_4M08 ) info.nativeFormats |= RTAUDIO_SINT8; + if ( inCaps.dwFormats & WAVE_FORMAT_96M08 ) info.nativeFormats |= RTAUDIO_SINT8; + + if ( info.nativeFormats & RTAUDIO_SINT16 ) { + if ( inCaps.dwFormats & WAVE_FORMAT_1M16 ) rates.push_back( 11025 ); + if ( inCaps.dwFormats & WAVE_FORMAT_2M16 ) rates.push_back( 22050 ); + if ( inCaps.dwFormats & WAVE_FORMAT_4M16 ) rates.push_back( 44100 ); + if ( inCaps.dwFormats & WAVE_FORMAT_96M16 ) rates.push_back( 96000 ); + } + else if ( info.nativeFormats & RTAUDIO_SINT8 ) { + if ( inCaps.dwFormats & WAVE_FORMAT_1M08 ) rates.push_back( 11025 ); + if ( inCaps.dwFormats & WAVE_FORMAT_2M08 ) rates.push_back( 22050 ); + if ( inCaps.dwFormats & WAVE_FORMAT_4M08 ) rates.push_back( 44100 ); + if ( inCaps.dwFormats & WAVE_FORMAT_96M08 ) rates.push_back( 96000 ); + } + } + else info.inputChannels = 0; // technically, this would be an error + + input->Release(); + + if ( info.inputChannels == 0 ) return info; + + // Copy the supported rates to the info structure but avoid duplication. + bool found; + for ( unsigned int i=0; i 0 && info.inputChannels > 0 ) + info.duplexChannels = (info.outputChannels > info.inputChannels) ? info.inputChannels : info.outputChannels; + + if ( device == 0 ) info.isDefaultInput = true; + + // Copy name and return. + info.name = dsDevices[ device ].name; + info.probed = true; + return info; +} + +bool RtApiDs :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, + unsigned int firstChannel, unsigned int sampleRate, + RtAudioFormat format, unsigned int *bufferSize, + RtAudio::StreamOptions *options ) +{ + if ( channels + firstChannel > 2 ) { + errorText_ = "RtApiDs::probeDeviceOpen: DirectSound does not support more than 2 channels per device."; + return FAILURE; + } + + size_t nDevices = dsDevices.size(); + if ( nDevices == 0 ) { + // This should not happen because a check is made before this function is called. + errorText_ = "RtApiDs::probeDeviceOpen: no devices found!"; + return FAILURE; + } + + if ( device >= nDevices ) { + // This should not happen because a check is made before this function is called. + errorText_ = "RtApiDs::probeDeviceOpen: device ID is invalid!"; + return FAILURE; + } + + if ( mode == OUTPUT ) { + if ( dsDevices[ device ].validId[0] == false ) { + errorStream_ << "RtApiDs::probeDeviceOpen: device (" << device << ") does not support output!"; + errorText_ = errorStream_.str(); + return FAILURE; + } + } + else { // mode == INPUT + if ( dsDevices[ device ].validId[1] == false ) { + errorStream_ << "RtApiDs::probeDeviceOpen: device (" << device << ") does not support input!"; + errorText_ = errorStream_.str(); + return FAILURE; + } + } + + // According to a note in PortAudio, using GetDesktopWindow() + // instead of GetForegroundWindow() is supposed to avoid problems + // that occur when the application's window is not the foreground + // window. Also, if the application window closes before the + // DirectSound buffer, DirectSound can crash. In the past, I had + // problems when using GetDesktopWindow() but it seems fine now + // (January 2010). I'll leave it commented here. + // HWND hWnd = GetForegroundWindow(); + HWND hWnd = GetDesktopWindow(); + + // Check the numberOfBuffers parameter and limit the lowest value to + // two. This is a judgement call and a value of two is probably too + // low for capture, but it should work for playback. + int nBuffers = 0; + if ( options ) nBuffers = options->numberOfBuffers; + if ( options && options->flags & RTAUDIO_MINIMIZE_LATENCY ) nBuffers = 2; + if ( nBuffers < 2 ) nBuffers = 3; + + // Check the lower range of the user-specified buffer size and set + // (arbitrarily) to a lower bound of 32. + if ( *bufferSize < 32 ) *bufferSize = 32; + + // Create the wave format structure. The data format setting will + // be determined later. + WAVEFORMATEX waveFormat; + ZeroMemory( &waveFormat, sizeof(WAVEFORMATEX) ); + waveFormat.wFormatTag = WAVE_FORMAT_PCM; + waveFormat.nChannels = channels + firstChannel; + waveFormat.nSamplesPerSec = (unsigned long) sampleRate; + + // Determine the device buffer size. By default, we'll use the value + // defined above (32K), but we will grow it to make allowances for + // very large software buffer sizes. + DWORD dsBufferSize = MINIMUM_DEVICE_BUFFER_SIZE; + DWORD dsPointerLeadTime = 0; + + void *ohandle = 0, *bhandle = 0; + HRESULT result; + if ( mode == OUTPUT ) { + + LPDIRECTSOUND output; + result = DirectSoundCreate( dsDevices[ device ].id[0], &output, NULL ); + if ( FAILED( result ) ) { + errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") opening output device (" << dsDevices[ device ].name << ")!"; + errorText_ = errorStream_.str(); + return FAILURE; + } + + DSCAPS outCaps; + outCaps.dwSize = sizeof( outCaps ); + result = output->GetCaps( &outCaps ); + if ( FAILED( result ) ) { + output->Release(); + errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") getting capabilities (" << dsDevices[ device ].name << ")!"; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Check channel information. + if ( channels + firstChannel == 2 && !( outCaps.dwFlags & DSCAPS_PRIMARYSTEREO ) ) { + errorStream_ << "RtApiDs::getDeviceInfo: the output device (" << dsDevices[ device ].name << ") does not support stereo playback."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Check format information. Use 16-bit format unless not + // supported or user requests 8-bit. + if ( outCaps.dwFlags & DSCAPS_PRIMARY16BIT && + !( format == RTAUDIO_SINT8 && outCaps.dwFlags & DSCAPS_PRIMARY8BIT ) ) { + waveFormat.wBitsPerSample = 16; + stream_.deviceFormat[mode] = RTAUDIO_SINT16; + } + else { + waveFormat.wBitsPerSample = 8; + stream_.deviceFormat[mode] = RTAUDIO_SINT8; + } + stream_.userFormat = format; + + // Update wave format structure and buffer information. + waveFormat.nBlockAlign = waveFormat.nChannels * waveFormat.wBitsPerSample / 8; + waveFormat.nAvgBytesPerSec = waveFormat.nSamplesPerSec * waveFormat.nBlockAlign; + dsPointerLeadTime = nBuffers * (*bufferSize) * (waveFormat.wBitsPerSample / 8) * channels; + + // If the user wants an even bigger buffer, increase the device buffer size accordingly. + while ( dsPointerLeadTime * 2U > dsBufferSize ) + dsBufferSize *= 2; + + // Set cooperative level to DSSCL_EXCLUSIVE ... sound stops when window focus changes. + // result = output->SetCooperativeLevel( hWnd, DSSCL_EXCLUSIVE ); + // Set cooperative level to DSSCL_PRIORITY ... sound remains when window focus changes. + result = output->SetCooperativeLevel( hWnd, DSSCL_PRIORITY ); + if ( FAILED( result ) ) { + output->Release(); + errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") setting cooperative level (" << dsDevices[ device ].name << ")!"; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Even though we will write to the secondary buffer, we need to + // access the primary buffer to set the correct output format + // (since the default is 8-bit, 22 kHz!). Setup the DS primary + // buffer description. + DSBUFFERDESC bufferDescription; + ZeroMemory( &bufferDescription, sizeof( DSBUFFERDESC ) ); + bufferDescription.dwSize = sizeof( DSBUFFERDESC ); + bufferDescription.dwFlags = DSBCAPS_PRIMARYBUFFER; + + // Obtain the primary buffer + LPDIRECTSOUNDBUFFER buffer; + result = output->CreateSoundBuffer( &bufferDescription, &buffer, NULL ); + if ( FAILED( result ) ) { + output->Release(); + errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") accessing primary buffer (" << dsDevices[ device ].name << ")!"; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Set the primary DS buffer sound format. + result = buffer->SetFormat( &waveFormat ); + if ( FAILED( result ) ) { + output->Release(); + errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") setting primary buffer format (" << dsDevices[ device ].name << ")!"; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Setup the secondary DS buffer description. + ZeroMemory( &bufferDescription, sizeof( DSBUFFERDESC ) ); + bufferDescription.dwSize = sizeof( DSBUFFERDESC ); + bufferDescription.dwFlags = ( DSBCAPS_STICKYFOCUS | + DSBCAPS_GLOBALFOCUS | + DSBCAPS_GETCURRENTPOSITION2 | + DSBCAPS_LOCHARDWARE ); // Force hardware mixing + bufferDescription.dwBufferBytes = dsBufferSize; + bufferDescription.lpwfxFormat = &waveFormat; + + // Try to create the secondary DS buffer. If that doesn't work, + // try to use software mixing. Otherwise, there's a problem. + result = output->CreateSoundBuffer( &bufferDescription, &buffer, NULL ); + if ( FAILED( result ) ) { + bufferDescription.dwFlags = ( DSBCAPS_STICKYFOCUS | + DSBCAPS_GLOBALFOCUS | + DSBCAPS_GETCURRENTPOSITION2 | + DSBCAPS_LOCSOFTWARE ); // Force software mixing + result = output->CreateSoundBuffer( &bufferDescription, &buffer, NULL ); + if ( FAILED( result ) ) { + output->Release(); + errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") creating secondary buffer (" << dsDevices[ device ].name << ")!"; + errorText_ = errorStream_.str(); + return FAILURE; + } + } + + // Get the buffer size ... might be different from what we specified. + DSBCAPS dsbcaps; + dsbcaps.dwSize = sizeof( DSBCAPS ); + result = buffer->GetCaps( &dsbcaps ); + if ( FAILED( result ) ) { + output->Release(); + buffer->Release(); + errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") getting buffer settings (" << dsDevices[ device ].name << ")!"; + errorText_ = errorStream_.str(); + return FAILURE; + } + + dsBufferSize = dsbcaps.dwBufferBytes; + + // Lock the DS buffer + LPVOID audioPtr; + DWORD dataLen; + result = buffer->Lock( 0, dsBufferSize, &audioPtr, &dataLen, NULL, NULL, 0 ); + if ( FAILED( result ) ) { + output->Release(); + buffer->Release(); + errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") locking buffer (" << dsDevices[ device ].name << ")!"; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Zero the DS buffer + ZeroMemory( audioPtr, dataLen ); + + // Unlock the DS buffer + result = buffer->Unlock( audioPtr, dataLen, NULL, 0 ); + if ( FAILED( result ) ) { + output->Release(); + buffer->Release(); + errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") unlocking buffer (" << dsDevices[ device ].name << ")!"; + errorText_ = errorStream_.str(); + return FAILURE; + } + + ohandle = (void *) output; + bhandle = (void *) buffer; + } + + if ( mode == INPUT ) { + + LPDIRECTSOUNDCAPTURE input; + result = DirectSoundCaptureCreate( dsDevices[ device ].id[1], &input, NULL ); + if ( FAILED( result ) ) { + errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") opening input device (" << dsDevices[ device ].name << ")!"; + errorText_ = errorStream_.str(); + return FAILURE; + } + + DSCCAPS inCaps; + inCaps.dwSize = sizeof( inCaps ); + result = input->GetCaps( &inCaps ); + if ( FAILED( result ) ) { + input->Release(); + errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") getting input capabilities (" << dsDevices[ device ].name << ")!"; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Check channel information. + if ( inCaps.dwChannels < channels + firstChannel ) { + errorText_ = "RtApiDs::getDeviceInfo: the input device does not support requested input channels."; + return FAILURE; + } + + // Check format information. Use 16-bit format unless user + // requests 8-bit. + DWORD deviceFormats; + if ( channels + firstChannel == 2 ) { + deviceFormats = WAVE_FORMAT_1S08 | WAVE_FORMAT_2S08 | WAVE_FORMAT_4S08 | WAVE_FORMAT_96S08; + if ( format == RTAUDIO_SINT8 && inCaps.dwFormats & deviceFormats ) { + waveFormat.wBitsPerSample = 8; + stream_.deviceFormat[mode] = RTAUDIO_SINT8; + } + else { // assume 16-bit is supported + waveFormat.wBitsPerSample = 16; + stream_.deviceFormat[mode] = RTAUDIO_SINT16; + } + } + else { // channel == 1 + deviceFormats = WAVE_FORMAT_1M08 | WAVE_FORMAT_2M08 | WAVE_FORMAT_4M08 | WAVE_FORMAT_96M08; + if ( format == RTAUDIO_SINT8 && inCaps.dwFormats & deviceFormats ) { + waveFormat.wBitsPerSample = 8; + stream_.deviceFormat[mode] = RTAUDIO_SINT8; + } + else { // assume 16-bit is supported + waveFormat.wBitsPerSample = 16; + stream_.deviceFormat[mode] = RTAUDIO_SINT16; + } + } + stream_.userFormat = format; + + // Update wave format structure and buffer information. + waveFormat.nBlockAlign = waveFormat.nChannels * waveFormat.wBitsPerSample / 8; + waveFormat.nAvgBytesPerSec = waveFormat.nSamplesPerSec * waveFormat.nBlockAlign; + dsPointerLeadTime = nBuffers * (*bufferSize) * (waveFormat.wBitsPerSample / 8) * channels; + + // If the user wants an even bigger buffer, increase the device buffer size accordingly. + while ( dsPointerLeadTime * 2U > dsBufferSize ) + dsBufferSize *= 2; + + // Setup the secondary DS buffer description. + DSCBUFFERDESC bufferDescription; + ZeroMemory( &bufferDescription, sizeof( DSCBUFFERDESC ) ); + bufferDescription.dwSize = sizeof( DSCBUFFERDESC ); + bufferDescription.dwFlags = 0; + bufferDescription.dwReserved = 0; + bufferDescription.dwBufferBytes = dsBufferSize; + bufferDescription.lpwfxFormat = &waveFormat; + + // Create the capture buffer. + LPDIRECTSOUNDCAPTUREBUFFER buffer; + result = input->CreateCaptureBuffer( &bufferDescription, &buffer, NULL ); + if ( FAILED( result ) ) { + input->Release(); + errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") creating input buffer (" << dsDevices[ device ].name << ")!"; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Get the buffer size ... might be different from what we specified. + DSCBCAPS dscbcaps; + dscbcaps.dwSize = sizeof( DSCBCAPS ); + result = buffer->GetCaps( &dscbcaps ); + if ( FAILED( result ) ) { + input->Release(); + buffer->Release(); + errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") getting buffer settings (" << dsDevices[ device ].name << ")!"; + errorText_ = errorStream_.str(); + return FAILURE; + } + + dsBufferSize = dscbcaps.dwBufferBytes; + + // NOTE: We could have a problem here if this is a duplex stream + // and the play and capture hardware buffer sizes are different + // (I'm actually not sure if that is a problem or not). + // Currently, we are not verifying that. + + // Lock the capture buffer + LPVOID audioPtr; + DWORD dataLen; + result = buffer->Lock( 0, dsBufferSize, &audioPtr, &dataLen, NULL, NULL, 0 ); + if ( FAILED( result ) ) { + input->Release(); + buffer->Release(); + errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") locking input buffer (" << dsDevices[ device ].name << ")!"; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Zero the buffer + ZeroMemory( audioPtr, dataLen ); + + // Unlock the buffer + result = buffer->Unlock( audioPtr, dataLen, NULL, 0 ); + if ( FAILED( result ) ) { + input->Release(); + buffer->Release(); + errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") unlocking input buffer (" << dsDevices[ device ].name << ")!"; + errorText_ = errorStream_.str(); + return FAILURE; + } + + ohandle = (void *) input; + bhandle = (void *) buffer; + } + + // Set various stream parameters + DsHandle *handle = 0; + stream_.nDeviceChannels[mode] = channels + firstChannel; + stream_.nUserChannels[mode] = channels; + stream_.bufferSize = *bufferSize; + stream_.channelOffset[mode] = firstChannel; + stream_.deviceInterleaved[mode] = true; + if ( options && options->flags & RTAUDIO_NONINTERLEAVED ) stream_.userInterleaved = false; + else stream_.userInterleaved = true; + + // Set flag for buffer conversion + stream_.doConvertBuffer[mode] = false; + if (stream_.nUserChannels[mode] != stream_.nDeviceChannels[mode]) + stream_.doConvertBuffer[mode] = true; + if (stream_.userFormat != stream_.deviceFormat[mode]) + stream_.doConvertBuffer[mode] = true; + if ( stream_.userInterleaved != stream_.deviceInterleaved[mode] && + stream_.nUserChannels[mode] > 1 ) + stream_.doConvertBuffer[mode] = true; + + // Allocate necessary internal buffers + long bufferBytes = stream_.nUserChannels[mode] * *bufferSize * formatBytes( stream_.userFormat ); + stream_.userBuffer[mode] = (char *) calloc( bufferBytes, 1 ); + if ( stream_.userBuffer[mode] == NULL ) { + errorText_ = "RtApiDs::probeDeviceOpen: error allocating user buffer memory."; + goto error; + } + + if ( stream_.doConvertBuffer[mode] ) { + + bool makeBuffer = true; + bufferBytes = stream_.nDeviceChannels[mode] * formatBytes( stream_.deviceFormat[mode] ); + if ( mode == INPUT ) { + if ( stream_.mode == OUTPUT && stream_.deviceBuffer ) { + unsigned long bytesOut = stream_.nDeviceChannels[0] * formatBytes( stream_.deviceFormat[0] ); + if ( bufferBytes <= (long) bytesOut ) makeBuffer = false; + } + } + + if ( makeBuffer ) { + bufferBytes *= *bufferSize; + if ( stream_.deviceBuffer ) free( stream_.deviceBuffer ); + stream_.deviceBuffer = (char *) calloc( bufferBytes, 1 ); + if ( stream_.deviceBuffer == NULL ) { + errorText_ = "RtApiDs::probeDeviceOpen: error allocating device buffer memory."; + goto error; + } + } + } + + // Allocate our DsHandle structures for the stream. + if ( stream_.apiHandle == 0 ) { + try { + handle = new DsHandle; + } + catch ( std::bad_alloc& ) { + errorText_ = "RtApiDs::probeDeviceOpen: error allocating AsioHandle memory."; + goto error; + } + + // Create a manual-reset event. + handle->condition = CreateEvent( NULL, // no security + TRUE, // manual-reset + FALSE, // non-signaled initially + NULL ); // unnamed + stream_.apiHandle = (void *) handle; + } + else + handle = (DsHandle *) stream_.apiHandle; + handle->id[mode] = ohandle; + handle->buffer[mode] = bhandle; + handle->dsBufferSize[mode] = dsBufferSize; + handle->dsPointerLeadTime[mode] = dsPointerLeadTime; + + stream_.device[mode] = device; + stream_.state = STREAM_STOPPED; + if ( stream_.mode == OUTPUT && mode == INPUT ) + // We had already set up an output stream. + stream_.mode = DUPLEX; + else + stream_.mode = mode; + stream_.nBuffers = nBuffers; + stream_.sampleRate = sampleRate; + + // Setup the buffer conversion information structure. + if ( stream_.doConvertBuffer[mode] ) setConvertInfo( mode, firstChannel ); + + // Setup the callback thread. + if ( stream_.callbackInfo.isRunning == false ) { + unsigned threadId; + stream_.callbackInfo.isRunning = true; + stream_.callbackInfo.object = (void *) this; + stream_.callbackInfo.thread = _beginthreadex( NULL, 0, &callbackHandler, + &stream_.callbackInfo, 0, &threadId ); + if ( stream_.callbackInfo.thread == 0 ) { + errorText_ = "RtApiDs::probeDeviceOpen: error creating callback thread!"; + goto error; + } + + // Boost DS thread priority + SetThreadPriority( (HANDLE) stream_.callbackInfo.thread, THREAD_PRIORITY_HIGHEST ); + } + return SUCCESS; + + error: + if ( handle ) { + if ( handle->buffer[0] ) { // the object pointer can be NULL and valid + LPDIRECTSOUND object = (LPDIRECTSOUND) handle->id[0]; + LPDIRECTSOUNDBUFFER buffer = (LPDIRECTSOUNDBUFFER) handle->buffer[0]; + if ( buffer ) buffer->Release(); + object->Release(); + } + if ( handle->buffer[1] ) { + LPDIRECTSOUNDCAPTURE object = (LPDIRECTSOUNDCAPTURE) handle->id[1]; + LPDIRECTSOUNDCAPTUREBUFFER buffer = (LPDIRECTSOUNDCAPTUREBUFFER) handle->buffer[1]; + if ( buffer ) buffer->Release(); + object->Release(); + } + CloseHandle( handle->condition ); + delete handle; + stream_.apiHandle = 0; + } + + for ( int i=0; i<2; i++ ) { + if ( stream_.userBuffer[i] ) { + free( stream_.userBuffer[i] ); + stream_.userBuffer[i] = 0; + } + } + + if ( stream_.deviceBuffer ) { + free( stream_.deviceBuffer ); + stream_.deviceBuffer = 0; + } + + stream_.state = STREAM_CLOSED; + return FAILURE; +} + +void RtApiDs :: closeStream() +{ + if ( stream_.state == STREAM_CLOSED ) { + errorText_ = "RtApiDs::closeStream(): no open stream to close!"; + error( RtAudioError::WARNING ); + return; + } + + // Stop the callback thread. + stream_.callbackInfo.isRunning = false; + WaitForSingleObject( (HANDLE) stream_.callbackInfo.thread, INFINITE ); + CloseHandle( (HANDLE) stream_.callbackInfo.thread ); + + DsHandle *handle = (DsHandle *) stream_.apiHandle; + if ( handle ) { + if ( handle->buffer[0] ) { // the object pointer can be NULL and valid + LPDIRECTSOUND object = (LPDIRECTSOUND) handle->id[0]; + LPDIRECTSOUNDBUFFER buffer = (LPDIRECTSOUNDBUFFER) handle->buffer[0]; + if ( buffer ) { + buffer->Stop(); + buffer->Release(); + } + object->Release(); + } + if ( handle->buffer[1] ) { + LPDIRECTSOUNDCAPTURE object = (LPDIRECTSOUNDCAPTURE) handle->id[1]; + LPDIRECTSOUNDCAPTUREBUFFER buffer = (LPDIRECTSOUNDCAPTUREBUFFER) handle->buffer[1]; + if ( buffer ) { + buffer->Stop(); + buffer->Release(); + } + object->Release(); + } + CloseHandle( handle->condition ); + delete handle; + stream_.apiHandle = 0; + } + + for ( int i=0; i<2; i++ ) { + if ( stream_.userBuffer[i] ) { + free( stream_.userBuffer[i] ); + stream_.userBuffer[i] = 0; + } + } + + if ( stream_.deviceBuffer ) { + free( stream_.deviceBuffer ); + stream_.deviceBuffer = 0; + } + + stream_.mode = UNINITIALIZED; + stream_.state = STREAM_CLOSED; +} + +void RtApiDs :: startStream() +{ + verifyStream(); + if ( stream_.state == STREAM_RUNNING ) { + errorText_ = "RtApiDs::startStream(): the stream is already running!"; + error( RtAudioError::WARNING ); + return; + } + + DsHandle *handle = (DsHandle *) stream_.apiHandle; + + // Increase scheduler frequency on lesser windows (a side-effect of + // increasing timer accuracy). On greater windows (Win2K or later), + // this is already in effect. + timeBeginPeriod( 1 ); + + buffersRolling = false; + duplexPrerollBytes = 0; + + if ( stream_.mode == DUPLEX ) { + // 0.5 seconds of silence in DUPLEX mode while the devices spin up and synchronize. + duplexPrerollBytes = (int) ( 0.5 * stream_.sampleRate * formatBytes( stream_.deviceFormat[1] ) * stream_.nDeviceChannels[1] ); + } + + HRESULT result = 0; + if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { + + LPDIRECTSOUNDBUFFER buffer = (LPDIRECTSOUNDBUFFER) handle->buffer[0]; + result = buffer->Play( 0, 0, DSBPLAY_LOOPING ); + if ( FAILED( result ) ) { + errorStream_ << "RtApiDs::startStream: error (" << getErrorString( result ) << ") starting output buffer!"; + errorText_ = errorStream_.str(); + goto unlock; + } + } + + if ( stream_.mode == INPUT || stream_.mode == DUPLEX ) { + + LPDIRECTSOUNDCAPTUREBUFFER buffer = (LPDIRECTSOUNDCAPTUREBUFFER) handle->buffer[1]; + result = buffer->Start( DSCBSTART_LOOPING ); + if ( FAILED( result ) ) { + errorStream_ << "RtApiDs::startStream: error (" << getErrorString( result ) << ") starting input buffer!"; + errorText_ = errorStream_.str(); + goto unlock; + } + } + + handle->drainCounter = 0; + handle->internalDrain = false; + ResetEvent( handle->condition ); + stream_.state = STREAM_RUNNING; + + unlock: + if ( FAILED( result ) ) error( RtAudioError::SYSTEM_ERROR ); +} + +void RtApiDs :: stopStream() +{ + verifyStream(); + if ( stream_.state == STREAM_STOPPED ) { + errorText_ = "RtApiDs::stopStream(): the stream is already stopped!"; + error( RtAudioError::WARNING ); + return; + } + + HRESULT result = 0; + LPVOID audioPtr; + DWORD dataLen; + DsHandle *handle = (DsHandle *) stream_.apiHandle; + if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { + if ( handle->drainCounter == 0 ) { + handle->drainCounter = 2; + WaitForSingleObject( handle->condition, INFINITE ); // block until signaled + } + + stream_.state = STREAM_STOPPED; + + MUTEX_LOCK( &stream_.mutex ); + + // Stop the buffer and clear memory + LPDIRECTSOUNDBUFFER buffer = (LPDIRECTSOUNDBUFFER) handle->buffer[0]; + result = buffer->Stop(); + if ( FAILED( result ) ) { + errorStream_ << "RtApiDs::stopStream: error (" << getErrorString( result ) << ") stopping output buffer!"; + errorText_ = errorStream_.str(); + goto unlock; + } + + // Lock the buffer and clear it so that if we start to play again, + // we won't have old data playing. + result = buffer->Lock( 0, handle->dsBufferSize[0], &audioPtr, &dataLen, NULL, NULL, 0 ); + if ( FAILED( result ) ) { + errorStream_ << "RtApiDs::stopStream: error (" << getErrorString( result ) << ") locking output buffer!"; + errorText_ = errorStream_.str(); + goto unlock; + } + + // Zero the DS buffer + ZeroMemory( audioPtr, dataLen ); + + // Unlock the DS buffer + result = buffer->Unlock( audioPtr, dataLen, NULL, 0 ); + if ( FAILED( result ) ) { + errorStream_ << "RtApiDs::stopStream: error (" << getErrorString( result ) << ") unlocking output buffer!"; + errorText_ = errorStream_.str(); + goto unlock; + } + + // If we start playing again, we must begin at beginning of buffer. + handle->bufferPointer[0] = 0; + } + + if ( stream_.mode == INPUT || stream_.mode == DUPLEX ) { + LPDIRECTSOUNDCAPTUREBUFFER buffer = (LPDIRECTSOUNDCAPTUREBUFFER) handle->buffer[1]; + audioPtr = NULL; + dataLen = 0; + + stream_.state = STREAM_STOPPED; + + if ( stream_.mode != DUPLEX ) + MUTEX_LOCK( &stream_.mutex ); + + result = buffer->Stop(); + if ( FAILED( result ) ) { + errorStream_ << "RtApiDs::stopStream: error (" << getErrorString( result ) << ") stopping input buffer!"; + errorText_ = errorStream_.str(); + goto unlock; + } + + // Lock the buffer and clear it so that if we start to play again, + // we won't have old data playing. + result = buffer->Lock( 0, handle->dsBufferSize[1], &audioPtr, &dataLen, NULL, NULL, 0 ); + if ( FAILED( result ) ) { + errorStream_ << "RtApiDs::stopStream: error (" << getErrorString( result ) << ") locking input buffer!"; + errorText_ = errorStream_.str(); + goto unlock; + } + + // Zero the DS buffer + ZeroMemory( audioPtr, dataLen ); + + // Unlock the DS buffer + result = buffer->Unlock( audioPtr, dataLen, NULL, 0 ); + if ( FAILED( result ) ) { + errorStream_ << "RtApiDs::stopStream: error (" << getErrorString( result ) << ") unlocking input buffer!"; + errorText_ = errorStream_.str(); + goto unlock; + } + + // If we start recording again, we must begin at beginning of buffer. + handle->bufferPointer[1] = 0; + } + + unlock: + timeEndPeriod( 1 ); // revert to normal scheduler frequency on lesser windows. + MUTEX_UNLOCK( &stream_.mutex ); + + if ( FAILED( result ) ) error( RtAudioError::SYSTEM_ERROR ); +} + +void RtApiDs :: abortStream() +{ + verifyStream(); + if ( stream_.state == STREAM_STOPPED ) { + errorText_ = "RtApiDs::abortStream(): the stream is already stopped!"; + error( RtAudioError::WARNING ); + return; + } + + DsHandle *handle = (DsHandle *) stream_.apiHandle; + handle->drainCounter = 2; + + stopStream(); +} + +void RtApiDs :: callbackEvent() +{ + if ( stream_.state == STREAM_STOPPED || stream_.state == STREAM_STOPPING ) { + Sleep( 50 ); // sleep 50 milliseconds + return; + } + + if ( stream_.state == STREAM_CLOSED ) { + errorText_ = "RtApiDs::callbackEvent(): the stream is closed ... this shouldn't happen!"; + error( RtAudioError::WARNING ); + return; + } + + CallbackInfo *info = (CallbackInfo *) &stream_.callbackInfo; + DsHandle *handle = (DsHandle *) stream_.apiHandle; + + // Check if we were draining the stream and signal is finished. + if ( handle->drainCounter > stream_.nBuffers + 2 ) { + + stream_.state = STREAM_STOPPING; + if ( handle->internalDrain == false ) + SetEvent( handle->condition ); + else + stopStream(); + return; + } + + // Invoke user callback to get fresh output data UNLESS we are + // draining stream. + if ( handle->drainCounter == 0 ) { + RtAudioCallback callback = (RtAudioCallback) info->callback; + double streamTime = getStreamTime(); + RtAudioStreamStatus status = 0; + if ( stream_.mode != INPUT && handle->xrun[0] == true ) { + status |= RTAUDIO_OUTPUT_UNDERFLOW; + handle->xrun[0] = false; + } + if ( stream_.mode != OUTPUT && handle->xrun[1] == true ) { + status |= RTAUDIO_INPUT_OVERFLOW; + handle->xrun[1] = false; + } + int cbReturnValue = callback( stream_.userBuffer[0], stream_.userBuffer[1], + stream_.bufferSize, streamTime, status, info->userData ); + if ( cbReturnValue == 2 ) { + stream_.state = STREAM_STOPPING; + handle->drainCounter = 2; + abortStream(); + return; + } + else if ( cbReturnValue == 1 ) { + handle->drainCounter = 1; + handle->internalDrain = true; + } + } + + HRESULT result; + DWORD currentWritePointer, safeWritePointer; + DWORD currentReadPointer, safeReadPointer; + UINT nextWritePointer; + + LPVOID buffer1 = NULL; + LPVOID buffer2 = NULL; + DWORD bufferSize1 = 0; + DWORD bufferSize2 = 0; + + char *buffer; + long bufferBytes; + + MUTEX_LOCK( &stream_.mutex ); + if ( stream_.state == STREAM_STOPPED ) { + MUTEX_UNLOCK( &stream_.mutex ); + return; + } + + if ( buffersRolling == false ) { + if ( stream_.mode == DUPLEX ) { + //assert( handle->dsBufferSize[0] == handle->dsBufferSize[1] ); + + // It takes a while for the devices to get rolling. As a result, + // there's no guarantee that the capture and write device pointers + // will move in lockstep. Wait here for both devices to start + // rolling, and then set our buffer pointers accordingly. + // e.g. Crystal Drivers: the capture buffer starts up 5700 to 9600 + // bytes later than the write buffer. + + // Stub: a serious risk of having a pre-emptive scheduling round + // take place between the two GetCurrentPosition calls... but I'm + // really not sure how to solve the problem. Temporarily boost to + // Realtime priority, maybe; but I'm not sure what priority the + // DirectSound service threads run at. We *should* be roughly + // within a ms or so of correct. + + LPDIRECTSOUNDBUFFER dsWriteBuffer = (LPDIRECTSOUNDBUFFER) handle->buffer[0]; + LPDIRECTSOUNDCAPTUREBUFFER dsCaptureBuffer = (LPDIRECTSOUNDCAPTUREBUFFER) handle->buffer[1]; + + DWORD startSafeWritePointer, startSafeReadPointer; + + result = dsWriteBuffer->GetCurrentPosition( NULL, &startSafeWritePointer ); + if ( FAILED( result ) ) { + errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current write position!"; + errorText_ = errorStream_.str(); + MUTEX_UNLOCK( &stream_.mutex ); + error( RtAudioError::SYSTEM_ERROR ); + return; + } + result = dsCaptureBuffer->GetCurrentPosition( NULL, &startSafeReadPointer ); + if ( FAILED( result ) ) { + errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current read position!"; + errorText_ = errorStream_.str(); + MUTEX_UNLOCK( &stream_.mutex ); + error( RtAudioError::SYSTEM_ERROR ); + return; + } + while ( true ) { + result = dsWriteBuffer->GetCurrentPosition( NULL, &safeWritePointer ); + if ( FAILED( result ) ) { + errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current write position!"; + errorText_ = errorStream_.str(); + MUTEX_UNLOCK( &stream_.mutex ); + error( RtAudioError::SYSTEM_ERROR ); + return; + } + result = dsCaptureBuffer->GetCurrentPosition( NULL, &safeReadPointer ); + if ( FAILED( result ) ) { + errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current read position!"; + errorText_ = errorStream_.str(); + MUTEX_UNLOCK( &stream_.mutex ); + error( RtAudioError::SYSTEM_ERROR ); + return; + } + if ( safeWritePointer != startSafeWritePointer && safeReadPointer != startSafeReadPointer ) break; + Sleep( 1 ); + } + + //assert( handle->dsBufferSize[0] == handle->dsBufferSize[1] ); + + handle->bufferPointer[0] = safeWritePointer + handle->dsPointerLeadTime[0]; + if ( handle->bufferPointer[0] >= handle->dsBufferSize[0] ) handle->bufferPointer[0] -= handle->dsBufferSize[0]; + handle->bufferPointer[1] = safeReadPointer; + } + else if ( stream_.mode == OUTPUT ) { + + // Set the proper nextWritePosition after initial startup. + LPDIRECTSOUNDBUFFER dsWriteBuffer = (LPDIRECTSOUNDBUFFER) handle->buffer[0]; + result = dsWriteBuffer->GetCurrentPosition( ¤tWritePointer, &safeWritePointer ); + if ( FAILED( result ) ) { + errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current write position!"; + errorText_ = errorStream_.str(); + MUTEX_UNLOCK( &stream_.mutex ); + error( RtAudioError::SYSTEM_ERROR ); + return; + } + handle->bufferPointer[0] = safeWritePointer + handle->dsPointerLeadTime[0]; + if ( handle->bufferPointer[0] >= handle->dsBufferSize[0] ) handle->bufferPointer[0] -= handle->dsBufferSize[0]; + } + + buffersRolling = true; + } + + if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { + + LPDIRECTSOUNDBUFFER dsBuffer = (LPDIRECTSOUNDBUFFER) handle->buffer[0]; + + if ( handle->drainCounter > 1 ) { // write zeros to the output stream + bufferBytes = stream_.bufferSize * stream_.nUserChannels[0]; + bufferBytes *= formatBytes( stream_.userFormat ); + memset( stream_.userBuffer[0], 0, bufferBytes ); + } + + // Setup parameters and do buffer conversion if necessary. + if ( stream_.doConvertBuffer[0] ) { + buffer = stream_.deviceBuffer; + convertBuffer( buffer, stream_.userBuffer[0], stream_.convertInfo[0] ); + bufferBytes = stream_.bufferSize * stream_.nDeviceChannels[0]; + bufferBytes *= formatBytes( stream_.deviceFormat[0] ); + } + else { + buffer = stream_.userBuffer[0]; + bufferBytes = stream_.bufferSize * stream_.nUserChannels[0]; + bufferBytes *= formatBytes( stream_.userFormat ); + } + + // No byte swapping necessary in DirectSound implementation. + + // Ahhh ... windoze. 16-bit data is signed but 8-bit data is + // unsigned. So, we need to convert our signed 8-bit data here to + // unsigned. + if ( stream_.deviceFormat[0] == RTAUDIO_SINT8 ) + for ( int i=0; idsBufferSize[0]; + nextWritePointer = handle->bufferPointer[0]; + + DWORD endWrite, leadPointer; + while ( true ) { + // Find out where the read and "safe write" pointers are. + result = dsBuffer->GetCurrentPosition( ¤tWritePointer, &safeWritePointer ); + if ( FAILED( result ) ) { + errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current write position!"; + errorText_ = errorStream_.str(); + MUTEX_UNLOCK( &stream_.mutex ); + error( RtAudioError::SYSTEM_ERROR ); + return; + } + + // We will copy our output buffer into the region between + // safeWritePointer and leadPointer. If leadPointer is not + // beyond the next endWrite position, wait until it is. + leadPointer = safeWritePointer + handle->dsPointerLeadTime[0]; + //std::cout << "safeWritePointer = " << safeWritePointer << ", leadPointer = " << leadPointer << ", nextWritePointer = " << nextWritePointer << std::endl; + if ( leadPointer > dsBufferSize ) leadPointer -= dsBufferSize; + if ( leadPointer < nextWritePointer ) leadPointer += dsBufferSize; // unwrap offset + endWrite = nextWritePointer + bufferBytes; + + // Check whether the entire write region is behind the play pointer. + if ( leadPointer >= endWrite ) break; + + // If we are here, then we must wait until the leadPointer advances + // beyond the end of our next write region. We use the + // Sleep() function to suspend operation until that happens. + double millis = ( endWrite - leadPointer ) * 1000.0; + millis /= ( formatBytes( stream_.deviceFormat[0]) * stream_.nDeviceChannels[0] * stream_.sampleRate); + if ( millis < 1.0 ) millis = 1.0; + Sleep( (DWORD) millis ); + } + + if ( dsPointerBetween( nextWritePointer, safeWritePointer, currentWritePointer, dsBufferSize ) + || dsPointerBetween( endWrite, safeWritePointer, currentWritePointer, dsBufferSize ) ) { + // We've strayed into the forbidden zone ... resync the read pointer. + handle->xrun[0] = true; + nextWritePointer = safeWritePointer + handle->dsPointerLeadTime[0] - bufferBytes; + if ( nextWritePointer >= dsBufferSize ) nextWritePointer -= dsBufferSize; + handle->bufferPointer[0] = nextWritePointer; + endWrite = nextWritePointer + bufferBytes; + } + + // Lock free space in the buffer + result = dsBuffer->Lock( nextWritePointer, bufferBytes, &buffer1, + &bufferSize1, &buffer2, &bufferSize2, 0 ); + if ( FAILED( result ) ) { + errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") locking buffer during playback!"; + errorText_ = errorStream_.str(); + MUTEX_UNLOCK( &stream_.mutex ); + error( RtAudioError::SYSTEM_ERROR ); + return; + } + + // Copy our buffer into the DS buffer + CopyMemory( buffer1, buffer, bufferSize1 ); + if ( buffer2 != NULL ) CopyMemory( buffer2, buffer+bufferSize1, bufferSize2 ); + + // Update our buffer offset and unlock sound buffer + dsBuffer->Unlock( buffer1, bufferSize1, buffer2, bufferSize2 ); + if ( FAILED( result ) ) { + errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") unlocking buffer during playback!"; + errorText_ = errorStream_.str(); + MUTEX_UNLOCK( &stream_.mutex ); + error( RtAudioError::SYSTEM_ERROR ); + return; + } + nextWritePointer = ( nextWritePointer + bufferSize1 + bufferSize2 ) % dsBufferSize; + handle->bufferPointer[0] = nextWritePointer; + } + + // Don't bother draining input + if ( handle->drainCounter ) { + handle->drainCounter++; + goto unlock; + } + + if ( stream_.mode == INPUT || stream_.mode == DUPLEX ) { + + // Setup parameters. + if ( stream_.doConvertBuffer[1] ) { + buffer = stream_.deviceBuffer; + bufferBytes = stream_.bufferSize * stream_.nDeviceChannels[1]; + bufferBytes *= formatBytes( stream_.deviceFormat[1] ); + } + else { + buffer = stream_.userBuffer[1]; + bufferBytes = stream_.bufferSize * stream_.nUserChannels[1]; + bufferBytes *= formatBytes( stream_.userFormat ); + } + + LPDIRECTSOUNDCAPTUREBUFFER dsBuffer = (LPDIRECTSOUNDCAPTUREBUFFER) handle->buffer[1]; + long nextReadPointer = handle->bufferPointer[1]; + DWORD dsBufferSize = handle->dsBufferSize[1]; + + // Find out where the write and "safe read" pointers are. + result = dsBuffer->GetCurrentPosition( ¤tReadPointer, &safeReadPointer ); + if ( FAILED( result ) ) { + errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current read position!"; + errorText_ = errorStream_.str(); + MUTEX_UNLOCK( &stream_.mutex ); + error( RtAudioError::SYSTEM_ERROR ); + return; + } + + if ( safeReadPointer < (DWORD)nextReadPointer ) safeReadPointer += dsBufferSize; // unwrap offset + DWORD endRead = nextReadPointer + bufferBytes; + + // Handling depends on whether we are INPUT or DUPLEX. + // If we're in INPUT mode then waiting is a good thing. If we're in DUPLEX mode, + // then a wait here will drag the write pointers into the forbidden zone. + // + // In DUPLEX mode, rather than wait, we will back off the read pointer until + // it's in a safe position. This causes dropouts, but it seems to be the only + // practical way to sync up the read and write pointers reliably, given the + // the very complex relationship between phase and increment of the read and write + // pointers. + // + // In order to minimize audible dropouts in DUPLEX mode, we will + // provide a pre-roll period of 0.5 seconds in which we return + // zeros from the read buffer while the pointers sync up. + + if ( stream_.mode == DUPLEX ) { + if ( safeReadPointer < endRead ) { + if ( duplexPrerollBytes <= 0 ) { + // Pre-roll time over. Be more agressive. + int adjustment = endRead-safeReadPointer; + + handle->xrun[1] = true; + // Two cases: + // - large adjustments: we've probably run out of CPU cycles, so just resync exactly, + // and perform fine adjustments later. + // - small adjustments: back off by twice as much. + if ( adjustment >= 2*bufferBytes ) + nextReadPointer = safeReadPointer-2*bufferBytes; + else + nextReadPointer = safeReadPointer-bufferBytes-adjustment; + + if ( nextReadPointer < 0 ) nextReadPointer += dsBufferSize; + + } + else { + // In pre=roll time. Just do it. + nextReadPointer = safeReadPointer - bufferBytes; + while ( nextReadPointer < 0 ) nextReadPointer += dsBufferSize; + } + endRead = nextReadPointer + bufferBytes; + } + } + else { // mode == INPUT + while ( safeReadPointer < endRead && stream_.callbackInfo.isRunning ) { + // See comments for playback. + double millis = (endRead - safeReadPointer) * 1000.0; + millis /= ( formatBytes(stream_.deviceFormat[1]) * stream_.nDeviceChannels[1] * stream_.sampleRate); + if ( millis < 1.0 ) millis = 1.0; + Sleep( (DWORD) millis ); + + // Wake up and find out where we are now. + result = dsBuffer->GetCurrentPosition( ¤tReadPointer, &safeReadPointer ); + if ( FAILED( result ) ) { + errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current read position!"; + errorText_ = errorStream_.str(); + MUTEX_UNLOCK( &stream_.mutex ); + error( RtAudioError::SYSTEM_ERROR ); + return; + } + + if ( safeReadPointer < (DWORD)nextReadPointer ) safeReadPointer += dsBufferSize; // unwrap offset + } + } + + // Lock free space in the buffer + result = dsBuffer->Lock( nextReadPointer, bufferBytes, &buffer1, + &bufferSize1, &buffer2, &bufferSize2, 0 ); + if ( FAILED( result ) ) { + errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") locking capture buffer!"; + errorText_ = errorStream_.str(); + MUTEX_UNLOCK( &stream_.mutex ); + error( RtAudioError::SYSTEM_ERROR ); + return; + } + + if ( duplexPrerollBytes <= 0 ) { + // Copy our buffer into the DS buffer + CopyMemory( buffer, buffer1, bufferSize1 ); + if ( buffer2 != NULL ) CopyMemory( buffer+bufferSize1, buffer2, bufferSize2 ); + } + else { + memset( buffer, 0, bufferSize1 ); + if ( buffer2 != NULL ) memset( buffer + bufferSize1, 0, bufferSize2 ); + duplexPrerollBytes -= bufferSize1 + bufferSize2; + } + + // Update our buffer offset and unlock sound buffer + nextReadPointer = ( nextReadPointer + bufferSize1 + bufferSize2 ) % dsBufferSize; + dsBuffer->Unlock( buffer1, bufferSize1, buffer2, bufferSize2 ); + if ( FAILED( result ) ) { + errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") unlocking capture buffer!"; + errorText_ = errorStream_.str(); + MUTEX_UNLOCK( &stream_.mutex ); + error( RtAudioError::SYSTEM_ERROR ); + return; + } + handle->bufferPointer[1] = nextReadPointer; + + // No byte swapping necessary in DirectSound implementation. + + // If necessary, convert 8-bit data from unsigned to signed. + if ( stream_.deviceFormat[1] == RTAUDIO_SINT8 ) + for ( int j=0; jobject; + bool* isRunning = &info->isRunning; + + while ( *isRunning == true ) { + object->callbackEvent(); + } + + _endthreadex( 0 ); + return 0; +} + +static BOOL CALLBACK deviceQueryCallback( LPGUID lpguid, + LPCTSTR description, + LPCTSTR /*module*/, + LPVOID lpContext ) +{ + struct DsProbeData& probeInfo = *(struct DsProbeData*) lpContext; + std::vector& dsDevices = *probeInfo.dsDevices; + + HRESULT hr; + bool validDevice = false; + if ( probeInfo.isInput == true ) { + DSCCAPS caps; + LPDIRECTSOUNDCAPTURE object; + + hr = DirectSoundCaptureCreate( lpguid, &object, NULL ); + if ( hr != DS_OK ) return TRUE; + + caps.dwSize = sizeof(caps); + hr = object->GetCaps( &caps ); + if ( hr == DS_OK ) { + if ( caps.dwChannels > 0 && caps.dwFormats > 0 ) + validDevice = true; + } + object->Release(); + } + else { + DSCAPS caps; + LPDIRECTSOUND object; + hr = DirectSoundCreate( lpguid, &object, NULL ); + if ( hr != DS_OK ) return TRUE; + + caps.dwSize = sizeof(caps); + hr = object->GetCaps( &caps ); + if ( hr == DS_OK ) { + if ( caps.dwFlags & DSCAPS_PRIMARYMONO || caps.dwFlags & DSCAPS_PRIMARYSTEREO ) + validDevice = true; + } + object->Release(); + } + + // If good device, then save its name and guid. + std::string name = convertCharPointerToStdString( description ); + //if ( name == "Primary Sound Driver" || name == "Primary Sound Capture Driver" ) + if ( lpguid == NULL ) + name = "Default Device"; + if ( validDevice ) { + for ( unsigned int i=0; i +#include + + // A structure to hold various information related to the ALSA API + // implementation. +struct AlsaHandle { + snd_pcm_t *handles[2]; + bool synchronized; + bool xrun[2]; + pthread_cond_t runnable_cv; + bool runnable; + + AlsaHandle() + :synchronized(false), runnable(false) { xrun[0] = false; xrun[1] = false; } +}; + +static void *alsaCallbackHandler( void * ptr ); + +RtApiAlsa :: RtApiAlsa() +{ + // Nothing to do here. +} + +RtApiAlsa :: ~RtApiAlsa() +{ + if ( stream_.state != STREAM_CLOSED ) closeStream(); +} + +unsigned int RtApiAlsa :: getDeviceCount( void ) +{ + unsigned nDevices = 0; + int result, subdevice, card; + char name[64]; + snd_ctl_t *handle; + + // Count cards and devices + card = -1; + snd_card_next( &card ); + while ( card >= 0 ) { + sprintf( name, "hw:%d", card ); + result = snd_ctl_open( &handle, name, 0 ); + if ( result < 0 ) { + errorStream_ << "RtApiAlsa::getDeviceCount: control open, card = " << card << ", " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + goto nextcard; + } + subdevice = -1; + while( 1 ) { + result = snd_ctl_pcm_next_device( handle, &subdevice ); + if ( result < 0 ) { + errorStream_ << "RtApiAlsa::getDeviceCount: control next device, card = " << card << ", " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + break; + } + if ( subdevice < 0 ) + break; + nDevices++; + } + nextcard: + snd_ctl_close( handle ); + snd_card_next( &card ); + } + + result = snd_ctl_open( &handle, "default", 0 ); + if (result == 0) { + nDevices++; + snd_ctl_close( handle ); + } + + return nDevices; +} + +RtAudio::DeviceInfo RtApiAlsa :: getDeviceInfo( unsigned int device ) +{ + RtAudio::DeviceInfo info; + info.probed = false; + + unsigned nDevices = 0; + int result, subdevice, card; + char name[64]; + snd_ctl_t *chandle; + + // Count cards and devices + card = -1; + subdevice = -1; + snd_card_next( &card ); + while ( card >= 0 ) { + sprintf( name, "hw:%d", card ); + result = snd_ctl_open( &chandle, name, SND_CTL_NONBLOCK ); + if ( result < 0 ) { + errorStream_ << "RtApiAlsa::getDeviceInfo: control open, card = " << card << ", " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + goto nextcard; + } + subdevice = -1; + while( 1 ) { + result = snd_ctl_pcm_next_device( chandle, &subdevice ); + if ( result < 0 ) { + errorStream_ << "RtApiAlsa::getDeviceInfo: control next device, card = " << card << ", " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + break; + } + if ( subdevice < 0 ) break; + if ( nDevices == device ) { + sprintf( name, "hw:%d,%d", card, subdevice ); + goto foundDevice; + } + nDevices++; + } + nextcard: + snd_ctl_close( chandle ); + snd_card_next( &card ); + } + + result = snd_ctl_open( &chandle, "default", SND_CTL_NONBLOCK ); + if ( result == 0 ) { + if ( nDevices == device ) { + strcpy( name, "default" ); + goto foundDevice; + } + nDevices++; + } + + if ( nDevices == 0 ) { + errorText_ = "RtApiAlsa::getDeviceInfo: no devices found!"; + error( RtAudioError::INVALID_USE ); + return info; + } + + if ( device >= nDevices ) { + errorText_ = "RtApiAlsa::getDeviceInfo: device ID is invalid!"; + error( RtAudioError::INVALID_USE ); + return info; + } + + foundDevice: + + // If a stream is already open, we cannot probe the stream devices. + // Thus, use the saved results. + if ( stream_.state != STREAM_CLOSED && + ( stream_.device[0] == device || stream_.device[1] == device ) ) { + snd_ctl_close( chandle ); + if ( device >= devices_.size() ) { + errorText_ = "RtApiAlsa::getDeviceInfo: device ID was not present before stream was opened."; + error( RtAudioError::WARNING ); + return info; + } + return devices_[ device ]; + } + + int openMode = SND_PCM_ASYNC; + snd_pcm_stream_t stream; + snd_pcm_info_t *pcminfo; + snd_pcm_info_alloca( &pcminfo ); + snd_pcm_t *phandle; + snd_pcm_hw_params_t *params; + snd_pcm_hw_params_alloca( ¶ms ); + + // First try for playback unless default device (which has subdev -1) + stream = SND_PCM_STREAM_PLAYBACK; + snd_pcm_info_set_stream( pcminfo, stream ); + if ( subdevice != -1 ) { + snd_pcm_info_set_device( pcminfo, subdevice ); + snd_pcm_info_set_subdevice( pcminfo, 0 ); + + result = snd_ctl_pcm_info( chandle, pcminfo ); + if ( result < 0 ) { + // Device probably doesn't support playback. + goto captureProbe; + } + } + + result = snd_pcm_open( &phandle, name, stream, openMode | SND_PCM_NONBLOCK ); + if ( result < 0 ) { + errorStream_ << "RtApiAlsa::getDeviceInfo: snd_pcm_open error for device (" << name << "), " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + goto captureProbe; + } + + // The device is open ... fill the parameter structure. + result = snd_pcm_hw_params_any( phandle, params ); + if ( result < 0 ) { + snd_pcm_close( phandle ); + errorStream_ << "RtApiAlsa::getDeviceInfo: snd_pcm_hw_params error for device (" << name << "), " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + goto captureProbe; + } + + // Get output channel information. + unsigned int value; + result = snd_pcm_hw_params_get_channels_max( params, &value ); + if ( result < 0 ) { + snd_pcm_close( phandle ); + errorStream_ << "RtApiAlsa::getDeviceInfo: error getting device (" << name << ") output channels, " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + goto captureProbe; + } + info.outputChannels = value; + snd_pcm_close( phandle ); + + captureProbe: + stream = SND_PCM_STREAM_CAPTURE; + snd_pcm_info_set_stream( pcminfo, stream ); + + // Now try for capture unless default device (with subdev = -1) + if ( subdevice != -1 ) { + result = snd_ctl_pcm_info( chandle, pcminfo ); + snd_ctl_close( chandle ); + if ( result < 0 ) { + // Device probably doesn't support capture. + if ( info.outputChannels == 0 ) return info; + goto probeParameters; + } + } + else + snd_ctl_close( chandle ); + + result = snd_pcm_open( &phandle, name, stream, openMode | SND_PCM_NONBLOCK); + if ( result < 0 ) { + errorStream_ << "RtApiAlsa::getDeviceInfo: snd_pcm_open error for device (" << name << "), " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + if ( info.outputChannels == 0 ) return info; + goto probeParameters; + } + + // The device is open ... fill the parameter structure. + result = snd_pcm_hw_params_any( phandle, params ); + if ( result < 0 ) { + snd_pcm_close( phandle ); + errorStream_ << "RtApiAlsa::getDeviceInfo: snd_pcm_hw_params error for device (" << name << "), " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + if ( info.outputChannels == 0 ) return info; + goto probeParameters; + } + + result = snd_pcm_hw_params_get_channels_max( params, &value ); + if ( result < 0 ) { + snd_pcm_close( phandle ); + errorStream_ << "RtApiAlsa::getDeviceInfo: error getting device (" << name << ") input channels, " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + if ( info.outputChannels == 0 ) return info; + goto probeParameters; + } + info.inputChannels = value; + snd_pcm_close( phandle ); + + // If device opens for both playback and capture, we determine the channels. + if ( info.outputChannels > 0 && info.inputChannels > 0 ) + info.duplexChannels = (info.outputChannels > info.inputChannels) ? info.inputChannels : info.outputChannels; + + // ALSA doesn't provide default devices so we'll use the first available one. + if ( device == 0 && info.outputChannels > 0 ) + info.isDefaultOutput = true; + if ( device == 0 && info.inputChannels > 0 ) + info.isDefaultInput = true; + + probeParameters: + // At this point, we just need to figure out the supported data + // formats and sample rates. We'll proceed by opening the device in + // the direction with the maximum number of channels, or playback if + // they are equal. This might limit our sample rate options, but so + // be it. + + if ( info.outputChannels >= info.inputChannels ) + stream = SND_PCM_STREAM_PLAYBACK; + else + stream = SND_PCM_STREAM_CAPTURE; + snd_pcm_info_set_stream( pcminfo, stream ); + + result = snd_pcm_open( &phandle, name, stream, openMode | SND_PCM_NONBLOCK); + if ( result < 0 ) { + errorStream_ << "RtApiAlsa::getDeviceInfo: snd_pcm_open error for device (" << name << "), " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + return info; + } + + // The device is open ... fill the parameter structure. + result = snd_pcm_hw_params_any( phandle, params ); + if ( result < 0 ) { + snd_pcm_close( phandle ); + errorStream_ << "RtApiAlsa::getDeviceInfo: snd_pcm_hw_params error for device (" << name << "), " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + return info; + } + + // Test our discrete set of sample rate values. + info.sampleRates.clear(); + for ( unsigned int i=0; i info.preferredSampleRate ) ) + info.preferredSampleRate = SAMPLE_RATES[i]; + } + } + if ( info.sampleRates.size() == 0 ) { + snd_pcm_close( phandle ); + errorStream_ << "RtApiAlsa::getDeviceInfo: no supported sample rates found for device (" << name << ")."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + return info; + } + + // Probe the supported data formats ... we don't care about endian-ness just yet + snd_pcm_format_t format; + info.nativeFormats = 0; + format = SND_PCM_FORMAT_S8; + if ( snd_pcm_hw_params_test_format( phandle, params, format ) == 0 ) + info.nativeFormats |= RTAUDIO_SINT8; + format = SND_PCM_FORMAT_S16; + if ( snd_pcm_hw_params_test_format( phandle, params, format ) == 0 ) + info.nativeFormats |= RTAUDIO_SINT16; + format = SND_PCM_FORMAT_S24; + if ( snd_pcm_hw_params_test_format( phandle, params, format ) == 0 ) + info.nativeFormats |= RTAUDIO_SINT24; + format = SND_PCM_FORMAT_S32; + if ( snd_pcm_hw_params_test_format( phandle, params, format ) == 0 ) + info.nativeFormats |= RTAUDIO_SINT32; + format = SND_PCM_FORMAT_FLOAT; + if ( snd_pcm_hw_params_test_format( phandle, params, format ) == 0 ) + info.nativeFormats |= RTAUDIO_FLOAT32; + format = SND_PCM_FORMAT_FLOAT64; + if ( snd_pcm_hw_params_test_format( phandle, params, format ) == 0 ) + info.nativeFormats |= RTAUDIO_FLOAT64; + + // Check that we have at least one supported format + if ( info.nativeFormats == 0 ) { + snd_pcm_close( phandle ); + errorStream_ << "RtApiAlsa::getDeviceInfo: pcm device (" << name << ") data format not supported by RtAudio."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + return info; + } + + // Get the device name + char *cardname; + result = snd_card_get_name( card, &cardname ); + if ( result >= 0 ) { + sprintf( name, "hw:%s,%d", cardname, subdevice ); + free( cardname ); + } + info.name = name; + + // That's all ... close the device and return + snd_pcm_close( phandle ); + info.probed = true; + return info; +} + +void RtApiAlsa :: saveDeviceInfo( void ) +{ + devices_.clear(); + + unsigned int nDevices = getDeviceCount(); + devices_.resize( nDevices ); + for ( unsigned int i=0; iflags & RTAUDIO_ALSA_USE_DEFAULT ) + snprintf(name, sizeof(name), "%s", "default"); + else { + // Count cards and devices + card = -1; + snd_card_next( &card ); + while ( card >= 0 ) { + sprintf( name, "hw:%d", card ); + result = snd_ctl_open( &chandle, name, SND_CTL_NONBLOCK ); + if ( result < 0 ) { + errorStream_ << "RtApiAlsa::probeDeviceOpen: control open, card = " << card << ", " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + return FAILURE; + } + subdevice = -1; + while( 1 ) { + result = snd_ctl_pcm_next_device( chandle, &subdevice ); + if ( result < 0 ) break; + if ( subdevice < 0 ) break; + if ( nDevices == device ) { + sprintf( name, "hw:%d,%d", card, subdevice ); + snd_ctl_close( chandle ); + goto foundDevice; + } + nDevices++; + } + snd_ctl_close( chandle ); + snd_card_next( &card ); + } + + result = snd_ctl_open( &chandle, "default", SND_CTL_NONBLOCK ); + if ( result == 0 ) { + if ( nDevices == device ) { + strcpy( name, "default" ); + goto foundDevice; + } + nDevices++; + } + + if ( nDevices == 0 ) { + // This should not happen because a check is made before this function is called. + errorText_ = "RtApiAlsa::probeDeviceOpen: no devices found!"; + return FAILURE; + } + + if ( device >= nDevices ) { + // This should not happen because a check is made before this function is called. + errorText_ = "RtApiAlsa::probeDeviceOpen: device ID is invalid!"; + return FAILURE; + } + } + + foundDevice: + + // The getDeviceInfo() function will not work for a device that is + // already open. Thus, we'll probe the system before opening a + // stream and save the results for use by getDeviceInfo(). + if ( mode == OUTPUT || ( mode == INPUT && stream_.mode != OUTPUT ) ) // only do once + this->saveDeviceInfo(); + + snd_pcm_stream_t stream; + if ( mode == OUTPUT ) + stream = SND_PCM_STREAM_PLAYBACK; + else + stream = SND_PCM_STREAM_CAPTURE; + + snd_pcm_t *phandle; + int openMode = SND_PCM_ASYNC; + result = snd_pcm_open( &phandle, name, stream, openMode ); + if ( result < 0 ) { + if ( mode == OUTPUT ) + errorStream_ << "RtApiAlsa::probeDeviceOpen: pcm device (" << name << ") won't open for output."; + else + errorStream_ << "RtApiAlsa::probeDeviceOpen: pcm device (" << name << ") won't open for input."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Fill the parameter structure. + snd_pcm_hw_params_t *hw_params; + snd_pcm_hw_params_alloca( &hw_params ); + result = snd_pcm_hw_params_any( phandle, hw_params ); + if ( result < 0 ) { + snd_pcm_close( phandle ); + errorStream_ << "RtApiAlsa::probeDeviceOpen: error getting pcm device (" << name << ") parameters, " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + return FAILURE; + } + +#if defined(__RTAUDIO_DEBUG__) + fprintf( stderr, "\nRtApiAlsa: dump hardware params just after device open:\n\n" ); + snd_pcm_hw_params_dump( hw_params, out ); +#endif + + // Set access ... check user preference. + if ( options && options->flags & RTAUDIO_NONINTERLEAVED ) { + stream_.userInterleaved = false; + result = snd_pcm_hw_params_set_access( phandle, hw_params, SND_PCM_ACCESS_RW_NONINTERLEAVED ); + if ( result < 0 ) { + result = snd_pcm_hw_params_set_access( phandle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED ); + stream_.deviceInterleaved[mode] = true; + } + else + stream_.deviceInterleaved[mode] = false; + } + else { + stream_.userInterleaved = true; + result = snd_pcm_hw_params_set_access( phandle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED ); + if ( result < 0 ) { + result = snd_pcm_hw_params_set_access( phandle, hw_params, SND_PCM_ACCESS_RW_NONINTERLEAVED ); + stream_.deviceInterleaved[mode] = false; + } + else + stream_.deviceInterleaved[mode] = true; + } + + if ( result < 0 ) { + snd_pcm_close( phandle ); + errorStream_ << "RtApiAlsa::probeDeviceOpen: error setting pcm device (" << name << ") access, " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Determine how to set the device format. + stream_.userFormat = format; + snd_pcm_format_t deviceFormat = SND_PCM_FORMAT_UNKNOWN; + + if ( format == RTAUDIO_SINT8 ) + deviceFormat = SND_PCM_FORMAT_S8; + else if ( format == RTAUDIO_SINT16 ) + deviceFormat = SND_PCM_FORMAT_S16; + else if ( format == RTAUDIO_SINT24 ) + deviceFormat = SND_PCM_FORMAT_S24; + else if ( format == RTAUDIO_SINT32 ) + deviceFormat = SND_PCM_FORMAT_S32; + else if ( format == RTAUDIO_FLOAT32 ) + deviceFormat = SND_PCM_FORMAT_FLOAT; + else if ( format == RTAUDIO_FLOAT64 ) + deviceFormat = SND_PCM_FORMAT_FLOAT64; + + if ( snd_pcm_hw_params_test_format(phandle, hw_params, deviceFormat) == 0) { + stream_.deviceFormat[mode] = format; + goto setFormat; + } + + // The user requested format is not natively supported by the device. + deviceFormat = SND_PCM_FORMAT_FLOAT64; + if ( snd_pcm_hw_params_test_format( phandle, hw_params, deviceFormat ) == 0 ) { + stream_.deviceFormat[mode] = RTAUDIO_FLOAT64; + goto setFormat; + } + + deviceFormat = SND_PCM_FORMAT_FLOAT; + if ( snd_pcm_hw_params_test_format(phandle, hw_params, deviceFormat ) == 0 ) { + stream_.deviceFormat[mode] = RTAUDIO_FLOAT32; + goto setFormat; + } + + deviceFormat = SND_PCM_FORMAT_S32; + if ( snd_pcm_hw_params_test_format(phandle, hw_params, deviceFormat ) == 0 ) { + stream_.deviceFormat[mode] = RTAUDIO_SINT32; + goto setFormat; + } + + deviceFormat = SND_PCM_FORMAT_S24; + if ( snd_pcm_hw_params_test_format(phandle, hw_params, deviceFormat ) == 0 ) { + stream_.deviceFormat[mode] = RTAUDIO_SINT24; + goto setFormat; + } + + deviceFormat = SND_PCM_FORMAT_S16; + if ( snd_pcm_hw_params_test_format(phandle, hw_params, deviceFormat ) == 0 ) { + stream_.deviceFormat[mode] = RTAUDIO_SINT16; + goto setFormat; + } + + deviceFormat = SND_PCM_FORMAT_S8; + if ( snd_pcm_hw_params_test_format(phandle, hw_params, deviceFormat ) == 0 ) { + stream_.deviceFormat[mode] = RTAUDIO_SINT8; + goto setFormat; + } + + // If we get here, no supported format was found. + snd_pcm_close( phandle ); + errorStream_ << "RtApiAlsa::probeDeviceOpen: pcm device " << device << " data format not supported by RtAudio."; + errorText_ = errorStream_.str(); + return FAILURE; + + setFormat: + result = snd_pcm_hw_params_set_format( phandle, hw_params, deviceFormat ); + if ( result < 0 ) { + snd_pcm_close( phandle ); + errorStream_ << "RtApiAlsa::probeDeviceOpen: error setting pcm device (" << name << ") data format, " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Determine whether byte-swaping is necessary. + stream_.doByteSwap[mode] = false; + if ( deviceFormat != SND_PCM_FORMAT_S8 ) { + result = snd_pcm_format_cpu_endian( deviceFormat ); + if ( result == 0 ) + stream_.doByteSwap[mode] = true; + else if (result < 0) { + snd_pcm_close( phandle ); + errorStream_ << "RtApiAlsa::probeDeviceOpen: error getting pcm device (" << name << ") endian-ness, " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + return FAILURE; + } + } + + // Set the sample rate. + result = snd_pcm_hw_params_set_rate_near( phandle, hw_params, (unsigned int*) &sampleRate, 0 ); + if ( result < 0 ) { + snd_pcm_close( phandle ); + errorStream_ << "RtApiAlsa::probeDeviceOpen: error setting sample rate on device (" << name << "), " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Determine the number of channels for this device. We support a possible + // minimum device channel number > than the value requested by the user. + stream_.nUserChannels[mode] = channels; + unsigned int value; + result = snd_pcm_hw_params_get_channels_max( hw_params, &value ); + unsigned int deviceChannels = value; + if ( result < 0 || deviceChannels < channels + firstChannel ) { + snd_pcm_close( phandle ); + errorStream_ << "RtApiAlsa::probeDeviceOpen: requested channel parameters not supported by device (" << name << "), " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + result = snd_pcm_hw_params_get_channels_min( hw_params, &value ); + if ( result < 0 ) { + snd_pcm_close( phandle ); + errorStream_ << "RtApiAlsa::probeDeviceOpen: error getting minimum channels for device (" << name << "), " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + return FAILURE; + } + deviceChannels = value; + if ( deviceChannels < channels + firstChannel ) deviceChannels = channels + firstChannel; + stream_.nDeviceChannels[mode] = deviceChannels; + + // Set the device channels. + result = snd_pcm_hw_params_set_channels( phandle, hw_params, deviceChannels ); + if ( result < 0 ) { + snd_pcm_close( phandle ); + errorStream_ << "RtApiAlsa::probeDeviceOpen: error setting channels for device (" << name << "), " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Set the buffer (or period) size. + int dir = 0; + snd_pcm_uframes_t periodSize = *bufferSize; + result = snd_pcm_hw_params_set_period_size_near( phandle, hw_params, &periodSize, &dir ); + if ( result < 0 ) { + snd_pcm_close( phandle ); + errorStream_ << "RtApiAlsa::probeDeviceOpen: error setting period size for device (" << name << "), " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + return FAILURE; + } + *bufferSize = periodSize; + + // Set the buffer number, which in ALSA is referred to as the "period". + unsigned int periods = 0; + if ( options && options->flags & RTAUDIO_MINIMIZE_LATENCY ) periods = 2; + if ( options && options->numberOfBuffers > 0 ) periods = options->numberOfBuffers; + if ( periods < 2 ) periods = 4; // a fairly safe default value + result = snd_pcm_hw_params_set_periods_near( phandle, hw_params, &periods, &dir ); + if ( result < 0 ) { + snd_pcm_close( phandle ); + errorStream_ << "RtApiAlsa::probeDeviceOpen: error setting periods for device (" << name << "), " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // If attempting to setup a duplex stream, the bufferSize parameter + // MUST be the same in both directions! + if ( stream_.mode == OUTPUT && mode == INPUT && *bufferSize != stream_.bufferSize ) { + snd_pcm_close( phandle ); + errorStream_ << "RtApiAlsa::probeDeviceOpen: system error setting buffer size for duplex stream on device (" << name << ")."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + stream_.bufferSize = *bufferSize; + + // Install the hardware configuration + result = snd_pcm_hw_params( phandle, hw_params ); + if ( result < 0 ) { + snd_pcm_close( phandle ); + errorStream_ << "RtApiAlsa::probeDeviceOpen: error installing hardware configuration on device (" << name << "), " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + return FAILURE; + } + +#if defined(__RTAUDIO_DEBUG__) + fprintf(stderr, "\nRtApiAlsa: dump hardware params after installation:\n\n"); + snd_pcm_hw_params_dump( hw_params, out ); +#endif + + // Set the software configuration to fill buffers with zeros and prevent device stopping on xruns. + snd_pcm_sw_params_t *sw_params = NULL; + snd_pcm_sw_params_alloca( &sw_params ); + snd_pcm_sw_params_current( phandle, sw_params ); + snd_pcm_sw_params_set_start_threshold( phandle, sw_params, *bufferSize ); + snd_pcm_sw_params_set_stop_threshold( phandle, sw_params, ULONG_MAX ); + snd_pcm_sw_params_set_silence_threshold( phandle, sw_params, 0 ); + + // The following two settings were suggested by Theo Veenker + //snd_pcm_sw_params_set_avail_min( phandle, sw_params, *bufferSize ); + //snd_pcm_sw_params_set_xfer_align( phandle, sw_params, 1 ); + + // here are two options for a fix + //snd_pcm_sw_params_set_silence_size( phandle, sw_params, ULONG_MAX ); + snd_pcm_uframes_t val; + snd_pcm_sw_params_get_boundary( sw_params, &val ); + snd_pcm_sw_params_set_silence_size( phandle, sw_params, val ); + + result = snd_pcm_sw_params( phandle, sw_params ); + if ( result < 0 ) { + snd_pcm_close( phandle ); + errorStream_ << "RtApiAlsa::probeDeviceOpen: error installing software configuration on device (" << name << "), " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + return FAILURE; + } + +#if defined(__RTAUDIO_DEBUG__) + fprintf(stderr, "\nRtApiAlsa: dump software params after installation:\n\n"); + snd_pcm_sw_params_dump( sw_params, out ); +#endif + + // Set flags for buffer conversion + stream_.doConvertBuffer[mode] = false; + if ( stream_.userFormat != stream_.deviceFormat[mode] ) + stream_.doConvertBuffer[mode] = true; + if ( stream_.nUserChannels[mode] < stream_.nDeviceChannels[mode] ) + stream_.doConvertBuffer[mode] = true; + if ( stream_.userInterleaved != stream_.deviceInterleaved[mode] && + stream_.nUserChannels[mode] > 1 ) + stream_.doConvertBuffer[mode] = true; + + // Allocate the ApiHandle if necessary and then save. + AlsaHandle *apiInfo = 0; + if ( stream_.apiHandle == 0 ) { + try { + apiInfo = (AlsaHandle *) new AlsaHandle; + } + catch ( std::bad_alloc& ) { + errorText_ = "RtApiAlsa::probeDeviceOpen: error allocating AlsaHandle memory."; + goto error; + } + + if ( pthread_cond_init( &apiInfo->runnable_cv, NULL ) ) { + errorText_ = "RtApiAlsa::probeDeviceOpen: error initializing pthread condition variable."; + goto error; + } + + stream_.apiHandle = (void *) apiInfo; + apiInfo->handles[0] = 0; + apiInfo->handles[1] = 0; + } + else { + apiInfo = (AlsaHandle *) stream_.apiHandle; + } + apiInfo->handles[mode] = phandle; + phandle = 0; + + // Allocate necessary internal buffers. + unsigned long bufferBytes; + bufferBytes = stream_.nUserChannels[mode] * *bufferSize * formatBytes( stream_.userFormat ); + stream_.userBuffer[mode] = (char *) calloc( bufferBytes, 1 ); + if ( stream_.userBuffer[mode] == NULL ) { + errorText_ = "RtApiAlsa::probeDeviceOpen: error allocating user buffer memory."; + goto error; + } + + if ( stream_.doConvertBuffer[mode] ) { + + bool makeBuffer = true; + bufferBytes = stream_.nDeviceChannels[mode] * formatBytes( stream_.deviceFormat[mode] ); + if ( mode == INPUT ) { + if ( stream_.mode == OUTPUT && stream_.deviceBuffer ) { + unsigned long bytesOut = stream_.nDeviceChannels[0] * formatBytes( stream_.deviceFormat[0] ); + if ( bufferBytes <= bytesOut ) makeBuffer = false; + } + } + + if ( makeBuffer ) { + bufferBytes *= *bufferSize; + if ( stream_.deviceBuffer ) free( stream_.deviceBuffer ); + stream_.deviceBuffer = (char *) calloc( bufferBytes, 1 ); + if ( stream_.deviceBuffer == NULL ) { + errorText_ = "RtApiAlsa::probeDeviceOpen: error allocating device buffer memory."; + goto error; + } + } + } + + stream_.sampleRate = sampleRate; + stream_.nBuffers = periods; + stream_.device[mode] = device; + stream_.state = STREAM_STOPPED; + + // Setup the buffer conversion information structure. + if ( stream_.doConvertBuffer[mode] ) setConvertInfo( mode, firstChannel ); + + // Setup thread if necessary. + if ( stream_.mode == OUTPUT && mode == INPUT ) { + // We had already set up an output stream. + stream_.mode = DUPLEX; + // Link the streams if possible. + apiInfo->synchronized = false; + if ( snd_pcm_link( apiInfo->handles[0], apiInfo->handles[1] ) == 0 ) + apiInfo->synchronized = true; + else { + errorText_ = "RtApiAlsa::probeDeviceOpen: unable to synchronize input and output devices."; + error( RtAudioError::WARNING ); + } + } + else { + stream_.mode = mode; + + // Setup callback thread. + stream_.callbackInfo.object = (void *) this; + + // Set the thread attributes for joinable and realtime scheduling + // priority (optional). The higher priority will only take affect + // if the program is run as root or suid. Note, under Linux + // processes with CAP_SYS_NICE privilege, a user can change + // scheduling policy and priority (thus need not be root). See + // POSIX "capabilities". + pthread_attr_t attr; + pthread_attr_init( &attr ); + pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_JOINABLE ); + +#ifdef SCHED_RR // Undefined with some OSes (eg: NetBSD 1.6.x with GNU Pthread) + if ( options && options->flags & RTAUDIO_SCHEDULE_REALTIME ) { + // We previously attempted to increase the audio callback priority + // to SCHED_RR here via the attributes. However, while no errors + // were reported in doing so, it did not work. So, now this is + // done in the alsaCallbackHandler function. + stream_.callbackInfo.doRealtime = true; + int priority = options->priority; + int min = sched_get_priority_min( SCHED_RR ); + int max = sched_get_priority_max( SCHED_RR ); + if ( priority < min ) priority = min; + else if ( priority > max ) priority = max; + stream_.callbackInfo.priority = priority; + } +#endif + + stream_.callbackInfo.isRunning = true; + result = pthread_create( &stream_.callbackInfo.thread, &attr, alsaCallbackHandler, &stream_.callbackInfo ); + pthread_attr_destroy( &attr ); + if ( result ) { + stream_.callbackInfo.isRunning = false; + errorText_ = "RtApiAlsa::error creating callback thread!"; + goto error; + } + } + + return SUCCESS; + + error: + if ( apiInfo ) { + pthread_cond_destroy( &apiInfo->runnable_cv ); + if ( apiInfo->handles[0] ) snd_pcm_close( apiInfo->handles[0] ); + if ( apiInfo->handles[1] ) snd_pcm_close( apiInfo->handles[1] ); + delete apiInfo; + stream_.apiHandle = 0; + } + + if ( phandle) snd_pcm_close( phandle ); + + for ( int i=0; i<2; i++ ) { + if ( stream_.userBuffer[i] ) { + free( stream_.userBuffer[i] ); + stream_.userBuffer[i] = 0; + } + } + + if ( stream_.deviceBuffer ) { + free( stream_.deviceBuffer ); + stream_.deviceBuffer = 0; + } + + stream_.state = STREAM_CLOSED; + return FAILURE; +} + +void RtApiAlsa :: closeStream() +{ + if ( stream_.state == STREAM_CLOSED ) { + errorText_ = "RtApiAlsa::closeStream(): no open stream to close!"; + error( RtAudioError::WARNING ); + return; + } + + AlsaHandle *apiInfo = (AlsaHandle *) stream_.apiHandle; + stream_.callbackInfo.isRunning = false; + MUTEX_LOCK( &stream_.mutex ); + if ( stream_.state == STREAM_STOPPED ) { + apiInfo->runnable = true; + pthread_cond_signal( &apiInfo->runnable_cv ); + } + MUTEX_UNLOCK( &stream_.mutex ); + pthread_join( stream_.callbackInfo.thread, NULL ); + + if ( stream_.state == STREAM_RUNNING ) { + stream_.state = STREAM_STOPPED; + if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) + snd_pcm_drop( apiInfo->handles[0] ); + if ( stream_.mode == INPUT || stream_.mode == DUPLEX ) + snd_pcm_drop( apiInfo->handles[1] ); + } + + if ( apiInfo ) { + pthread_cond_destroy( &apiInfo->runnable_cv ); + if ( apiInfo->handles[0] ) snd_pcm_close( apiInfo->handles[0] ); + if ( apiInfo->handles[1] ) snd_pcm_close( apiInfo->handles[1] ); + delete apiInfo; + stream_.apiHandle = 0; + } + + for ( int i=0; i<2; i++ ) { + if ( stream_.userBuffer[i] ) { + free( stream_.userBuffer[i] ); + stream_.userBuffer[i] = 0; + } + } + + if ( stream_.deviceBuffer ) { + free( stream_.deviceBuffer ); + stream_.deviceBuffer = 0; + } + + stream_.mode = UNINITIALIZED; + stream_.state = STREAM_CLOSED; +} + +void RtApiAlsa :: startStream() +{ + // This method calls snd_pcm_prepare if the device isn't already in that state. + + verifyStream(); + if ( stream_.state == STREAM_RUNNING ) { + errorText_ = "RtApiAlsa::startStream(): the stream is already running!"; + error( RtAudioError::WARNING ); + return; + } + + MUTEX_LOCK( &stream_.mutex ); + + int result = 0; + snd_pcm_state_t state; + AlsaHandle *apiInfo = (AlsaHandle *) stream_.apiHandle; + snd_pcm_t **handle = (snd_pcm_t **) apiInfo->handles; + if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { + state = snd_pcm_state( handle[0] ); + if ( state != SND_PCM_STATE_PREPARED ) { + result = snd_pcm_prepare( handle[0] ); + if ( result < 0 ) { + errorStream_ << "RtApiAlsa::startStream: error preparing output pcm device, " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + goto unlock; + } + } + } + + if ( ( stream_.mode == INPUT || stream_.mode == DUPLEX ) && !apiInfo->synchronized ) { + result = snd_pcm_drop(handle[1]); // fix to remove stale data received since device has been open + state = snd_pcm_state( handle[1] ); + if ( state != SND_PCM_STATE_PREPARED ) { + result = snd_pcm_prepare( handle[1] ); + if ( result < 0 ) { + errorStream_ << "RtApiAlsa::startStream: error preparing input pcm device, " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + goto unlock; + } + } + } + + stream_.state = STREAM_RUNNING; + + unlock: + apiInfo->runnable = true; + pthread_cond_signal( &apiInfo->runnable_cv ); + MUTEX_UNLOCK( &stream_.mutex ); + + if ( result >= 0 ) return; + error( RtAudioError::SYSTEM_ERROR ); +} + +void RtApiAlsa :: stopStream() +{ + verifyStream(); + if ( stream_.state == STREAM_STOPPED ) { + errorText_ = "RtApiAlsa::stopStream(): the stream is already stopped!"; + error( RtAudioError::WARNING ); + return; + } + + stream_.state = STREAM_STOPPED; + MUTEX_LOCK( &stream_.mutex ); + + int result = 0; + AlsaHandle *apiInfo = (AlsaHandle *) stream_.apiHandle; + snd_pcm_t **handle = (snd_pcm_t **) apiInfo->handles; + if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { + if ( apiInfo->synchronized ) + result = snd_pcm_drop( handle[0] ); + else + result = snd_pcm_drain( handle[0] ); + if ( result < 0 ) { + errorStream_ << "RtApiAlsa::stopStream: error draining output pcm device, " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + goto unlock; + } + } + + if ( ( stream_.mode == INPUT || stream_.mode == DUPLEX ) && !apiInfo->synchronized ) { + result = snd_pcm_drop( handle[1] ); + if ( result < 0 ) { + errorStream_ << "RtApiAlsa::stopStream: error stopping input pcm device, " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + goto unlock; + } + } + + unlock: + apiInfo->runnable = false; // fixes high CPU usage when stopped + MUTEX_UNLOCK( &stream_.mutex ); + + if ( result >= 0 ) return; + error( RtAudioError::SYSTEM_ERROR ); +} + +void RtApiAlsa :: abortStream() +{ + verifyStream(); + if ( stream_.state == STREAM_STOPPED ) { + errorText_ = "RtApiAlsa::abortStream(): the stream is already stopped!"; + error( RtAudioError::WARNING ); + return; + } + + stream_.state = STREAM_STOPPED; + MUTEX_LOCK( &stream_.mutex ); + + int result = 0; + AlsaHandle *apiInfo = (AlsaHandle *) stream_.apiHandle; + snd_pcm_t **handle = (snd_pcm_t **) apiInfo->handles; + if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { + result = snd_pcm_drop( handle[0] ); + if ( result < 0 ) { + errorStream_ << "RtApiAlsa::abortStream: error aborting output pcm device, " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + goto unlock; + } + } + + if ( ( stream_.mode == INPUT || stream_.mode == DUPLEX ) && !apiInfo->synchronized ) { + result = snd_pcm_drop( handle[1] ); + if ( result < 0 ) { + errorStream_ << "RtApiAlsa::abortStream: error aborting input pcm device, " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + goto unlock; + } + } + + unlock: + apiInfo->runnable = false; // fixes high CPU usage when stopped + MUTEX_UNLOCK( &stream_.mutex ); + + if ( result >= 0 ) return; + error( RtAudioError::SYSTEM_ERROR ); +} + +void RtApiAlsa :: callbackEvent() +{ + AlsaHandle *apiInfo = (AlsaHandle *) stream_.apiHandle; + if ( stream_.state == STREAM_STOPPED ) { + MUTEX_LOCK( &stream_.mutex ); + while ( !apiInfo->runnable ) + pthread_cond_wait( &apiInfo->runnable_cv, &stream_.mutex ); + + if ( stream_.state != STREAM_RUNNING ) { + MUTEX_UNLOCK( &stream_.mutex ); + return; + } + MUTEX_UNLOCK( &stream_.mutex ); + } + + if ( stream_.state == STREAM_CLOSED ) { + errorText_ = "RtApiAlsa::callbackEvent(): the stream is closed ... this shouldn't happen!"; + error( RtAudioError::WARNING ); + return; + } + + int doStopStream = 0; + RtAudioCallback callback = (RtAudioCallback) stream_.callbackInfo.callback; + double streamTime = getStreamTime(); + RtAudioStreamStatus status = 0; + if ( stream_.mode != INPUT && apiInfo->xrun[0] == true ) { + status |= RTAUDIO_OUTPUT_UNDERFLOW; + apiInfo->xrun[0] = false; + } + if ( stream_.mode != OUTPUT && apiInfo->xrun[1] == true ) { + status |= RTAUDIO_INPUT_OVERFLOW; + apiInfo->xrun[1] = false; + } + doStopStream = callback( stream_.userBuffer[0], stream_.userBuffer[1], + stream_.bufferSize, streamTime, status, stream_.callbackInfo.userData ); + + if ( doStopStream == 2 ) { + abortStream(); + return; + } + + MUTEX_LOCK( &stream_.mutex ); + + // The state might change while waiting on a mutex. + if ( stream_.state == STREAM_STOPPED ) goto unlock; + + int result; + char *buffer; + int channels; + snd_pcm_t **handle; + snd_pcm_sframes_t frames; + RtAudioFormat format; + handle = (snd_pcm_t **) apiInfo->handles; + + if ( stream_.mode == INPUT || stream_.mode == DUPLEX ) { + + // Setup parameters. + if ( stream_.doConvertBuffer[1] ) { + buffer = stream_.deviceBuffer; + channels = stream_.nDeviceChannels[1]; + format = stream_.deviceFormat[1]; + } + else { + buffer = stream_.userBuffer[1]; + channels = stream_.nUserChannels[1]; + format = stream_.userFormat; + } + + // Read samples from device in interleaved/non-interleaved format. + if ( stream_.deviceInterleaved[1] ) + result = snd_pcm_readi( handle[1], buffer, stream_.bufferSize ); + else { + void *bufs[channels]; + size_t offset = stream_.bufferSize * formatBytes( format ); + for ( int i=0; ixrun[1] = true; + result = snd_pcm_prepare( handle[1] ); + if ( result < 0 ) { + errorStream_ << "RtApiAlsa::callbackEvent: error preparing device after overrun, " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + } + } + else { + errorStream_ << "RtApiAlsa::callbackEvent: error, current state is " << snd_pcm_state_name( state ) << ", " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + } + } + else { + errorStream_ << "RtApiAlsa::callbackEvent: audio read error, " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + } + error( RtAudioError::WARNING ); + goto tryOutput; + } + + // Do byte swapping if necessary. + if ( stream_.doByteSwap[1] ) + byteSwapBuffer( buffer, stream_.bufferSize * channels, format ); + + // Do buffer conversion if necessary. + if ( stream_.doConvertBuffer[1] ) + convertBuffer( stream_.userBuffer[1], stream_.deviceBuffer, stream_.convertInfo[1] ); + + // Check stream latency + result = snd_pcm_delay( handle[1], &frames ); + if ( result == 0 && frames > 0 ) stream_.latency[1] = frames; + } + + tryOutput: + + if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { + + // Setup parameters and do buffer conversion if necessary. + if ( stream_.doConvertBuffer[0] ) { + buffer = stream_.deviceBuffer; + convertBuffer( buffer, stream_.userBuffer[0], stream_.convertInfo[0] ); + channels = stream_.nDeviceChannels[0]; + format = stream_.deviceFormat[0]; + } + else { + buffer = stream_.userBuffer[0]; + channels = stream_.nUserChannels[0]; + format = stream_.userFormat; + } + + // Do byte swapping if necessary. + if ( stream_.doByteSwap[0] ) + byteSwapBuffer(buffer, stream_.bufferSize * channels, format); + + // Write samples to device in interleaved/non-interleaved format. + if ( stream_.deviceInterleaved[0] ) + result = snd_pcm_writei( handle[0], buffer, stream_.bufferSize ); + else { + void *bufs[channels]; + size_t offset = stream_.bufferSize * formatBytes( format ); + for ( int i=0; ixrun[0] = true; + result = snd_pcm_prepare( handle[0] ); + if ( result < 0 ) { + errorStream_ << "RtApiAlsa::callbackEvent: error preparing device after underrun, " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + } + else + errorText_ = "RtApiAlsa::callbackEvent: audio write error, underrun."; + } + else { + errorStream_ << "RtApiAlsa::callbackEvent: error, current state is " << snd_pcm_state_name( state ) << ", " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + } + } + else { + errorStream_ << "RtApiAlsa::callbackEvent: audio write error, " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + } + error( RtAudioError::WARNING ); + goto unlock; + } + + // Check stream latency + result = snd_pcm_delay( handle[0], &frames ); + if ( result == 0 && frames > 0 ) stream_.latency[0] = frames; + } + + unlock: + MUTEX_UNLOCK( &stream_.mutex ); + + RtApi::tickStreamTime(); + if ( doStopStream == 1 ) this->stopStream(); +} + +static void *alsaCallbackHandler( void *ptr ) +{ + CallbackInfo *info = (CallbackInfo *) ptr; + RtApiAlsa *object = (RtApiAlsa *) info->object; + bool *isRunning = &info->isRunning; + +#ifdef SCHED_RR // Undefined with some OSes (eg: NetBSD 1.6.x with GNU Pthread) + if ( info->doRealtime ) { + pthread_t tID = pthread_self(); // ID of this thread + sched_param prio = { info->priority }; // scheduling priority of thread + pthread_setschedparam( tID, SCHED_RR, &prio ); + } +#endif + + while ( *isRunning == true ) { + pthread_testcancel(); + object->callbackEvent(); + } + + pthread_exit( NULL ); +} + +//******************** End of __LINUX_ALSA__ *********************// +#endif + +#if defined(__LINUX_PULSE__) + +// Code written by Peter Meerwald, pmeerw@pmeerw.net +// and Tristan Matthews. + +#include +#include +#include + +static const unsigned int SUPPORTED_SAMPLERATES[] = { 8000, 16000, 22050, 32000, + 44100, 48000, 96000, 0}; + +struct rtaudio_pa_format_mapping_t { + RtAudioFormat rtaudio_format; + pa_sample_format_t pa_format; +}; + +static const rtaudio_pa_format_mapping_t supported_sampleformats[] = { + {RTAUDIO_SINT16, PA_SAMPLE_S16LE}, + {RTAUDIO_SINT32, PA_SAMPLE_S32LE}, + {RTAUDIO_FLOAT32, PA_SAMPLE_FLOAT32LE}, + {0, PA_SAMPLE_INVALID}}; + +struct PulseAudioHandle { + pa_simple *s_play; + pa_simple *s_rec; + pthread_t thread; + pthread_cond_t runnable_cv; + bool runnable; + PulseAudioHandle() : s_play(0), s_rec(0), runnable(false) { } +}; + +RtApiPulse::~RtApiPulse() +{ + if ( stream_.state != STREAM_CLOSED ) + closeStream(); +} + +unsigned int RtApiPulse::getDeviceCount( void ) +{ + return 1; +} + +RtAudio::DeviceInfo RtApiPulse::getDeviceInfo( unsigned int /*device*/ ) +{ + RtAudio::DeviceInfo info; + info.probed = true; + info.name = "PulseAudio"; + info.outputChannels = 2; + info.inputChannels = 2; + info.duplexChannels = 2; + info.isDefaultOutput = true; + info.isDefaultInput = true; + + for ( const unsigned int *sr = SUPPORTED_SAMPLERATES; *sr; ++sr ) + info.sampleRates.push_back( *sr ); + + info.preferredSampleRate = 48000; + info.nativeFormats = RTAUDIO_SINT16 | RTAUDIO_SINT32 | RTAUDIO_FLOAT32; + + return info; +} + +static void *pulseaudio_callback( void * user ) +{ + CallbackInfo *cbi = static_cast( user ); + RtApiPulse *context = static_cast( cbi->object ); + volatile bool *isRunning = &cbi->isRunning; + + while ( *isRunning ) { + pthread_testcancel(); + context->callbackEvent(); + } + + pthread_exit( NULL ); +} + +void RtApiPulse::closeStream( void ) +{ + PulseAudioHandle *pah = static_cast( stream_.apiHandle ); + + stream_.callbackInfo.isRunning = false; + if ( pah ) { + MUTEX_LOCK( &stream_.mutex ); + if ( stream_.state == STREAM_STOPPED ) { + pah->runnable = true; + pthread_cond_signal( &pah->runnable_cv ); + } + MUTEX_UNLOCK( &stream_.mutex ); + + pthread_join( pah->thread, 0 ); + if ( pah->s_play ) { + pa_simple_flush( pah->s_play, NULL ); + pa_simple_free( pah->s_play ); + } + if ( pah->s_rec ) + pa_simple_free( pah->s_rec ); + + pthread_cond_destroy( &pah->runnable_cv ); + delete pah; + stream_.apiHandle = 0; + } + + if ( stream_.userBuffer[0] ) { + free( stream_.userBuffer[0] ); + stream_.userBuffer[0] = 0; + } + if ( stream_.userBuffer[1] ) { + free( stream_.userBuffer[1] ); + stream_.userBuffer[1] = 0; + } + + stream_.state = STREAM_CLOSED; + stream_.mode = UNINITIALIZED; +} + +void RtApiPulse::callbackEvent( void ) +{ + PulseAudioHandle *pah = static_cast( stream_.apiHandle ); + + if ( stream_.state == STREAM_STOPPED ) { + MUTEX_LOCK( &stream_.mutex ); + while ( !pah->runnable ) + pthread_cond_wait( &pah->runnable_cv, &stream_.mutex ); + + if ( stream_.state != STREAM_RUNNING ) { + MUTEX_UNLOCK( &stream_.mutex ); + return; + } + MUTEX_UNLOCK( &stream_.mutex ); + } + + if ( stream_.state == STREAM_CLOSED ) { + errorText_ = "RtApiPulse::callbackEvent(): the stream is closed ... " + "this shouldn't happen!"; + error( RtAudioError::WARNING ); + return; + } + + RtAudioCallback callback = (RtAudioCallback) stream_.callbackInfo.callback; + double streamTime = getStreamTime(); + RtAudioStreamStatus status = 0; + int doStopStream = callback( stream_.userBuffer[OUTPUT], stream_.userBuffer[INPUT], + stream_.bufferSize, streamTime, status, + stream_.callbackInfo.userData ); + + if ( doStopStream == 2 ) { + abortStream(); + return; + } + + MUTEX_LOCK( &stream_.mutex ); + void *pulse_in = stream_.doConvertBuffer[INPUT] ? stream_.deviceBuffer : stream_.userBuffer[INPUT]; + void *pulse_out = stream_.doConvertBuffer[OUTPUT] ? stream_.deviceBuffer : stream_.userBuffer[OUTPUT]; + + if ( stream_.state != STREAM_RUNNING ) + goto unlock; + + int pa_error; + size_t bytes; + if (stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { + if ( stream_.doConvertBuffer[OUTPUT] ) { + convertBuffer( stream_.deviceBuffer, + stream_.userBuffer[OUTPUT], + stream_.convertInfo[OUTPUT] ); + bytes = stream_.nDeviceChannels[OUTPUT] * stream_.bufferSize * + formatBytes( stream_.deviceFormat[OUTPUT] ); + } else + bytes = stream_.nUserChannels[OUTPUT] * stream_.bufferSize * + formatBytes( stream_.userFormat ); + + if ( pa_simple_write( pah->s_play, pulse_out, bytes, &pa_error ) < 0 ) { + errorStream_ << "RtApiPulse::callbackEvent: audio write error, " << + pa_strerror( pa_error ) << "."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + } + } + + if ( stream_.mode == INPUT || stream_.mode == DUPLEX) { + if ( stream_.doConvertBuffer[INPUT] ) + bytes = stream_.nDeviceChannels[INPUT] * stream_.bufferSize * + formatBytes( stream_.deviceFormat[INPUT] ); + else + bytes = stream_.nUserChannels[INPUT] * stream_.bufferSize * + formatBytes( stream_.userFormat ); + + if ( pa_simple_read( pah->s_rec, pulse_in, bytes, &pa_error ) < 0 ) { + errorStream_ << "RtApiPulse::callbackEvent: audio read error, " << + pa_strerror( pa_error ) << "."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + } + if ( stream_.doConvertBuffer[INPUT] ) { + convertBuffer( stream_.userBuffer[INPUT], + stream_.deviceBuffer, + stream_.convertInfo[INPUT] ); + } + } + + unlock: + MUTEX_UNLOCK( &stream_.mutex ); + RtApi::tickStreamTime(); + + if ( doStopStream == 1 ) + stopStream(); +} + +void RtApiPulse::startStream( void ) +{ + PulseAudioHandle *pah = static_cast( stream_.apiHandle ); + + if ( stream_.state == STREAM_CLOSED ) { + errorText_ = "RtApiPulse::startStream(): the stream is not open!"; + error( RtAudioError::INVALID_USE ); + return; + } + if ( stream_.state == STREAM_RUNNING ) { + errorText_ = "RtApiPulse::startStream(): the stream is already running!"; + error( RtAudioError::WARNING ); + return; + } + + MUTEX_LOCK( &stream_.mutex ); + + stream_.state = STREAM_RUNNING; + + pah->runnable = true; + pthread_cond_signal( &pah->runnable_cv ); + MUTEX_UNLOCK( &stream_.mutex ); +} + +void RtApiPulse::stopStream( void ) +{ + PulseAudioHandle *pah = static_cast( stream_.apiHandle ); + + if ( stream_.state == STREAM_CLOSED ) { + errorText_ = "RtApiPulse::stopStream(): the stream is not open!"; + error( RtAudioError::INVALID_USE ); + return; + } + if ( stream_.state == STREAM_STOPPED ) { + errorText_ = "RtApiPulse::stopStream(): the stream is already stopped!"; + error( RtAudioError::WARNING ); + return; + } + + stream_.state = STREAM_STOPPED; + MUTEX_LOCK( &stream_.mutex ); + + if ( pah && pah->s_play ) { + int pa_error; + if ( pa_simple_drain( pah->s_play, &pa_error ) < 0 ) { + errorStream_ << "RtApiPulse::stopStream: error draining output device, " << + pa_strerror( pa_error ) << "."; + errorText_ = errorStream_.str(); + MUTEX_UNLOCK( &stream_.mutex ); + error( RtAudioError::SYSTEM_ERROR ); + return; + } + } + + stream_.state = STREAM_STOPPED; + MUTEX_UNLOCK( &stream_.mutex ); +} + +void RtApiPulse::abortStream( void ) +{ + PulseAudioHandle *pah = static_cast( stream_.apiHandle ); + + if ( stream_.state == STREAM_CLOSED ) { + errorText_ = "RtApiPulse::abortStream(): the stream is not open!"; + error( RtAudioError::INVALID_USE ); + return; + } + if ( stream_.state == STREAM_STOPPED ) { + errorText_ = "RtApiPulse::abortStream(): the stream is already stopped!"; + error( RtAudioError::WARNING ); + return; + } + + stream_.state = STREAM_STOPPED; + MUTEX_LOCK( &stream_.mutex ); + + if ( pah && pah->s_play ) { + int pa_error; + if ( pa_simple_flush( pah->s_play, &pa_error ) < 0 ) { + errorStream_ << "RtApiPulse::abortStream: error flushing output device, " << + pa_strerror( pa_error ) << "."; + errorText_ = errorStream_.str(); + MUTEX_UNLOCK( &stream_.mutex ); + error( RtAudioError::SYSTEM_ERROR ); + return; + } + } + + stream_.state = STREAM_STOPPED; + MUTEX_UNLOCK( &stream_.mutex ); +} + +bool RtApiPulse::probeDeviceOpen( unsigned int device, StreamMode mode, + unsigned int channels, unsigned int firstChannel, + unsigned int sampleRate, RtAudioFormat format, + unsigned int *bufferSize, RtAudio::StreamOptions *options ) +{ + PulseAudioHandle *pah = 0; + unsigned long bufferBytes = 0; + pa_sample_spec ss; + + if ( device != 0 ) return false; + if ( mode != INPUT && mode != OUTPUT ) return false; + if ( channels != 1 && channels != 2 ) { + errorText_ = "RtApiPulse::probeDeviceOpen: unsupported number of channels."; + return false; + } + ss.channels = channels; + + if ( firstChannel != 0 ) return false; + + bool sr_found = false; + for ( const unsigned int *sr = SUPPORTED_SAMPLERATES; *sr; ++sr ) { + if ( sampleRate == *sr ) { + sr_found = true; + stream_.sampleRate = sampleRate; + ss.rate = sampleRate; + break; + } + } + if ( !sr_found ) { + errorText_ = "RtApiPulse::probeDeviceOpen: unsupported sample rate."; + return false; + } + + bool sf_found = 0; + for ( const rtaudio_pa_format_mapping_t *sf = supported_sampleformats; + sf->rtaudio_format && sf->pa_format != PA_SAMPLE_INVALID; ++sf ) { + if ( format == sf->rtaudio_format ) { + sf_found = true; + stream_.userFormat = sf->rtaudio_format; + stream_.deviceFormat[mode] = stream_.userFormat; + ss.format = sf->pa_format; + break; + } + } + if ( !sf_found ) { // Use internal data format conversion. + stream_.userFormat = format; + stream_.deviceFormat[mode] = RTAUDIO_FLOAT32; + ss.format = PA_SAMPLE_FLOAT32LE; + } + + // Set other stream parameters. + if ( options && options->flags & RTAUDIO_NONINTERLEAVED ) stream_.userInterleaved = false; + else stream_.userInterleaved = true; + stream_.deviceInterleaved[mode] = true; + stream_.nBuffers = 1; + stream_.doByteSwap[mode] = false; + stream_.nUserChannels[mode] = channels; + stream_.nDeviceChannels[mode] = channels + firstChannel; + stream_.channelOffset[mode] = 0; + std::string streamName = "RtAudio"; + + // Set flags for buffer conversion. + stream_.doConvertBuffer[mode] = false; + if ( stream_.userFormat != stream_.deviceFormat[mode] ) + stream_.doConvertBuffer[mode] = true; + if ( stream_.nUserChannels[mode] < stream_.nDeviceChannels[mode] ) + stream_.doConvertBuffer[mode] = true; + + // Allocate necessary internal buffers. + bufferBytes = stream_.nUserChannels[mode] * *bufferSize * formatBytes( stream_.userFormat ); + stream_.userBuffer[mode] = (char *) calloc( bufferBytes, 1 ); + if ( stream_.userBuffer[mode] == NULL ) { + errorText_ = "RtApiPulse::probeDeviceOpen: error allocating user buffer memory."; + goto error; + } + stream_.bufferSize = *bufferSize; + + if ( stream_.doConvertBuffer[mode] ) { + + bool makeBuffer = true; + bufferBytes = stream_.nDeviceChannels[mode] * formatBytes( stream_.deviceFormat[mode] ); + if ( mode == INPUT ) { + if ( stream_.mode == OUTPUT && stream_.deviceBuffer ) { + unsigned long bytesOut = stream_.nDeviceChannels[0] * formatBytes( stream_.deviceFormat[0] ); + if ( bufferBytes <= bytesOut ) makeBuffer = false; + } + } + + if ( makeBuffer ) { + bufferBytes *= *bufferSize; + if ( stream_.deviceBuffer ) free( stream_.deviceBuffer ); + stream_.deviceBuffer = (char *) calloc( bufferBytes, 1 ); + if ( stream_.deviceBuffer == NULL ) { + errorText_ = "RtApiPulse::probeDeviceOpen: error allocating device buffer memory."; + goto error; + } + } + } + + stream_.device[mode] = device; + + // Setup the buffer conversion information structure. + if ( stream_.doConvertBuffer[mode] ) setConvertInfo( mode, firstChannel ); + + if ( !stream_.apiHandle ) { + PulseAudioHandle *pah = new PulseAudioHandle; + if ( !pah ) { + errorText_ = "RtApiPulse::probeDeviceOpen: error allocating memory for handle."; + goto error; + } + + stream_.apiHandle = pah; + if ( pthread_cond_init( &pah->runnable_cv, NULL ) != 0 ) { + errorText_ = "RtApiPulse::probeDeviceOpen: error creating condition variable."; + goto error; + } + } + pah = static_cast( stream_.apiHandle ); + + int error; + if ( options && !options->streamName.empty() ) streamName = options->streamName; + switch ( mode ) { + case INPUT: + pa_buffer_attr buffer_attr; + buffer_attr.fragsize = bufferBytes; + buffer_attr.maxlength = -1; + + pah->s_rec = pa_simple_new( NULL, streamName.c_str(), PA_STREAM_RECORD, NULL, "Record", &ss, NULL, &buffer_attr, &error ); + if ( !pah->s_rec ) { + errorText_ = "RtApiPulse::probeDeviceOpen: error connecting input to PulseAudio server."; + goto error; + } + break; + case OUTPUT: + pah->s_play = pa_simple_new( NULL, streamName.c_str(), PA_STREAM_PLAYBACK, NULL, "Playback", &ss, NULL, NULL, &error ); + if ( !pah->s_play ) { + errorText_ = "RtApiPulse::probeDeviceOpen: error connecting output to PulseAudio server."; + goto error; + } + break; + default: + goto error; + } + + if ( stream_.mode == UNINITIALIZED ) + stream_.mode = mode; + else if ( stream_.mode == mode ) + goto error; + else + stream_.mode = DUPLEX; + + if ( !stream_.callbackInfo.isRunning ) { + stream_.callbackInfo.object = this; + stream_.callbackInfo.isRunning = true; + if ( pthread_create( &pah->thread, NULL, pulseaudio_callback, (void *)&stream_.callbackInfo) != 0 ) { + errorText_ = "RtApiPulse::probeDeviceOpen: error creating thread."; + goto error; + } + } + + stream_.state = STREAM_STOPPED; + return true; + + error: + if ( pah && stream_.callbackInfo.isRunning ) { + pthread_cond_destroy( &pah->runnable_cv ); + delete pah; + stream_.apiHandle = 0; + } + + for ( int i=0; i<2; i++ ) { + if ( stream_.userBuffer[i] ) { + free( stream_.userBuffer[i] ); + stream_.userBuffer[i] = 0; + } + } + + if ( stream_.deviceBuffer ) { + free( stream_.deviceBuffer ); + stream_.deviceBuffer = 0; + } + + return FAILURE; +} + +//******************** End of __LINUX_PULSE__ *********************// +#endif + +#if defined(__LINUX_OSS__) + +#include +#include +#include +#include +#include +#include +#include + +static void *ossCallbackHandler(void * ptr); + +// A structure to hold various information related to the OSS API +// implementation. +struct OssHandle { + int id[2]; // device ids + bool xrun[2]; + bool triggered; + pthread_cond_t runnable; + + OssHandle() + :triggered(false) { id[0] = 0; id[1] = 0; xrun[0] = false; xrun[1] = false; } +}; + +RtApiOss :: RtApiOss() +{ + // Nothing to do here. +} + +RtApiOss :: ~RtApiOss() +{ + if ( stream_.state != STREAM_CLOSED ) closeStream(); +} + +unsigned int RtApiOss :: getDeviceCount( void ) +{ + int mixerfd = open( "/dev/mixer", O_RDWR, 0 ); + if ( mixerfd == -1 ) { + errorText_ = "RtApiOss::getDeviceCount: error opening '/dev/mixer'."; + error( RtAudioError::WARNING ); + return 0; + } + + oss_sysinfo sysinfo; + if ( ioctl( mixerfd, SNDCTL_SYSINFO, &sysinfo ) == -1 ) { + close( mixerfd ); + errorText_ = "RtApiOss::getDeviceCount: error getting sysinfo, OSS version >= 4.0 is required."; + error( RtAudioError::WARNING ); + return 0; + } + + close( mixerfd ); + return sysinfo.numaudios; +} + +RtAudio::DeviceInfo RtApiOss :: getDeviceInfo( unsigned int device ) +{ + RtAudio::DeviceInfo info; + info.probed = false; + + int mixerfd = open( "/dev/mixer", O_RDWR, 0 ); + if ( mixerfd == -1 ) { + errorText_ = "RtApiOss::getDeviceInfo: error opening '/dev/mixer'."; + error( RtAudioError::WARNING ); + return info; + } + + oss_sysinfo sysinfo; + int result = ioctl( mixerfd, SNDCTL_SYSINFO, &sysinfo ); + if ( result == -1 ) { + close( mixerfd ); + errorText_ = "RtApiOss::getDeviceInfo: error getting sysinfo, OSS version >= 4.0 is required."; + error( RtAudioError::WARNING ); + return info; + } + + unsigned nDevices = sysinfo.numaudios; + if ( nDevices == 0 ) { + close( mixerfd ); + errorText_ = "RtApiOss::getDeviceInfo: no devices found!"; + error( RtAudioError::INVALID_USE ); + return info; + } + + if ( device >= nDevices ) { + close( mixerfd ); + errorText_ = "RtApiOss::getDeviceInfo: device ID is invalid!"; + error( RtAudioError::INVALID_USE ); + return info; + } + + oss_audioinfo ainfo; + ainfo.dev = device; + result = ioctl( mixerfd, SNDCTL_AUDIOINFO, &ainfo ); + close( mixerfd ); + if ( result == -1 ) { + errorStream_ << "RtApiOss::getDeviceInfo: error getting device (" << ainfo.name << ") info."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + return info; + } + + // Probe channels + if ( ainfo.caps & PCM_CAP_OUTPUT ) info.outputChannels = ainfo.max_channels; + if ( ainfo.caps & PCM_CAP_INPUT ) info.inputChannels = ainfo.max_channels; + if ( ainfo.caps & PCM_CAP_DUPLEX ) { + if ( info.outputChannels > 0 && info.inputChannels > 0 && ainfo.caps & PCM_CAP_DUPLEX ) + info.duplexChannels = (info.outputChannels > info.inputChannels) ? info.inputChannels : info.outputChannels; + } + + // Probe data formats ... do for input + unsigned long mask = ainfo.iformats; + if ( mask & AFMT_S16_LE || mask & AFMT_S16_BE ) + info.nativeFormats |= RTAUDIO_SINT16; + if ( mask & AFMT_S8 ) + info.nativeFormats |= RTAUDIO_SINT8; + if ( mask & AFMT_S32_LE || mask & AFMT_S32_BE ) + info.nativeFormats |= RTAUDIO_SINT32; + if ( mask & AFMT_FLOAT ) + info.nativeFormats |= RTAUDIO_FLOAT32; + if ( mask & AFMT_S24_LE || mask & AFMT_S24_BE ) + info.nativeFormats |= RTAUDIO_SINT24; + + // Check that we have at least one supported format + if ( info.nativeFormats == 0 ) { + errorStream_ << "RtApiOss::getDeviceInfo: device (" << ainfo.name << ") data format not supported by RtAudio."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + return info; + } + + // Probe the supported sample rates. + info.sampleRates.clear(); + if ( ainfo.nrates ) { + for ( unsigned int i=0; i info.preferredSampleRate ) ) + info.preferredSampleRate = SAMPLE_RATES[k]; + + break; + } + } + } + } + else { + // Check min and max rate values; + for ( unsigned int k=0; k= (int) SAMPLE_RATES[k] ) { + info.sampleRates.push_back( SAMPLE_RATES[k] ); + + if ( !info.preferredSampleRate || ( SAMPLE_RATES[k] <= 48000 && SAMPLE_RATES[k] > info.preferredSampleRate ) ) + info.preferredSampleRate = SAMPLE_RATES[k]; + } + } + } + + if ( info.sampleRates.size() == 0 ) { + errorStream_ << "RtApiOss::getDeviceInfo: no supported sample rates found for device (" << ainfo.name << ")."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + } + else { + info.probed = true; + info.name = ainfo.name; + } + + return info; +} + + +bool RtApiOss :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, + unsigned int firstChannel, unsigned int sampleRate, + RtAudioFormat format, unsigned int *bufferSize, + RtAudio::StreamOptions *options ) +{ + int mixerfd = open( "/dev/mixer", O_RDWR, 0 ); + if ( mixerfd == -1 ) { + errorText_ = "RtApiOss::probeDeviceOpen: error opening '/dev/mixer'."; + return FAILURE; + } + + oss_sysinfo sysinfo; + int result = ioctl( mixerfd, SNDCTL_SYSINFO, &sysinfo ); + if ( result == -1 ) { + close( mixerfd ); + errorText_ = "RtApiOss::probeDeviceOpen: error getting sysinfo, OSS version >= 4.0 is required."; + return FAILURE; + } + + unsigned nDevices = sysinfo.numaudios; + if ( nDevices == 0 ) { + // This should not happen because a check is made before this function is called. + close( mixerfd ); + errorText_ = "RtApiOss::probeDeviceOpen: no devices found!"; + return FAILURE; + } + + if ( device >= nDevices ) { + // This should not happen because a check is made before this function is called. + close( mixerfd ); + errorText_ = "RtApiOss::probeDeviceOpen: device ID is invalid!"; + return FAILURE; + } + + oss_audioinfo ainfo; + ainfo.dev = device; + result = ioctl( mixerfd, SNDCTL_AUDIOINFO, &ainfo ); + close( mixerfd ); + if ( result == -1 ) { + errorStream_ << "RtApiOss::getDeviceInfo: error getting device (" << ainfo.name << ") info."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Check if device supports input or output + if ( ( mode == OUTPUT && !( ainfo.caps & PCM_CAP_OUTPUT ) ) || + ( mode == INPUT && !( ainfo.caps & PCM_CAP_INPUT ) ) ) { + if ( mode == OUTPUT ) + errorStream_ << "RtApiOss::probeDeviceOpen: device (" << ainfo.name << ") does not support output."; + else + errorStream_ << "RtApiOss::probeDeviceOpen: device (" << ainfo.name << ") does not support input."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + int flags = 0; + OssHandle *handle = (OssHandle *) stream_.apiHandle; + if ( mode == OUTPUT ) + flags |= O_WRONLY; + else { // mode == INPUT + if (stream_.mode == OUTPUT && stream_.device[0] == device) { + // We just set the same device for playback ... close and reopen for duplex (OSS only). + close( handle->id[0] ); + handle->id[0] = 0; + if ( !( ainfo.caps & PCM_CAP_DUPLEX ) ) { + errorStream_ << "RtApiOss::probeDeviceOpen: device (" << ainfo.name << ") does not support duplex mode."; + errorText_ = errorStream_.str(); + return FAILURE; + } + // Check that the number previously set channels is the same. + if ( stream_.nUserChannels[0] != channels ) { + errorStream_ << "RtApiOss::probeDeviceOpen: input/output channels must be equal for OSS duplex device (" << ainfo.name << ")."; + errorText_ = errorStream_.str(); + return FAILURE; + } + flags |= O_RDWR; + } + else + flags |= O_RDONLY; + } + + // Set exclusive access if specified. + if ( options && options->flags & RTAUDIO_HOG_DEVICE ) flags |= O_EXCL; + + // Try to open the device. + int fd; + fd = open( ainfo.devnode, flags, 0 ); + if ( fd == -1 ) { + if ( errno == EBUSY ) + errorStream_ << "RtApiOss::probeDeviceOpen: device (" << ainfo.name << ") is busy."; + else + errorStream_ << "RtApiOss::probeDeviceOpen: error opening device (" << ainfo.name << ")."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // For duplex operation, specifically set this mode (this doesn't seem to work). + /* + if ( flags | O_RDWR ) { + result = ioctl( fd, SNDCTL_DSP_SETDUPLEX, NULL ); + if ( result == -1) { + errorStream_ << "RtApiOss::probeDeviceOpen: error setting duplex mode for device (" << ainfo.name << ")."; + errorText_ = errorStream_.str(); + return FAILURE; + } + } + */ + + // Check the device channel support. + stream_.nUserChannels[mode] = channels; + if ( ainfo.max_channels < (int)(channels + firstChannel) ) { + close( fd ); + errorStream_ << "RtApiOss::probeDeviceOpen: the device (" << ainfo.name << ") does not support requested channel parameters."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Set the number of channels. + int deviceChannels = channels + firstChannel; + result = ioctl( fd, SNDCTL_DSP_CHANNELS, &deviceChannels ); + if ( result == -1 || deviceChannels < (int)(channels + firstChannel) ) { + close( fd ); + errorStream_ << "RtApiOss::probeDeviceOpen: error setting channel parameters on device (" << ainfo.name << ")."; + errorText_ = errorStream_.str(); + return FAILURE; + } + stream_.nDeviceChannels[mode] = deviceChannels; + + // Get the data format mask + int mask; + result = ioctl( fd, SNDCTL_DSP_GETFMTS, &mask ); + if ( result == -1 ) { + close( fd ); + errorStream_ << "RtApiOss::probeDeviceOpen: error getting device (" << ainfo.name << ") data formats."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Determine how to set the device format. + stream_.userFormat = format; + int deviceFormat = -1; + stream_.doByteSwap[mode] = false; + if ( format == RTAUDIO_SINT8 ) { + if ( mask & AFMT_S8 ) { + deviceFormat = AFMT_S8; + stream_.deviceFormat[mode] = RTAUDIO_SINT8; + } + } + else if ( format == RTAUDIO_SINT16 ) { + if ( mask & AFMT_S16_NE ) { + deviceFormat = AFMT_S16_NE; + stream_.deviceFormat[mode] = RTAUDIO_SINT16; + } + else if ( mask & AFMT_S16_OE ) { + deviceFormat = AFMT_S16_OE; + stream_.deviceFormat[mode] = RTAUDIO_SINT16; + stream_.doByteSwap[mode] = true; + } + } + else if ( format == RTAUDIO_SINT24 ) { + if ( mask & AFMT_S24_NE ) { + deviceFormat = AFMT_S24_NE; + stream_.deviceFormat[mode] = RTAUDIO_SINT24; + } + else if ( mask & AFMT_S24_OE ) { + deviceFormat = AFMT_S24_OE; + stream_.deviceFormat[mode] = RTAUDIO_SINT24; + stream_.doByteSwap[mode] = true; + } + } + else if ( format == RTAUDIO_SINT32 ) { + if ( mask & AFMT_S32_NE ) { + deviceFormat = AFMT_S32_NE; + stream_.deviceFormat[mode] = RTAUDIO_SINT32; + } + else if ( mask & AFMT_S32_OE ) { + deviceFormat = AFMT_S32_OE; + stream_.deviceFormat[mode] = RTAUDIO_SINT32; + stream_.doByteSwap[mode] = true; + } + } + + if ( deviceFormat == -1 ) { + // The user requested format is not natively supported by the device. + if ( mask & AFMT_S16_NE ) { + deviceFormat = AFMT_S16_NE; + stream_.deviceFormat[mode] = RTAUDIO_SINT16; + } + else if ( mask & AFMT_S32_NE ) { + deviceFormat = AFMT_S32_NE; + stream_.deviceFormat[mode] = RTAUDIO_SINT32; + } + else if ( mask & AFMT_S24_NE ) { + deviceFormat = AFMT_S24_NE; + stream_.deviceFormat[mode] = RTAUDIO_SINT24; + } + else if ( mask & AFMT_S16_OE ) { + deviceFormat = AFMT_S16_OE; + stream_.deviceFormat[mode] = RTAUDIO_SINT16; + stream_.doByteSwap[mode] = true; + } + else if ( mask & AFMT_S32_OE ) { + deviceFormat = AFMT_S32_OE; + stream_.deviceFormat[mode] = RTAUDIO_SINT32; + stream_.doByteSwap[mode] = true; + } + else if ( mask & AFMT_S24_OE ) { + deviceFormat = AFMT_S24_OE; + stream_.deviceFormat[mode] = RTAUDIO_SINT24; + stream_.doByteSwap[mode] = true; + } + else if ( mask & AFMT_S8) { + deviceFormat = AFMT_S8; + stream_.deviceFormat[mode] = RTAUDIO_SINT8; + } + } + + if ( stream_.deviceFormat[mode] == 0 ) { + // This really shouldn't happen ... + close( fd ); + errorStream_ << "RtApiOss::probeDeviceOpen: device (" << ainfo.name << ") data format not supported by RtAudio."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Set the data format. + int temp = deviceFormat; + result = ioctl( fd, SNDCTL_DSP_SETFMT, &deviceFormat ); + if ( result == -1 || deviceFormat != temp ) { + close( fd ); + errorStream_ << "RtApiOss::probeDeviceOpen: error setting data format on device (" << ainfo.name << ")."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Attempt to set the buffer size. According to OSS, the minimum + // number of buffers is two. The supposed minimum buffer size is 16 + // bytes, so that will be our lower bound. The argument to this + // call is in the form 0xMMMMSSSS (hex), where the buffer size (in + // bytes) is given as 2^SSSS and the number of buffers as 2^MMMM. + // We'll check the actual value used near the end of the setup + // procedure. + int ossBufferBytes = *bufferSize * formatBytes( stream_.deviceFormat[mode] ) * deviceChannels; + if ( ossBufferBytes < 16 ) ossBufferBytes = 16; + int buffers = 0; + if ( options ) buffers = options->numberOfBuffers; + if ( options && options->flags & RTAUDIO_MINIMIZE_LATENCY ) buffers = 2; + if ( buffers < 2 ) buffers = 3; + temp = ((int) buffers << 16) + (int)( log10( (double)ossBufferBytes ) / log10( 2.0 ) ); + result = ioctl( fd, SNDCTL_DSP_SETFRAGMENT, &temp ); + if ( result == -1 ) { + close( fd ); + errorStream_ << "RtApiOss::probeDeviceOpen: error setting buffer size on device (" << ainfo.name << ")."; + errorText_ = errorStream_.str(); + return FAILURE; + } + stream_.nBuffers = buffers; + + // Save buffer size (in sample frames). + *bufferSize = ossBufferBytes / ( formatBytes(stream_.deviceFormat[mode]) * deviceChannels ); + stream_.bufferSize = *bufferSize; + + // Set the sample rate. + int srate = sampleRate; + result = ioctl( fd, SNDCTL_DSP_SPEED, &srate ); + if ( result == -1 ) { + close( fd ); + errorStream_ << "RtApiOss::probeDeviceOpen: error setting sample rate (" << sampleRate << ") on device (" << ainfo.name << ")."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Verify the sample rate setup worked. + if ( abs( srate - sampleRate ) > 100 ) { + close( fd ); + errorStream_ << "RtApiOss::probeDeviceOpen: device (" << ainfo.name << ") does not support sample rate (" << sampleRate << ")."; + errorText_ = errorStream_.str(); + return FAILURE; + } + stream_.sampleRate = sampleRate; + + if ( mode == INPUT && stream_.mode == OUTPUT && stream_.device[0] == device) { + // We're doing duplex setup here. + stream_.deviceFormat[0] = stream_.deviceFormat[1]; + stream_.nDeviceChannels[0] = deviceChannels; + } + + // Set interleaving parameters. + stream_.userInterleaved = true; + stream_.deviceInterleaved[mode] = true; + if ( options && options->flags & RTAUDIO_NONINTERLEAVED ) + stream_.userInterleaved = false; + + // Set flags for buffer conversion + stream_.doConvertBuffer[mode] = false; + if ( stream_.userFormat != stream_.deviceFormat[mode] ) + stream_.doConvertBuffer[mode] = true; + if ( stream_.nUserChannels[mode] < stream_.nDeviceChannels[mode] ) + stream_.doConvertBuffer[mode] = true; + if ( stream_.userInterleaved != stream_.deviceInterleaved[mode] && + stream_.nUserChannels[mode] > 1 ) + stream_.doConvertBuffer[mode] = true; + + // Allocate the stream handles if necessary and then save. + if ( stream_.apiHandle == 0 ) { + try { + handle = new OssHandle; + } + catch ( std::bad_alloc& ) { + errorText_ = "RtApiOss::probeDeviceOpen: error allocating OssHandle memory."; + goto error; + } + + if ( pthread_cond_init( &handle->runnable, NULL ) ) { + errorText_ = "RtApiOss::probeDeviceOpen: error initializing pthread condition variable."; + goto error; + } + + stream_.apiHandle = (void *) handle; + } + else { + handle = (OssHandle *) stream_.apiHandle; + } + handle->id[mode] = fd; + + // Allocate necessary internal buffers. + unsigned long bufferBytes; + bufferBytes = stream_.nUserChannels[mode] * *bufferSize * formatBytes( stream_.userFormat ); + stream_.userBuffer[mode] = (char *) calloc( bufferBytes, 1 ); + if ( stream_.userBuffer[mode] == NULL ) { + errorText_ = "RtApiOss::probeDeviceOpen: error allocating user buffer memory."; + goto error; + } + + if ( stream_.doConvertBuffer[mode] ) { + + bool makeBuffer = true; + bufferBytes = stream_.nDeviceChannels[mode] * formatBytes( stream_.deviceFormat[mode] ); + if ( mode == INPUT ) { + if ( stream_.mode == OUTPUT && stream_.deviceBuffer ) { + unsigned long bytesOut = stream_.nDeviceChannels[0] * formatBytes( stream_.deviceFormat[0] ); + if ( bufferBytes <= bytesOut ) makeBuffer = false; + } + } + + if ( makeBuffer ) { + bufferBytes *= *bufferSize; + if ( stream_.deviceBuffer ) free( stream_.deviceBuffer ); + stream_.deviceBuffer = (char *) calloc( bufferBytes, 1 ); + if ( stream_.deviceBuffer == NULL ) { + errorText_ = "RtApiOss::probeDeviceOpen: error allocating device buffer memory."; + goto error; + } + } + } + + stream_.device[mode] = device; + stream_.state = STREAM_STOPPED; + + // Setup the buffer conversion information structure. + if ( stream_.doConvertBuffer[mode] ) setConvertInfo( mode, firstChannel ); + + // Setup thread if necessary. + if ( stream_.mode == OUTPUT && mode == INPUT ) { + // We had already set up an output stream. + stream_.mode = DUPLEX; + if ( stream_.device[0] == device ) handle->id[0] = fd; + } + else { + stream_.mode = mode; + + // Setup callback thread. + stream_.callbackInfo.object = (void *) this; + + // Set the thread attributes for joinable and realtime scheduling + // priority. The higher priority will only take affect if the + // program is run as root or suid. + pthread_attr_t attr; + pthread_attr_init( &attr ); + pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_JOINABLE ); +#ifdef SCHED_RR // Undefined with some OSes (eg: NetBSD 1.6.x with GNU Pthread) + if ( options && options->flags & RTAUDIO_SCHEDULE_REALTIME ) { + struct sched_param param; + int priority = options->priority; + int min = sched_get_priority_min( SCHED_RR ); + int max = sched_get_priority_max( SCHED_RR ); + if ( priority < min ) priority = min; + else if ( priority > max ) priority = max; + param.sched_priority = priority; + pthread_attr_setschedparam( &attr, ¶m ); + pthread_attr_setschedpolicy( &attr, SCHED_RR ); + } + else + pthread_attr_setschedpolicy( &attr, SCHED_OTHER ); +#else + pthread_attr_setschedpolicy( &attr, SCHED_OTHER ); +#endif + + stream_.callbackInfo.isRunning = true; + result = pthread_create( &stream_.callbackInfo.thread, &attr, ossCallbackHandler, &stream_.callbackInfo ); + pthread_attr_destroy( &attr ); + if ( result ) { + stream_.callbackInfo.isRunning = false; + errorText_ = "RtApiOss::error creating callback thread!"; + goto error; + } + } + + return SUCCESS; + + error: + if ( handle ) { + pthread_cond_destroy( &handle->runnable ); + if ( handle->id[0] ) close( handle->id[0] ); + if ( handle->id[1] ) close( handle->id[1] ); + delete handle; + stream_.apiHandle = 0; + } + + for ( int i=0; i<2; i++ ) { + if ( stream_.userBuffer[i] ) { + free( stream_.userBuffer[i] ); + stream_.userBuffer[i] = 0; + } + } + + if ( stream_.deviceBuffer ) { + free( stream_.deviceBuffer ); + stream_.deviceBuffer = 0; + } + + return FAILURE; +} + +void RtApiOss :: closeStream() +{ + if ( stream_.state == STREAM_CLOSED ) { + errorText_ = "RtApiOss::closeStream(): no open stream to close!"; + error( RtAudioError::WARNING ); + return; + } + + OssHandle *handle = (OssHandle *) stream_.apiHandle; + stream_.callbackInfo.isRunning = false; + MUTEX_LOCK( &stream_.mutex ); + if ( stream_.state == STREAM_STOPPED ) + pthread_cond_signal( &handle->runnable ); + MUTEX_UNLOCK( &stream_.mutex ); + pthread_join( stream_.callbackInfo.thread, NULL ); + + if ( stream_.state == STREAM_RUNNING ) { + if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) + ioctl( handle->id[0], SNDCTL_DSP_HALT, 0 ); + else + ioctl( handle->id[1], SNDCTL_DSP_HALT, 0 ); + stream_.state = STREAM_STOPPED; + } + + if ( handle ) { + pthread_cond_destroy( &handle->runnable ); + if ( handle->id[0] ) close( handle->id[0] ); + if ( handle->id[1] ) close( handle->id[1] ); + delete handle; + stream_.apiHandle = 0; + } + + for ( int i=0; i<2; i++ ) { + if ( stream_.userBuffer[i] ) { + free( stream_.userBuffer[i] ); + stream_.userBuffer[i] = 0; + } + } + + if ( stream_.deviceBuffer ) { + free( stream_.deviceBuffer ); + stream_.deviceBuffer = 0; + } + + stream_.mode = UNINITIALIZED; + stream_.state = STREAM_CLOSED; +} + +void RtApiOss :: startStream() +{ + verifyStream(); + if ( stream_.state == STREAM_RUNNING ) { + errorText_ = "RtApiOss::startStream(): the stream is already running!"; + error( RtAudioError::WARNING ); + return; + } + + MUTEX_LOCK( &stream_.mutex ); + + stream_.state = STREAM_RUNNING; + + // No need to do anything else here ... OSS automatically starts + // when fed samples. + + MUTEX_UNLOCK( &stream_.mutex ); + + OssHandle *handle = (OssHandle *) stream_.apiHandle; + pthread_cond_signal( &handle->runnable ); +} + +void RtApiOss :: stopStream() +{ + verifyStream(); + if ( stream_.state == STREAM_STOPPED ) { + errorText_ = "RtApiOss::stopStream(): the stream is already stopped!"; + error( RtAudioError::WARNING ); + return; + } + + MUTEX_LOCK( &stream_.mutex ); + + // The state might change while waiting on a mutex. + if ( stream_.state == STREAM_STOPPED ) { + MUTEX_UNLOCK( &stream_.mutex ); + return; + } + + int result = 0; + OssHandle *handle = (OssHandle *) stream_.apiHandle; + if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { + + // Flush the output with zeros a few times. + char *buffer; + int samples; + RtAudioFormat format; + + if ( stream_.doConvertBuffer[0] ) { + buffer = stream_.deviceBuffer; + samples = stream_.bufferSize * stream_.nDeviceChannels[0]; + format = stream_.deviceFormat[0]; + } + else { + buffer = stream_.userBuffer[0]; + samples = stream_.bufferSize * stream_.nUserChannels[0]; + format = stream_.userFormat; + } + + memset( buffer, 0, samples * formatBytes(format) ); + for ( unsigned int i=0; iid[0], buffer, samples * formatBytes(format) ); + if ( result == -1 ) { + errorText_ = "RtApiOss::stopStream: audio write error."; + error( RtAudioError::WARNING ); + } + } + + result = ioctl( handle->id[0], SNDCTL_DSP_HALT, 0 ); + if ( result == -1 ) { + errorStream_ << "RtApiOss::stopStream: system error stopping callback procedure on device (" << stream_.device[0] << ")."; + errorText_ = errorStream_.str(); + goto unlock; + } + handle->triggered = false; + } + + if ( stream_.mode == INPUT || ( stream_.mode == DUPLEX && handle->id[0] != handle->id[1] ) ) { + result = ioctl( handle->id[1], SNDCTL_DSP_HALT, 0 ); + if ( result == -1 ) { + errorStream_ << "RtApiOss::stopStream: system error stopping input callback procedure on device (" << stream_.device[0] << ")."; + errorText_ = errorStream_.str(); + goto unlock; + } + } + + unlock: + stream_.state = STREAM_STOPPED; + MUTEX_UNLOCK( &stream_.mutex ); + + if ( result != -1 ) return; + error( RtAudioError::SYSTEM_ERROR ); +} + +void RtApiOss :: abortStream() +{ + verifyStream(); + if ( stream_.state == STREAM_STOPPED ) { + errorText_ = "RtApiOss::abortStream(): the stream is already stopped!"; + error( RtAudioError::WARNING ); + return; + } + + MUTEX_LOCK( &stream_.mutex ); + + // The state might change while waiting on a mutex. + if ( stream_.state == STREAM_STOPPED ) { + MUTEX_UNLOCK( &stream_.mutex ); + return; + } + + int result = 0; + OssHandle *handle = (OssHandle *) stream_.apiHandle; + if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { + result = ioctl( handle->id[0], SNDCTL_DSP_HALT, 0 ); + if ( result == -1 ) { + errorStream_ << "RtApiOss::abortStream: system error stopping callback procedure on device (" << stream_.device[0] << ")."; + errorText_ = errorStream_.str(); + goto unlock; + } + handle->triggered = false; + } + + if ( stream_.mode == INPUT || ( stream_.mode == DUPLEX && handle->id[0] != handle->id[1] ) ) { + result = ioctl( handle->id[1], SNDCTL_DSP_HALT, 0 ); + if ( result == -1 ) { + errorStream_ << "RtApiOss::abortStream: system error stopping input callback procedure on device (" << stream_.device[0] << ")."; + errorText_ = errorStream_.str(); + goto unlock; + } + } + + unlock: + stream_.state = STREAM_STOPPED; + MUTEX_UNLOCK( &stream_.mutex ); + + if ( result != -1 ) return; + error( RtAudioError::SYSTEM_ERROR ); +} + +void RtApiOss :: callbackEvent() +{ + OssHandle *handle = (OssHandle *) stream_.apiHandle; + if ( stream_.state == STREAM_STOPPED ) { + MUTEX_LOCK( &stream_.mutex ); + pthread_cond_wait( &handle->runnable, &stream_.mutex ); + if ( stream_.state != STREAM_RUNNING ) { + MUTEX_UNLOCK( &stream_.mutex ); + return; + } + MUTEX_UNLOCK( &stream_.mutex ); + } + + if ( stream_.state == STREAM_CLOSED ) { + errorText_ = "RtApiOss::callbackEvent(): the stream is closed ... this shouldn't happen!"; + error( RtAudioError::WARNING ); + return; + } + + // Invoke user callback to get fresh output data. + int doStopStream = 0; + RtAudioCallback callback = (RtAudioCallback) stream_.callbackInfo.callback; + double streamTime = getStreamTime(); + RtAudioStreamStatus status = 0; + if ( stream_.mode != INPUT && handle->xrun[0] == true ) { + status |= RTAUDIO_OUTPUT_UNDERFLOW; + handle->xrun[0] = false; + } + if ( stream_.mode != OUTPUT && handle->xrun[1] == true ) { + status |= RTAUDIO_INPUT_OVERFLOW; + handle->xrun[1] = false; + } + doStopStream = callback( stream_.userBuffer[0], stream_.userBuffer[1], + stream_.bufferSize, streamTime, status, stream_.callbackInfo.userData ); + if ( doStopStream == 2 ) { + this->abortStream(); + return; + } + + MUTEX_LOCK( &stream_.mutex ); + + // The state might change while waiting on a mutex. + if ( stream_.state == STREAM_STOPPED ) goto unlock; + + int result; + char *buffer; + int samples; + RtAudioFormat format; + + if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { + + // Setup parameters and do buffer conversion if necessary. + if ( stream_.doConvertBuffer[0] ) { + buffer = stream_.deviceBuffer; + convertBuffer( buffer, stream_.userBuffer[0], stream_.convertInfo[0] ); + samples = stream_.bufferSize * stream_.nDeviceChannels[0]; + format = stream_.deviceFormat[0]; + } + else { + buffer = stream_.userBuffer[0]; + samples = stream_.bufferSize * stream_.nUserChannels[0]; + format = stream_.userFormat; + } + + // Do byte swapping if necessary. + if ( stream_.doByteSwap[0] ) + byteSwapBuffer( buffer, samples, format ); + + if ( stream_.mode == DUPLEX && handle->triggered == false ) { + int trig = 0; + ioctl( handle->id[0], SNDCTL_DSP_SETTRIGGER, &trig ); + result = write( handle->id[0], buffer, samples * formatBytes(format) ); + trig = PCM_ENABLE_INPUT|PCM_ENABLE_OUTPUT; + ioctl( handle->id[0], SNDCTL_DSP_SETTRIGGER, &trig ); + handle->triggered = true; + } + else + // Write samples to device. + result = write( handle->id[0], buffer, samples * formatBytes(format) ); + + if ( result == -1 ) { + // We'll assume this is an underrun, though there isn't a + // specific means for determining that. + handle->xrun[0] = true; + errorText_ = "RtApiOss::callbackEvent: audio write error."; + error( RtAudioError::WARNING ); + // Continue on to input section. + } + } + + if ( stream_.mode == INPUT || stream_.mode == DUPLEX ) { + + // Setup parameters. + if ( stream_.doConvertBuffer[1] ) { + buffer = stream_.deviceBuffer; + samples = stream_.bufferSize * stream_.nDeviceChannels[1]; + format = stream_.deviceFormat[1]; + } + else { + buffer = stream_.userBuffer[1]; + samples = stream_.bufferSize * stream_.nUserChannels[1]; + format = stream_.userFormat; + } + + // Read samples from device. + result = read( handle->id[1], buffer, samples * formatBytes(format) ); + + if ( result == -1 ) { + // We'll assume this is an overrun, though there isn't a + // specific means for determining that. + handle->xrun[1] = true; + errorText_ = "RtApiOss::callbackEvent: audio read error."; + error( RtAudioError::WARNING ); + goto unlock; + } + + // Do byte swapping if necessary. + if ( stream_.doByteSwap[1] ) + byteSwapBuffer( buffer, samples, format ); + + // Do buffer conversion if necessary. + if ( stream_.doConvertBuffer[1] ) + convertBuffer( stream_.userBuffer[1], stream_.deviceBuffer, stream_.convertInfo[1] ); + } + + unlock: + MUTEX_UNLOCK( &stream_.mutex ); + + RtApi::tickStreamTime(); + if ( doStopStream == 1 ) this->stopStream(); +} + +static void *ossCallbackHandler( void *ptr ) +{ + CallbackInfo *info = (CallbackInfo *) ptr; + RtApiOss *object = (RtApiOss *) info->object; + bool *isRunning = &info->isRunning; + + while ( *isRunning == true ) { + pthread_testcancel(); + object->callbackEvent(); + } + + pthread_exit( NULL ); +} + +//******************** End of __LINUX_OSS__ *********************// +#endif + + +// *************************************************** // +// +// Protected common (OS-independent) RtAudio methods. +// +// *************************************************** // + +// This method can be modified to control the behavior of error +// message printing. +void RtApi :: error( RtAudioError::Type type ) +{ + errorStream_.str(""); // clear the ostringstream + + RtAudioErrorCallback errorCallback = (RtAudioErrorCallback) stream_.callbackInfo.errorCallback; + if ( errorCallback ) { + // abortStream() can generate new error messages. Ignore them. Just keep original one. + + if ( firstErrorOccurred_ ) + return; + + firstErrorOccurred_ = true; + const std::string errorMessage = errorText_; + + if ( type != RtAudioError::WARNING && stream_.state != STREAM_STOPPED) { + stream_.callbackInfo.isRunning = false; // exit from the thread + abortStream(); + } + + errorCallback( type, errorMessage ); + firstErrorOccurred_ = false; + return; + } + + if ( type == RtAudioError::WARNING && showWarnings_ == true ) + std::cerr << '\n' << errorText_ << "\n\n"; + else if ( type != RtAudioError::WARNING ) + throw( RtAudioError( errorText_, type ) ); +} + +void RtApi :: verifyStream() +{ + if ( stream_.state == STREAM_CLOSED ) { + errorText_ = "RtApi:: a stream is not open!"; + error( RtAudioError::INVALID_USE ); + } +} + +void RtApi :: clearStreamInfo() +{ + stream_.mode = UNINITIALIZED; + stream_.state = STREAM_CLOSED; + stream_.sampleRate = 0; + stream_.bufferSize = 0; + stream_.nBuffers = 0; + stream_.userFormat = 0; + stream_.userInterleaved = true; + stream_.streamTime = 0.0; + stream_.apiHandle = 0; + stream_.deviceBuffer = 0; + stream_.callbackInfo.callback = 0; + stream_.callbackInfo.userData = 0; + stream_.callbackInfo.isRunning = false; + stream_.callbackInfo.errorCallback = 0; + for ( int i=0; i<2; i++ ) { + stream_.device[i] = 11111; + stream_.doConvertBuffer[i] = false; + stream_.deviceInterleaved[i] = true; + stream_.doByteSwap[i] = false; + stream_.nUserChannels[i] = 0; + stream_.nDeviceChannels[i] = 0; + stream_.channelOffset[i] = 0; + stream_.deviceFormat[i] = 0; + stream_.latency[i] = 0; + stream_.userBuffer[i] = 0; + stream_.convertInfo[i].channels = 0; + stream_.convertInfo[i].inJump = 0; + stream_.convertInfo[i].outJump = 0; + stream_.convertInfo[i].inFormat = 0; + stream_.convertInfo[i].outFormat = 0; + stream_.convertInfo[i].inOffset.clear(); + stream_.convertInfo[i].outOffset.clear(); + } +} + +unsigned int RtApi :: formatBytes( RtAudioFormat format ) +{ + if ( format == RTAUDIO_SINT16 ) + return 2; + else if ( format == RTAUDIO_SINT32 || format == RTAUDIO_FLOAT32 ) + return 4; + else if ( format == RTAUDIO_FLOAT64 ) + return 8; + else if ( format == RTAUDIO_SINT24 ) + return 3; + else if ( format == RTAUDIO_SINT8 ) + return 1; + + errorText_ = "RtApi::formatBytes: undefined format."; + error( RtAudioError::WARNING ); + + return 0; +} + +void RtApi :: setConvertInfo( StreamMode mode, unsigned int firstChannel ) +{ + if ( mode == INPUT ) { // convert device to user buffer + stream_.convertInfo[mode].inJump = stream_.nDeviceChannels[1]; + stream_.convertInfo[mode].outJump = stream_.nUserChannels[1]; + stream_.convertInfo[mode].inFormat = stream_.deviceFormat[1]; + stream_.convertInfo[mode].outFormat = stream_.userFormat; + } + else { // convert user to device buffer + stream_.convertInfo[mode].inJump = stream_.nUserChannels[0]; + stream_.convertInfo[mode].outJump = stream_.nDeviceChannels[0]; + stream_.convertInfo[mode].inFormat = stream_.userFormat; + stream_.convertInfo[mode].outFormat = stream_.deviceFormat[0]; + } + + if ( stream_.convertInfo[mode].inJump < stream_.convertInfo[mode].outJump ) + stream_.convertInfo[mode].channels = stream_.convertInfo[mode].inJump; + else + stream_.convertInfo[mode].channels = stream_.convertInfo[mode].outJump; + + // Set up the interleave/deinterleave offsets. + if ( stream_.deviceInterleaved[mode] != stream_.userInterleaved ) { + if ( ( mode == OUTPUT && stream_.deviceInterleaved[mode] ) || + ( mode == INPUT && stream_.userInterleaved ) ) { + for ( int k=0; k 0 ) { + if ( stream_.deviceInterleaved[mode] ) { + if ( mode == OUTPUT ) { + for ( int k=0; k> 8); + //out[info.outOffset[j]] >>= 8; + } + in += info.inJump; + out += info.outJump; + } + } + else if (info.inFormat == RTAUDIO_FLOAT32) { + Float32 *in = (Float32 *)inBuffer; + for (unsigned int i=0; i> 8); + } + in += info.inJump; + out += info.outJump; + } + } + else if (info.inFormat == RTAUDIO_SINT32) { + Int32 *in = (Int32 *)inBuffer; + for (unsigned int i=0; i> 16) & 0x0000ffff); + } + in += info.inJump; + out += info.outJump; + } + } + else if (info.inFormat == RTAUDIO_FLOAT32) { + Float32 *in = (Float32 *)inBuffer; + for (unsigned int i=0; i> 8) & 0x00ff); + } + in += info.inJump; + out += info.outJump; + } + } + else if (info.inFormat == RTAUDIO_SINT24) { + Int24 *in = (Int24 *)inBuffer; + for (unsigned int i=0; i> 16); + } + in += info.inJump; + out += info.outJump; + } + } + else if (info.inFormat == RTAUDIO_SINT32) { + Int32 *in = (Int32 *)inBuffer; + for (unsigned int i=0; i> 24) & 0x000000ff); + } + in += info.inJump; + out += info.outJump; + } + } + else if (info.inFormat == RTAUDIO_FLOAT32) { + Float32 *in = (Float32 *)inBuffer; + for (unsigned int i=0; i>8) | (x<<8); } +//static inline uint32_t bswap_32(uint32_t x) { return (bswap_16(x&0xffff)<<16) | (bswap_16(x>>16)); } +//static inline uint64_t bswap_64(uint64_t x) { return (((unsigned long long)bswap_32(x&0xffffffffull))<<32) | (bswap_32(x>>32)); } + +void RtApi :: byteSwapBuffer( char *buffer, unsigned int samples, RtAudioFormat format ) +{ + char val; + char *ptr; + + ptr = buffer; + if ( format == RTAUDIO_SINT16 ) { + for ( unsigned int i=0; i +#include +#include +#include + +/*! \typedef typedef unsigned long RtAudioFormat; + \brief RtAudio data format type. + + Support for signed integers and floats. Audio data fed to/from an + RtAudio stream is assumed to ALWAYS be in host byte order. The + internal routines will automatically take care of any necessary + byte-swapping between the host format and the soundcard. Thus, + endian-ness is not a concern in the following format definitions. + + - \e RTAUDIO_SINT8: 8-bit signed integer. + - \e RTAUDIO_SINT16: 16-bit signed integer. + - \e RTAUDIO_SINT24: 24-bit signed integer. + - \e RTAUDIO_SINT32: 32-bit signed integer. + - \e RTAUDIO_FLOAT32: Normalized between plus/minus 1.0. + - \e RTAUDIO_FLOAT64: Normalized between plus/minus 1.0. +*/ +typedef unsigned long RtAudioFormat; +static const RtAudioFormat RTAUDIO_SINT8 = 0x1; // 8-bit signed integer. +static const RtAudioFormat RTAUDIO_SINT16 = 0x2; // 16-bit signed integer. +static const RtAudioFormat RTAUDIO_SINT24 = 0x4; // 24-bit signed integer. +static const RtAudioFormat RTAUDIO_SINT32 = 0x8; // 32-bit signed integer. +static const RtAudioFormat RTAUDIO_FLOAT32 = 0x10; // Normalized between plus/minus 1.0. +static const RtAudioFormat RTAUDIO_FLOAT64 = 0x20; // Normalized between plus/minus 1.0. + +/*! \typedef typedef unsigned long RtAudioStreamFlags; + \brief RtAudio stream option flags. + + The following flags can be OR'ed together to allow a client to + make changes to the default stream behavior: + + - \e RTAUDIO_NONINTERLEAVED: Use non-interleaved buffers (default = interleaved). + - \e RTAUDIO_MINIMIZE_LATENCY: Attempt to set stream parameters for lowest possible latency. + - \e RTAUDIO_HOG_DEVICE: Attempt grab device for exclusive use. + - \e RTAUDIO_ALSA_USE_DEFAULT: Use the "default" PCM device (ALSA only). + + By default, RtAudio streams pass and receive audio data from the + client in an interleaved format. By passing the + RTAUDIO_NONINTERLEAVED flag to the openStream() function, audio + data will instead be presented in non-interleaved buffers. In + this case, each buffer argument in the RtAudioCallback function + will point to a single array of data, with \c nFrames samples for + each channel concatenated back-to-back. For example, the first + sample of data for the second channel would be located at index \c + nFrames (assuming the \c buffer pointer was recast to the correct + data type for the stream). + + Certain audio APIs offer a number of parameters that influence the + I/O latency of a stream. By default, RtAudio will attempt to set + these parameters internally for robust (glitch-free) performance + (though some APIs, like Windows Direct Sound, make this difficult). + By passing the RTAUDIO_MINIMIZE_LATENCY flag to the openStream() + function, internal stream settings will be influenced in an attempt + to minimize stream latency, though possibly at the expense of stream + performance. + + If the RTAUDIO_HOG_DEVICE flag is set, RtAudio will attempt to + open the input and/or output stream device(s) for exclusive use. + Note that this is not possible with all supported audio APIs. + + If the RTAUDIO_SCHEDULE_REALTIME flag is set, RtAudio will attempt + to select realtime scheduling (round-robin) for the callback thread. + + If the RTAUDIO_ALSA_USE_DEFAULT flag is set, RtAudio will attempt to + open the "default" PCM device when using the ALSA API. Note that this + will override any specified input or output device id. +*/ +typedef unsigned int RtAudioStreamFlags; +static const RtAudioStreamFlags RTAUDIO_NONINTERLEAVED = 0x1; // Use non-interleaved buffers (default = interleaved). +static const RtAudioStreamFlags RTAUDIO_MINIMIZE_LATENCY = 0x2; // Attempt to set stream parameters for lowest possible latency. +static const RtAudioStreamFlags RTAUDIO_HOG_DEVICE = 0x4; // Attempt grab device and prevent use by others. +static const RtAudioStreamFlags RTAUDIO_SCHEDULE_REALTIME = 0x8; // Try to select realtime scheduling for callback thread. +static const RtAudioStreamFlags RTAUDIO_ALSA_USE_DEFAULT = 0x10; // Use the "default" PCM device (ALSA only). + +/*! \typedef typedef unsigned long RtAudioStreamStatus; + \brief RtAudio stream status (over- or underflow) flags. + + Notification of a stream over- or underflow is indicated by a + non-zero stream \c status argument in the RtAudioCallback function. + The stream status can be one of the following two options, + depending on whether the stream is open for output and/or input: + + - \e RTAUDIO_INPUT_OVERFLOW: Input data was discarded because of an overflow condition at the driver. + - \e RTAUDIO_OUTPUT_UNDERFLOW: The output buffer ran low, likely producing a break in the output sound. +*/ +typedef unsigned int RtAudioStreamStatus; +static const RtAudioStreamStatus RTAUDIO_INPUT_OVERFLOW = 0x1; // Input data was discarded because of an overflow condition at the driver. +static const RtAudioStreamStatus RTAUDIO_OUTPUT_UNDERFLOW = 0x2; // The output buffer ran low, likely causing a gap in the output sound. + +//! RtAudio callback function prototype. +/*! + All RtAudio clients must create a function of type RtAudioCallback + to read and/or write data from/to the audio stream. When the + underlying audio system is ready for new input or output data, this + function will be invoked. + + \param outputBuffer For output (or duplex) streams, the client + should write \c nFrames of audio sample frames into this + buffer. This argument should be recast to the datatype + specified when the stream was opened. For input-only + streams, this argument will be NULL. + + \param inputBuffer For input (or duplex) streams, this buffer will + hold \c nFrames of input audio sample frames. This + argument should be recast to the datatype specified when the + stream was opened. For output-only streams, this argument + will be NULL. + + \param nFrames The number of sample frames of input or output + data in the buffers. The actual buffer size in bytes is + dependent on the data type and number of channels in use. + + \param streamTime The number of seconds that have elapsed since the + stream was started. + + \param status If non-zero, this argument indicates a data overflow + or underflow condition for the stream. The particular + condition can be determined by comparison with the + RtAudioStreamStatus flags. + + \param userData A pointer to optional data provided by the client + when opening the stream (default = NULL). + + To continue normal stream operation, the RtAudioCallback function + should return a value of zero. To stop the stream and drain the + output buffer, the function should return a value of one. To abort + the stream immediately, the client should return a value of two. + */ +typedef int (*RtAudioCallback)( void *outputBuffer, void *inputBuffer, + unsigned int nFrames, + double streamTime, + RtAudioStreamStatus status, + void *userData ); + +/************************************************************************/ +/*! \class RtAudioError + \brief Exception handling class for RtAudio. + + The RtAudioError class is quite simple but it does allow errors to be + "caught" by RtAudioError::Type. See the RtAudio documentation to know + which methods can throw an RtAudioError. +*/ +/************************************************************************/ + +class RtAudioError : public std::exception +{ + public: + //! Defined RtAudioError types. + enum Type { + WARNING, /*!< A non-critical error. */ + DEBUG_WARNING, /*!< A non-critical error which might be useful for debugging. */ + UNSPECIFIED, /*!< The default, unspecified error type. */ + NO_DEVICES_FOUND, /*!< No devices found on system. */ + INVALID_DEVICE, /*!< An invalid device ID was specified. */ + MEMORY_ERROR, /*!< An error occured during memory allocation. */ + INVALID_PARAMETER, /*!< An invalid parameter was specified to a function. */ + INVALID_USE, /*!< The function was called incorrectly. */ + DRIVER_ERROR, /*!< A system driver error occured. */ + SYSTEM_ERROR, /*!< A system error occured. */ + THREAD_ERROR /*!< A thread error occured. */ + }; + + //! The constructor. + RtAudioError( const std::string& message, Type type = RtAudioError::UNSPECIFIED ) throw() : message_(message), type_(type) {} + + //! The destructor. + virtual ~RtAudioError( void ) throw() {} + + //! Prints thrown error message to stderr. + virtual void printMessage( void ) const throw() { std::cerr << '\n' << message_ << "\n\n"; } + + //! Returns the thrown error message type. + virtual const Type& getType(void) const throw() { return type_; } + + //! Returns the thrown error message string. + virtual const std::string& getMessage(void) const throw() { return message_; } + + //! Returns the thrown error message as a c-style string. + virtual const char* what( void ) const throw() { return message_.c_str(); } + + protected: + std::string message_; + Type type_; +}; + +//! RtAudio error callback function prototype. +/*! + \param type Type of error. + \param errorText Error description. + */ +typedef void (*RtAudioErrorCallback)( RtAudioError::Type type, const std::string &errorText ); + +// **************************************************************** // +// +// RtAudio class declaration. +// +// RtAudio is a "controller" used to select an available audio i/o +// interface. It presents a common API for the user to call but all +// functionality is implemented by the class RtApi and its +// subclasses. RtAudio creates an instance of an RtApi subclass +// based on the user's API choice. If no choice is made, RtAudio +// attempts to make a "logical" API selection. +// +// **************************************************************** // + +class RtApi; + +class RtAudio +{ + public: + + //! Audio API specifier arguments. + enum Api { + UNSPECIFIED, /*!< Search for a working compiled API. */ + LINUX_ALSA, /*!< The Advanced Linux Sound Architecture API. */ + LINUX_PULSE, /*!< The Linux PulseAudio API. */ + LINUX_OSS, /*!< The Linux Open Sound System API. */ + UNIX_JACK, /*!< The Jack Low-Latency Audio Server API. */ + MACOSX_CORE, /*!< Macintosh OS-X Core Audio API. */ + WINDOWS_WASAPI, /*!< The Microsoft WASAPI API. */ + WINDOWS_ASIO, /*!< The Steinberg Audio Stream I/O API. */ + WINDOWS_DS, /*!< The Microsoft Direct Sound API. */ + RTAUDIO_DUMMY /*!< A compilable but non-functional API. */ + }; + + //! The public device information structure for returning queried values. + struct DeviceInfo { + bool probed; /*!< true if the device capabilities were successfully probed. */ + std::string name; /*!< Character string device identifier. */ + unsigned int outputChannels; /*!< Maximum output channels supported by device. */ + unsigned int inputChannels; /*!< Maximum input channels supported by device. */ + unsigned int duplexChannels; /*!< Maximum simultaneous input/output channels supported by device. */ + bool isDefaultOutput; /*!< true if this is the default output device. */ + bool isDefaultInput; /*!< true if this is the default input device. */ + std::vector sampleRates; /*!< Supported sample rates (queried from list of standard rates). */ + unsigned int preferredSampleRate; /*!< Preferred sample rate, eg. for WASAPI the system sample rate. */ + RtAudioFormat nativeFormats; /*!< Bit mask of supported data formats. */ + + // Default constructor. + DeviceInfo() + :probed(false), outputChannels(0), inputChannels(0), duplexChannels(0), + isDefaultOutput(false), isDefaultInput(false), preferredSampleRate(0), nativeFormats(0) {} + }; + + //! The structure for specifying input or ouput stream parameters. + struct StreamParameters { + unsigned int deviceId; /*!< Device index (0 to getDeviceCount() - 1). */ + unsigned int nChannels; /*!< Number of channels. */ + unsigned int firstChannel; /*!< First channel index on device (default = 0). */ + + // Default constructor. + StreamParameters() + : deviceId(0), nChannels(0), firstChannel(0) {} + }; + + //! The structure for specifying stream options. + /*! + The following flags can be OR'ed together to allow a client to + make changes to the default stream behavior: + + - \e RTAUDIO_NONINTERLEAVED: Use non-interleaved buffers (default = interleaved). + - \e RTAUDIO_MINIMIZE_LATENCY: Attempt to set stream parameters for lowest possible latency. + - \e RTAUDIO_HOG_DEVICE: Attempt grab device for exclusive use. + - \e RTAUDIO_SCHEDULE_REALTIME: Attempt to select realtime scheduling for callback thread. + - \e RTAUDIO_ALSA_USE_DEFAULT: Use the "default" PCM device (ALSA only). + + By default, RtAudio streams pass and receive audio data from the + client in an interleaved format. By passing the + RTAUDIO_NONINTERLEAVED flag to the openStream() function, audio + data will instead be presented in non-interleaved buffers. In + this case, each buffer argument in the RtAudioCallback function + will point to a single array of data, with \c nFrames samples for + each channel concatenated back-to-back. For example, the first + sample of data for the second channel would be located at index \c + nFrames (assuming the \c buffer pointer was recast to the correct + data type for the stream). + + Certain audio APIs offer a number of parameters that influence the + I/O latency of a stream. By default, RtAudio will attempt to set + these parameters internally for robust (glitch-free) performance + (though some APIs, like Windows Direct Sound, make this difficult). + By passing the RTAUDIO_MINIMIZE_LATENCY flag to the openStream() + function, internal stream settings will be influenced in an attempt + to minimize stream latency, though possibly at the expense of stream + performance. + + If the RTAUDIO_HOG_DEVICE flag is set, RtAudio will attempt to + open the input and/or output stream device(s) for exclusive use. + Note that this is not possible with all supported audio APIs. + + If the RTAUDIO_SCHEDULE_REALTIME flag is set, RtAudio will attempt + to select realtime scheduling (round-robin) for the callback thread. + The \c priority parameter will only be used if the RTAUDIO_SCHEDULE_REALTIME + flag is set. It defines the thread's realtime priority. + + If the RTAUDIO_ALSA_USE_DEFAULT flag is set, RtAudio will attempt to + open the "default" PCM device when using the ALSA API. Note that this + will override any specified input or output device id. + + The \c numberOfBuffers parameter can be used to control stream + latency in the Windows DirectSound, Linux OSS, and Linux Alsa APIs + only. A value of two is usually the smallest allowed. Larger + numbers can potentially result in more robust stream performance, + though likely at the cost of stream latency. The value set by the + user is replaced during execution of the RtAudio::openStream() + function by the value actually used by the system. + + The \c streamName parameter can be used to set the client name + when using the Jack API. By default, the client name is set to + RtApiJack. However, if you wish to create multiple instances of + RtAudio with Jack, each instance must have a unique client name. + */ + struct StreamOptions { + RtAudioStreamFlags flags; /*!< A bit-mask of stream flags (RTAUDIO_NONINTERLEAVED, RTAUDIO_MINIMIZE_LATENCY, RTAUDIO_HOG_DEVICE, RTAUDIO_ALSA_USE_DEFAULT). */ + unsigned int numberOfBuffers; /*!< Number of stream buffers. */ + std::string streamName; /*!< A stream name (currently used only in Jack). */ + int priority; /*!< Scheduling priority of callback thread (only used with flag RTAUDIO_SCHEDULE_REALTIME). */ + + // Default constructor. + StreamOptions() + : flags(0), numberOfBuffers(0), priority(0) {} + }; + + //! A static function to determine the current RtAudio version. + static std::string getVersion( void ) throw(); + + //! A static function to determine the available compiled audio APIs. + /*! + The values returned in the std::vector can be compared against + the enumerated list values. Note that there can be more than one + API compiled for certain operating systems. + */ + static void getCompiledApi( std::vector &apis ) throw(); + + //! The class constructor. + /*! + The constructor performs minor initialization tasks. An exception + can be thrown if no API support is compiled. + + If no API argument is specified and multiple API support has been + compiled, the default order of use is JACK, ALSA, OSS (Linux + systems) and ASIO, DS (Windows systems). + */ + RtAudio( RtAudio::Api api=UNSPECIFIED ); + + //! The destructor. + /*! + If a stream is running or open, it will be stopped and closed + automatically. + */ + ~RtAudio() throw(); + + //! Returns the audio API specifier for the current instance of RtAudio. + RtAudio::Api getCurrentApi( void ) throw(); + + //! A public function that queries for the number of audio devices available. + /*! + This function performs a system query of available devices each time it + is called, thus supporting devices connected \e after instantiation. If + a system error occurs during processing, a warning will be issued. + */ + unsigned int getDeviceCount( void ) throw(); + + //! Return an RtAudio::DeviceInfo structure for a specified device number. + /*! + + Any device integer between 0 and getDeviceCount() - 1 is valid. + If an invalid argument is provided, an RtAudioError (type = INVALID_USE) + will be thrown. If a device is busy or otherwise unavailable, the + structure member "probed" will have a value of "false" and all + other members are undefined. If the specified device is the + current default input or output device, the corresponding + "isDefault" member will have a value of "true". + */ + RtAudio::DeviceInfo getDeviceInfo( unsigned int device ); + + //! A function that returns the index of the default output device. + /*! + If the underlying audio API does not provide a "default + device", or if no devices are available, the return value will be + 0. Note that this is a valid device identifier and it is the + client's responsibility to verify that a device is available + before attempting to open a stream. + */ + unsigned int getDefaultOutputDevice( void ) throw(); + + //! A function that returns the index of the default input device. + /*! + If the underlying audio API does not provide a "default + device", or if no devices are available, the return value will be + 0. Note that this is a valid device identifier and it is the + client's responsibility to verify that a device is available + before attempting to open a stream. + */ + unsigned int getDefaultInputDevice( void ) throw(); + + //! A public function for opening a stream with the specified parameters. + /*! + An RtAudioError (type = SYSTEM_ERROR) is thrown if a stream cannot be + opened with the specified parameters or an error occurs during + processing. An RtAudioError (type = INVALID_USE) is thrown if any + invalid device ID or channel number parameters are specified. + + \param outputParameters Specifies output stream parameters to use + when opening a stream, including a device ID, number of channels, + and starting channel number. For input-only streams, this + argument should be NULL. The device ID is an index value between + 0 and getDeviceCount() - 1. + \param inputParameters Specifies input stream parameters to use + when opening a stream, including a device ID, number of channels, + and starting channel number. For output-only streams, this + argument should be NULL. The device ID is an index value between + 0 and getDeviceCount() - 1. + \param format An RtAudioFormat specifying the desired sample data format. + \param sampleRate The desired sample rate (sample frames per second). + \param *bufferFrames A pointer to a value indicating the desired + internal buffer size in sample frames. The actual value + used by the device is returned via the same pointer. A + value of zero can be specified, in which case the lowest + allowable value is determined. + \param callback A client-defined function that will be invoked + when input data is available and/or output data is needed. + \param userData An optional pointer to data that can be accessed + from within the callback function. + \param options An optional pointer to a structure containing various + global stream options, including a list of OR'ed RtAudioStreamFlags + and a suggested number of stream buffers that can be used to + control stream latency. More buffers typically result in more + robust performance, though at a cost of greater latency. If a + value of zero is specified, a system-specific median value is + chosen. If the RTAUDIO_MINIMIZE_LATENCY flag bit is set, the + lowest allowable value is used. The actual value used is + returned via the structure argument. The parameter is API dependent. + \param errorCallback A client-defined function that will be invoked + when an error has occured. + */ + void openStream( RtAudio::StreamParameters *outputParameters, + RtAudio::StreamParameters *inputParameters, + RtAudioFormat format, unsigned int sampleRate, + unsigned int *bufferFrames, RtAudioCallback callback, + void *userData = NULL, RtAudio::StreamOptions *options = NULL, RtAudioErrorCallback errorCallback = NULL ); + + //! A function that closes a stream and frees any associated stream memory. + /*! + If a stream is not open, this function issues a warning and + returns (no exception is thrown). + */ + void closeStream( void ) throw(); + + //! A function that starts a stream. + /*! + An RtAudioError (type = SYSTEM_ERROR) is thrown if an error occurs + during processing. An RtAudioError (type = INVALID_USE) is thrown if a + stream is not open. A warning is issued if the stream is already + running. + */ + void startStream( void ); + + //! Stop a stream, allowing any samples remaining in the output queue to be played. + /*! + An RtAudioError (type = SYSTEM_ERROR) is thrown if an error occurs + during processing. An RtAudioError (type = INVALID_USE) is thrown if a + stream is not open. A warning is issued if the stream is already + stopped. + */ + void stopStream( void ); + + //! Stop a stream, discarding any samples remaining in the input/output queue. + /*! + An RtAudioError (type = SYSTEM_ERROR) is thrown if an error occurs + during processing. An RtAudioError (type = INVALID_USE) is thrown if a + stream is not open. A warning is issued if the stream is already + stopped. + */ + void abortStream( void ); + + //! Returns true if a stream is open and false if not. + bool isStreamOpen( void ) const throw(); + + //! Returns true if the stream is running and false if it is stopped or not open. + bool isStreamRunning( void ) const throw(); + + //! Returns the number of elapsed seconds since the stream was started. + /*! + If a stream is not open, an RtAudioError (type = INVALID_USE) will be thrown. + */ + double getStreamTime( void ); + + //! Set the stream time to a time in seconds greater than or equal to 0.0. + /*! + If a stream is not open, an RtAudioError (type = INVALID_USE) will be thrown. + */ + void setStreamTime( double time ); + + //! Returns the internal stream latency in sample frames. + /*! + The stream latency refers to delay in audio input and/or output + caused by internal buffering by the audio system and/or hardware. + For duplex streams, the returned value will represent the sum of + the input and output latencies. If a stream is not open, an + RtAudioError (type = INVALID_USE) will be thrown. If the API does not + report latency, the return value will be zero. + */ + long getStreamLatency( void ); + + //! Returns actual sample rate in use by the stream. + /*! + On some systems, the sample rate used may be slightly different + than that specified in the stream parameters. If a stream is not + open, an RtAudioError (type = INVALID_USE) will be thrown. + */ + unsigned int getStreamSampleRate( void ); + + //! Specify whether warning messages should be printed to stderr. + void showWarnings( bool value = true ) throw(); + + /* --- Monocasual hack ---------------------------------------------------- */ + //protected: + /* ------------------------------------------------------------------------ */ + + void openRtApi( RtAudio::Api api ); + RtApi *rtapi_; +}; + +// Operating system dependent thread functionality. +#if defined(__WINDOWS_DS__) || defined(__WINDOWS_ASIO__) || defined(__WINDOWS_WASAPI__) + + #ifndef NOMINMAX + #define NOMINMAX + #endif + #include + #include + + typedef uintptr_t ThreadHandle; + typedef CRITICAL_SECTION StreamMutex; + +#elif defined(__LINUX_ALSA__) || defined(__LINUX_PULSE__) || defined(__UNIX_JACK__) || defined(__LINUX_OSS__) || defined(__MACOSX_CORE__) + // Using pthread library for various flavors of unix. + #include + + typedef pthread_t ThreadHandle; + typedef pthread_mutex_t StreamMutex; + +#else // Setup for "dummy" behavior + + #define __RTAUDIO_DUMMY__ + typedef int ThreadHandle; + typedef int StreamMutex; + +#endif + +// This global structure type is used to pass callback information +// between the private RtAudio stream structure and global callback +// handling functions. +struct CallbackInfo { + void *object; // Used as a "this" pointer. + ThreadHandle thread; + void *callback; + void *userData; + void *errorCallback; + void *apiInfo; // void pointer for API specific callback information + bool isRunning; + bool doRealtime; + int priority; + + // Default constructor. + CallbackInfo() + :object(0), callback(0), userData(0), errorCallback(0), apiInfo(0), isRunning(false), doRealtime(false) {} +}; + +// **************************************************************** // +// +// RtApi class declaration. +// +// Subclasses of RtApi contain all API- and OS-specific code necessary +// to fully implement the RtAudio API. +// +// Note that RtApi is an abstract base class and cannot be +// explicitly instantiated. The class RtAudio will create an +// instance of an RtApi subclass (RtApiOss, RtApiAlsa, +// RtApiJack, RtApiCore, RtApiDs, or RtApiAsio). +// +// **************************************************************** // + +#pragma pack(push, 1) +class S24 { + + protected: + unsigned char c3[3]; + + public: + S24() {} + + S24& operator = ( const int& i ) { + c3[0] = (i & 0x000000ff); + c3[1] = (i & 0x0000ff00) >> 8; + c3[2] = (i & 0x00ff0000) >> 16; + return *this; + } + + S24( const S24& v ) { *this = v; } + S24( const double& d ) { *this = (int) d; } + S24( const float& f ) { *this = (int) f; } + S24( const signed short& s ) { *this = (int) s; } + S24( const char& c ) { *this = (int) c; } + + int asInt() { + int i = c3[0] | (c3[1] << 8) | (c3[2] << 16); + if (i & 0x800000) i |= ~0xffffff; + return i; + } +}; +#pragma pack(pop) + +#if defined( HAVE_GETTIMEOFDAY ) + #include +#endif + +#include + +class RtApi +{ +public: + + /* --- Monocasual hack ---------------------------------------------------- */ + #ifdef __linux__ + void *__HACK__getJackClient(); + #endif + /* ------------------------------------------------------------------------ */ + + RtApi(); + virtual ~RtApi(); + virtual RtAudio::Api getCurrentApi( void ) = 0; + virtual unsigned int getDeviceCount( void ) = 0; + virtual RtAudio::DeviceInfo getDeviceInfo( unsigned int device ) = 0; + virtual unsigned int getDefaultInputDevice( void ); + virtual unsigned int getDefaultOutputDevice( void ); + void openStream( RtAudio::StreamParameters *outputParameters, + RtAudio::StreamParameters *inputParameters, + RtAudioFormat format, unsigned int sampleRate, + unsigned int *bufferFrames, RtAudioCallback callback, + void *userData, RtAudio::StreamOptions *options, + RtAudioErrorCallback errorCallback ); + virtual void closeStream( void ); + virtual void startStream( void ) = 0; + virtual void stopStream( void ) = 0; + virtual void abortStream( void ) = 0; + long getStreamLatency( void ); + unsigned int getStreamSampleRate( void ); + virtual double getStreamTime( void ); + virtual void setStreamTime( double time ); + bool isStreamOpen( void ) const { return stream_.state != STREAM_CLOSED; } + bool isStreamRunning( void ) const { return stream_.state == STREAM_RUNNING; } + void showWarnings( bool value ) { showWarnings_ = value; } + + +protected: + + static const unsigned int MAX_SAMPLE_RATES; + static const unsigned int SAMPLE_RATES[]; + + enum { FAILURE, SUCCESS }; + + enum StreamState { + STREAM_STOPPED, + STREAM_STOPPING, + STREAM_RUNNING, + STREAM_CLOSED = -50 + }; + + enum StreamMode { + OUTPUT, + INPUT, + DUPLEX, + UNINITIALIZED = -75 + }; + + // A protected structure used for buffer conversion. + struct ConvertInfo { + int channels; + int inJump, outJump; + RtAudioFormat inFormat, outFormat; + std::vector inOffset; + std::vector outOffset; + }; + + // A protected structure for audio streams. + struct RtApiStream { + unsigned int device[2]; // Playback and record, respectively. + void *apiHandle; // void pointer for API specific stream handle information + StreamMode mode; // OUTPUT, INPUT, or DUPLEX. + StreamState state; // STOPPED, RUNNING, or CLOSED + char *userBuffer[2]; // Playback and record, respectively. + char *deviceBuffer; + bool doConvertBuffer[2]; // Playback and record, respectively. + bool userInterleaved; + bool deviceInterleaved[2]; // Playback and record, respectively. + bool doByteSwap[2]; // Playback and record, respectively. + unsigned int sampleRate; + unsigned int bufferSize; + unsigned int nBuffers; + unsigned int nUserChannels[2]; // Playback and record, respectively. + unsigned int nDeviceChannels[2]; // Playback and record channels, respectively. + unsigned int channelOffset[2]; // Playback and record, respectively. + unsigned long latency[2]; // Playback and record, respectively. + RtAudioFormat userFormat; + RtAudioFormat deviceFormat[2]; // Playback and record, respectively. + StreamMutex mutex; + CallbackInfo callbackInfo; + ConvertInfo convertInfo[2]; + double streamTime; // Number of elapsed seconds since the stream started. + +#if defined(HAVE_GETTIMEOFDAY) + struct timeval lastTickTimestamp; +#endif + + RtApiStream() + :apiHandle(0), deviceBuffer(0) { device[0] = 11111; device[1] = 11111; } + }; + + typedef S24 Int24; + typedef signed short Int16; + typedef signed int Int32; + typedef float Float32; + typedef double Float64; + + std::ostringstream errorStream_; + std::string errorText_; + bool showWarnings_; + RtApiStream stream_; + bool firstErrorOccurred_; + + /*! + Protected, api-specific method that attempts to open a device + with the given parameters. This function MUST be implemented by + all subclasses. If an error is encountered during the probe, a + "warning" message is reported and FAILURE is returned. A + successful probe is indicated by a return value of SUCCESS. + */ + virtual bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, + unsigned int firstChannel, unsigned int sampleRate, + RtAudioFormat format, unsigned int *bufferSize, + RtAudio::StreamOptions *options ); + + //! A protected function used to increment the stream time. + void tickStreamTime( void ); + + //! Protected common method to clear an RtApiStream structure. + void clearStreamInfo(); + + /*! + Protected common method that throws an RtAudioError (type = + INVALID_USE) if a stream is not open. + */ + void verifyStream( void ); + + //! Protected common error method to allow global control over error handling. + void error( RtAudioError::Type type ); + + /*! + Protected method used to perform format, channel number, and/or interleaving + conversions between the user and device buffers. + */ + void convertBuffer( char *outBuffer, char *inBuffer, ConvertInfo &info ); + + //! Protected common method used to perform byte-swapping on buffers. + void byteSwapBuffer( char *buffer, unsigned int samples, RtAudioFormat format ); + + //! Protected common method that returns the number of bytes for a given format. + unsigned int formatBytes( RtAudioFormat format ); + + //! Protected common method that sets up the parameters for buffer conversion. + void setConvertInfo( StreamMode mode, unsigned int firstChannel ); +}; + +// **************************************************************** // +// +// Inline RtAudio definitions. +// +// **************************************************************** // + +inline RtAudio::Api RtAudio :: getCurrentApi( void ) throw() { return rtapi_->getCurrentApi(); } +inline unsigned int RtAudio :: getDeviceCount( void ) throw() { return rtapi_->getDeviceCount(); } +inline RtAudio::DeviceInfo RtAudio :: getDeviceInfo( unsigned int device ) { return rtapi_->getDeviceInfo( device ); } +inline unsigned int RtAudio :: getDefaultInputDevice( void ) throw() { return rtapi_->getDefaultInputDevice(); } +inline unsigned int RtAudio :: getDefaultOutputDevice( void ) throw() { return rtapi_->getDefaultOutputDevice(); } +inline void RtAudio :: closeStream( void ) throw() { return rtapi_->closeStream(); } +inline void RtAudio :: startStream( void ) { return rtapi_->startStream(); } +inline void RtAudio :: stopStream( void ) { return rtapi_->stopStream(); } +inline void RtAudio :: abortStream( void ) { return rtapi_->abortStream(); } +inline bool RtAudio :: isStreamOpen( void ) const throw() { return rtapi_->isStreamOpen(); } +inline bool RtAudio :: isStreamRunning( void ) const throw() { return rtapi_->isStreamRunning(); } +inline long RtAudio :: getStreamLatency( void ) { return rtapi_->getStreamLatency(); } +inline unsigned int RtAudio :: getStreamSampleRate( void ) { return rtapi_->getStreamSampleRate(); } +inline double RtAudio :: getStreamTime( void ) { return rtapi_->getStreamTime(); } +inline void RtAudio :: setStreamTime( double time ) { return rtapi_->setStreamTime( time ); } +inline void RtAudio :: showWarnings( bool value ) throw() { rtapi_->showWarnings( value ); } + +// RtApi Subclass prototypes. + +#if defined(__MACOSX_CORE__) + +#include + +class RtApiCore: public RtApi +{ +public: + + RtApiCore(); + ~RtApiCore(); + RtAudio::Api getCurrentApi( void ) { return RtAudio::MACOSX_CORE; } + unsigned int getDeviceCount( void ); + RtAudio::DeviceInfo getDeviceInfo( unsigned int device ); + unsigned int getDefaultOutputDevice( void ); + unsigned int getDefaultInputDevice( void ); + void closeStream( void ); + void startStream( void ); + void stopStream( void ); + void abortStream( void ); + long getStreamLatency( void ); + + // This function is intended for internal use only. It must be + // public because it is called by the internal callback handler, + // which is not a member of RtAudio. External use of this function + // will most likely produce highly undesireable results! + bool callbackEvent( AudioDeviceID deviceId, + const AudioBufferList *inBufferList, + const AudioBufferList *outBufferList ); + + private: + + bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, + unsigned int firstChannel, unsigned int sampleRate, + RtAudioFormat format, unsigned int *bufferSize, + RtAudio::StreamOptions *options ); + static const char* getErrorCode( OSStatus code ); +}; + +#endif + +#if defined(__UNIX_JACK__) + +class RtApiJack: public RtApi +{ +public: + + RtApiJack(); + ~RtApiJack(); + RtAudio::Api getCurrentApi( void ) { return RtAudio::UNIX_JACK; } + unsigned int getDeviceCount( void ); + RtAudio::DeviceInfo getDeviceInfo( unsigned int device ); + void closeStream( void ); + void startStream( void ); + void stopStream( void ); + void abortStream( void ); + long getStreamLatency( void ); + + // This function is intended for internal use only. It must be + // public because it is called by the internal callback handler, + // which is not a member of RtAudio. External use of this function + // will most likely produce highly undesireable results! + bool callbackEvent( unsigned long nframes ); + + private: + + bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, + unsigned int firstChannel, unsigned int sampleRate, + RtAudioFormat format, unsigned int *bufferSize, + RtAudio::StreamOptions *options ); +}; + +#endif + +#if defined(__WINDOWS_ASIO__) + +class RtApiAsio: public RtApi +{ +public: + + RtApiAsio(); + ~RtApiAsio(); + RtAudio::Api getCurrentApi( void ) { return RtAudio::WINDOWS_ASIO; } + unsigned int getDeviceCount( void ); + RtAudio::DeviceInfo getDeviceInfo( unsigned int device ); + void closeStream( void ); + void startStream( void ); + void stopStream( void ); + void abortStream( void ); + long getStreamLatency( void ); + + // This function is intended for internal use only. It must be + // public because it is called by the internal callback handler, + // which is not a member of RtAudio. External use of this function + // will most likely produce highly undesireable results! + bool callbackEvent( long bufferIndex ); + + private: + + std::vector devices_; + void saveDeviceInfo( void ); + bool coInitialized_; + bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, + unsigned int firstChannel, unsigned int sampleRate, + RtAudioFormat format, unsigned int *bufferSize, + RtAudio::StreamOptions *options ); +}; + +#endif + +#if defined(__WINDOWS_DS__) + +class RtApiDs: public RtApi +{ +public: + + RtApiDs(); + ~RtApiDs(); + RtAudio::Api getCurrentApi( void ) { return RtAudio::WINDOWS_DS; } + unsigned int getDeviceCount( void ); + unsigned int getDefaultOutputDevice( void ); + unsigned int getDefaultInputDevice( void ); + RtAudio::DeviceInfo getDeviceInfo( unsigned int device ); + void closeStream( void ); + void startStream( void ); + void stopStream( void ); + void abortStream( void ); + long getStreamLatency( void ); + + // This function is intended for internal use only. It must be + // public because it is called by the internal callback handler, + // which is not a member of RtAudio. External use of this function + // will most likely produce highly undesireable results! + void callbackEvent( void ); + + private: + + bool coInitialized_; + bool buffersRolling; + long duplexPrerollBytes; + std::vector dsDevices; + bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, + unsigned int firstChannel, unsigned int sampleRate, + RtAudioFormat format, unsigned int *bufferSize, + RtAudio::StreamOptions *options ); +}; + +#endif + +#if defined(__WINDOWS_WASAPI__) + +struct IMMDeviceEnumerator; + +class RtApiWasapi : public RtApi +{ +public: + RtApiWasapi(); + ~RtApiWasapi(); + + RtAudio::Api getCurrentApi( void ) { return RtAudio::WINDOWS_WASAPI; } + unsigned int getDeviceCount( void ); + RtAudio::DeviceInfo getDeviceInfo( unsigned int device ); + unsigned int getDefaultOutputDevice( void ); + unsigned int getDefaultInputDevice( void ); + void closeStream( void ); + void startStream( void ); + void stopStream( void ); + void abortStream( void ); + +private: + bool coInitialized_; + IMMDeviceEnumerator* deviceEnumerator_; + + bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, + unsigned int firstChannel, unsigned int sampleRate, + RtAudioFormat format, unsigned int* bufferSize, + RtAudio::StreamOptions* options ); + + static DWORD WINAPI runWasapiThread( void* wasapiPtr ); + static DWORD WINAPI stopWasapiThread( void* wasapiPtr ); + static DWORD WINAPI abortWasapiThread( void* wasapiPtr ); + void wasapiThread(); +}; + +#endif + +#if defined(__LINUX_ALSA__) + +class RtApiAlsa: public RtApi +{ +public: + + RtApiAlsa(); + ~RtApiAlsa(); + RtAudio::Api getCurrentApi() { return RtAudio::LINUX_ALSA; } + unsigned int getDeviceCount( void ); + RtAudio::DeviceInfo getDeviceInfo( unsigned int device ); + void closeStream( void ); + void startStream( void ); + void stopStream( void ); + void abortStream( void ); + + // This function is intended for internal use only. It must be + // public because it is called by the internal callback handler, + // which is not a member of RtAudio. External use of this function + // will most likely produce highly undesireable results! + void callbackEvent( void ); + + private: + + std::vector devices_; + void saveDeviceInfo( void ); + bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, + unsigned int firstChannel, unsigned int sampleRate, + RtAudioFormat format, unsigned int *bufferSize, + RtAudio::StreamOptions *options ); +}; + +#endif + +#if defined(__LINUX_PULSE__) + +class RtApiPulse: public RtApi +{ +public: + ~RtApiPulse(); + RtAudio::Api getCurrentApi() { return RtAudio::LINUX_PULSE; } + unsigned int getDeviceCount( void ); + RtAudio::DeviceInfo getDeviceInfo( unsigned int device ); + void closeStream( void ); + void startStream( void ); + void stopStream( void ); + void abortStream( void ); + + // This function is intended for internal use only. It must be + // public because it is called by the internal callback handler, + // which is not a member of RtAudio. External use of this function + // will most likely produce highly undesireable results! + void callbackEvent( void ); + + private: + + std::vector devices_; + void saveDeviceInfo( void ); + bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, + unsigned int firstChannel, unsigned int sampleRate, + RtAudioFormat format, unsigned int *bufferSize, + RtAudio::StreamOptions *options ); +}; + +#endif + +#if defined(__LINUX_OSS__) + +class RtApiOss: public RtApi +{ +public: + + RtApiOss(); + ~RtApiOss(); + RtAudio::Api getCurrentApi() { return RtAudio::LINUX_OSS; } + unsigned int getDeviceCount( void ); + RtAudio::DeviceInfo getDeviceInfo( unsigned int device ); + void closeStream( void ); + void startStream( void ); + void stopStream( void ); + void abortStream( void ); + + // This function is intended for internal use only. It must be + // public because it is called by the internal callback handler, + // which is not a member of RtAudio. External use of this function + // will most likely produce highly undesireable results! + void callbackEvent( void ); + + private: + + bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, + unsigned int firstChannel, unsigned int sampleRate, + RtAudioFormat format, unsigned int *bufferSize, + RtAudio::StreamOptions *options ); +}; + +#endif + +#if defined(__RTAUDIO_DUMMY__) + +class RtApiDummy: public RtApi +{ +public: + + RtApiDummy() { errorText_ = "RtApiDummy: This class provides no functionality."; error( RtAudioError::WARNING ); } + RtAudio::Api getCurrentApi( void ) { return RtAudio::RTAUDIO_DUMMY; } + unsigned int getDeviceCount( void ) { return 0; } + RtAudio::DeviceInfo getDeviceInfo( unsigned int /*device*/ ) { RtAudio::DeviceInfo info; return info; } + void closeStream( void ) {} + void startStream( void ) {} + void stopStream( void ) {} + void abortStream( void ) {} + + private: + + bool probeDeviceOpen( unsigned int /*device*/, StreamMode /*mode*/, unsigned int /*channels*/, + unsigned int /*firstChannel*/, unsigned int /*sampleRate*/, + RtAudioFormat /*format*/, unsigned int * /*bufferSize*/, + RtAudio::StreamOptions * /*options*/ ) { return false; } +}; + +#endif + +#endif + +// Indentation settings for Vim and Emacs +// +// Local Variables: +// c-basic-offset: 2 +// indent-tabs-mode: nil +// End: +// +// vim: et sts=2 sw=2 diff --git a/src/ext/giada.ico b/src/ext/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/ext/resource.h b/src/ext/resource.h new file mode 100644 index 0000000..d771ba8 --- /dev/null +++ b/src/ext/resource.h @@ -0,0 +1 @@ +#define IDI_ICON1 101 \ No newline at end of file diff --git a/src/ext/resource.rc b/src/ext/resource.rc new file mode 100644 index 0000000..fb0be40 --- /dev/null +++ b/src/ext/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/glue/channel.cpp b/src/glue/channel.cpp new file mode 100644 index 0000000..0af1044 --- /dev/null +++ b/src/glue/channel.cpp @@ -0,0 +1,487 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * glue + * Intermediate layer GUI <-> CORE. + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include "../gui/dialogs/gd_mainWindow.h" +#include "../gui/dialogs/gd_editor.h" +#include "../gui/elems/ge_waveTools.h" +#include "../gui/elems/ge_waveform.h" +#include "../gui/elems/ge_mixed.h" +#include "../gui/elems/mainWindow/keyboard/keyboard.h" +#include "../gui/elems/mainWindow/keyboard/channel.h" +#include "../utils/gui.h" +#include "../core/mixerHandler.h" +#include "../core/mixer.h" +#include "../core/pluginHost.h" +#include "../core/conf.h" +#include "../core/wave.h" +#include "../core/channel.h" +#include "../core/sampleChannel.h" +#include "../core/midiChannel.h" +#include "../core/plugin.h" +#include "main.h" +#include "channel.h" + + +extern gdMainWindow *G_MainWin; +extern Conf G_Conf; +extern KernelAudio G_KernelAudio; +extern Recorder G_Recorder; +extern Mixer G_Mixer; +#ifdef WITH_VST +extern PluginHost G_PluginHost; +#endif + + +using std::string; + + +static bool __soloSession__ = false; + + +int glue_loadChannel(SampleChannel *ch, const string &fname) +{ + /* Always stop a channel before loading a new sample in it. This will prevent + issues if tracker is outside the boundaries of the new sample -> segfault. */ + + ch->hardStop(0); + + /* save the patch and take the last browser's dir in order to re-use it + * the next time */ + + G_Conf.samplePath = gu_dirname(fname); + + int result = ch->load(fname.c_str(), G_Conf.samplerate, G_Conf.rsmpQuality); + + if (result == SAMPLE_LOADED_OK) + G_MainWin->keyboard->updateChannel(ch->guiChannel); + + return result; +} + + +/* -------------------------------------------------------------------------- */ + + +Channel *glue_addChannel(int column, int type) +{ + Channel *ch = G_Mixer.addChannel(type); + geChannel *gch = G_MainWin->keyboard->addChannel(column, ch); + ch->guiChannel = gch; + return ch; +} + + +/* -------------------------------------------------------------------------- */ + + +void glue_deleteChannel(Channel *ch) +{ + G_Recorder.clearChan(ch->index); + ch->hasActions = false; +#ifdef WITH_VST + G_PluginHost.freeStack(PluginHost::CHANNEL, &G_Mixer.mutex_plugins, ch); +#endif + Fl::lock(); + G_MainWin->keyboard->deleteChannel(ch->guiChannel); + Fl::unlock(); + G_Mixer.deleteChannel(ch); + gu_closeAllSubwindows(); +} + + +/* -------------------------------------------------------------------------- */ + + +void glue_freeChannel(Channel *ch) +{ +#ifdef WITH_VST + + G_PluginHost.freeStack(PluginHost::CHANNEL, &G_Mixer.mutex_plugins, ch); + ch->guiChannel->fx->full = false; + +#endif + G_MainWin->keyboard->freeChannel(ch->guiChannel); + G_Recorder.clearChan(ch->index); + ch->hasActions = false; + ch->empty(); +} + + +/* -------------------------------------------------------------------------- */ + + +void glue_toggleArm(Channel *ch, bool gui) +{ + ch->armed = !ch->armed; + if (!gui) + ch->guiChannel->arm->value(ch->armed); +} + + +/* -------------------------------------------------------------------------- */ + + +int glue_cloneChannel(Channel *src) +{ + Channel *ch = G_Mixer.addChannel(src->type); + geChannel *gch = G_MainWin->keyboard->addChannel(src->guiChannel->getColumnIndex(), ch); + + ch->guiChannel = gch; + ch->copy(src, &G_Mixer.mutex_plugins); + + G_MainWin->keyboard->updateChannel(ch->guiChannel); + return true; +} + + +/* -------------------------------------------------------------------------- */ + + +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(G_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_setPitch(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(); + } +} + + +/* -------------------------------------------------------------------------- */ + + +void glue_setPanning(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", (int) std::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", (int) std::abs((ch->panLeft * 100.0f) - 100)); + win->panNum->value(buf); + } + win->panNum->redraw(); +} + + +/* -------------------------------------------------------------------------- */ + + +void glue_setMute(Channel *ch, bool gui) +{ + if (G_Recorder.active && G_Recorder.canRec(ch, &G_Mixer)) { + if (!ch->mute) { + G_Recorder.startOverdub(ch->index, ACTION_MUTES, G_Mixer.currentFrame, + G_KernelAudio.realBufsize); + ch->readActions = false; // don't read actions while overdubbing + } + else + G_Recorder.stopOverdub(&G_Mixer); + } + + 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; + ch->sendMidiLsolo(); + + /* mute all other channels and unmute this (if muted) */ + + for (unsigned i=0; isolo && !och->mute) { + och->setMute(false); + Fl::lock(); + och->guiChannel->mute->value(true); + Fl::unlock(); + } + } + + if (ch->mute) { + ch->unsetMute(false); + Fl::lock(); + ch->guiChannel->mute->value(false); + Fl::unlock(); + } + + if (!gui) { + Fl::lock(); + ch->guiChannel->solo->value(1); + Fl::unlock(); + } +} + + +/* -------------------------------------------------------------------------- */ + + +void glue_setSoloOff(Channel *ch, bool gui) +{ + /* if this is uniqueSolo, stop solo session and restore mute status, + * else mute this */ + + if (mh_uniqueSolo(ch)) { + __soloSession__ = false; + for (unsigned i=0; imute_s) { + och->setMute(false); + Fl::lock(); + och->guiChannel->mute->value(true); + Fl::unlock(); + } + else { + och->unsetMute(false); + Fl::lock(); + och->guiChannel->mute->value(false); + Fl::unlock(); + } + och->mute_s = false; + } + } + else { + ch->setMute(false); + Fl::lock(); + ch->guiChannel->mute->value(true); + Fl::unlock(); + } + + ch->solo = !ch->solo; + ch->sendMidiLsolo(); + + if (!gui) { + Fl::lock(); + ch->guiChannel->solo->value(0); + Fl::unlock(); + } +} + + +/* -------------------------------------------------------------------------- */ + + +void glue_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(); + 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(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(); + } +} diff --git a/src/glue/channel.h b/src/glue/channel.h new file mode 100644 index 0000000..3534add --- /dev/null +++ b/src/glue/channel.h @@ -0,0 +1,98 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * glue + * Intermediate layer GUI <-> CORE. + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License 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_CHANNEL_H +#define GLUE_CHANNEL_H + + +#include + + +/* 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 std::string &fname); + +/* deleteChannel + * Remove a channel from Mixer. */ + +void glue_deleteChannel(class Channel *ch); + +/* freeChannel + * Unload the sample from a sample channel. */ + +void glue_freeChannel(class Channel *ch); + +/* cloneChannel + * Make an exact copy of Channel *ch. */ + +int glue_cloneChannel(class Channel *ch); + +/* toggle/set* + * Toggle or set several channel properties. If gui == true the signal comes + * from a manual interaction on the GUI, otherwise it's a MIDI/Jack/external + * signal. */ + +void glue_toggleArm(class Channel *ch, bool gui=true); +void glue_setChanVol(class Channel *ch, float v, bool gui=true); +void glue_setMute(class Channel *ch, bool gui=true); +void glue_setSoloOn (class Channel *ch, bool gui=true); +void glue_setSoloOff(class Channel *ch, bool gui=true); + +void glue_setPitch(class gdEditor *win, class SampleChannel *ch, float val, + bool numeric); + +void glue_setPanning(class gdEditor *win, class SampleChannel *ch, float val); + +/* setBeginEndChannel + * sets start/end points in the sample editor. Recalc=false: don't recalc + * internal position. check=true: check the points' consistency */ + +void glue_setBeginEndChannel(class gdEditor *win, class SampleChannel *ch, + int b, int e, bool recalc=false, bool check=true); + +void glue_setBoost(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. */ + +void glue_setVolEditor(class gdEditor *win, class SampleChannel *ch, float val, + bool numeric); + + +#endif diff --git a/src/glue/io.cpp b/src/glue/io.cpp new file mode 100644 index 0000000..4dc9acf --- /dev/null +++ b/src/glue/io.cpp @@ -0,0 +1,336 @@ +/* ----------------------------------------------------------------------------- + * + * 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-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include +#include "../gui/dialogs/gd_mainWindow.h" +#include "../gui/dialogs/gd_warnings.h" +#include "../gui/elems/mainWindow/mainTransport.h" +#include "../gui/elems/mainWindow/mainTimer.h" +#include "../gui/elems/mainWindow/keyboard/keyboard.h" +#include "../gui/elems/mainWindow/keyboard/channel.h" +#include "../gui/elems/mainWindow/keyboard/sampleChannel.h" +#include "../utils/gui.h" +#include "../utils/log.h" +#include "../core/recorder.h" +#include "../core/mixer.h" +#include "../core/mixerHandler.h" +#include "../core/wave.h" +#include "../core/channel.h" +#include "../core/sampleChannel.h" +#include "../core/midiChannel.h" +#include "main.h" +#include "channel.h" +#include "io.h" + + +extern Recorder G_Recorder; +extern KernelAudio G_KernelAudio; +extern bool G_audio_status; +extern Mixer G_Mixer; +extern gdMainWindow *G_MainWin; + + +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, G_Mixer.quantize, G_Mixer.running, false, 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 (G_Recorder.active) { + if (G_Mixer.running) { + ch->kill(0); // on frame 0: user-generated event + if (G_Recorder.canRec(ch, &G_Mixer) && !(ch->mode & LOOP_ANY)) { // don't record killChan actions for LOOP channels + G_Recorder.rec(ch->index, ACTION_KILLCHAN, G_Mixer.currentFrame); + ch->hasActions = true; + } + } + } + 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 + } + } + else { /* case no modifier */ + + /* record now if the quantizer is off, otherwise let mixer to handle it + * when a quantoWait has passed. Moreover, KEYPRESS and KEYREL are + * meaningless for loop modes */ + + if (G_Mixer.quantize == 0 && + G_Recorder.canRec(ch, &G_Mixer) && + !(ch->mode & LOOP_ANY)) + { + if (ch->mode == SINGLE_PRESS) { + G_Recorder.startOverdub(ch->index, ACTION_KEYS, G_Mixer.currentFrame, + G_KernelAudio.realBufsize); + ch->readActions = false; // don't read actions while overdubbing + } + else { + G_Recorder.rec(ch->index, ACTION_KEYPRESS, G_Mixer.currentFrame); + ch->hasActions = true; + + /* Why return here? You record an action (as done on line 148) and then + you call ch->start (line 165): Mixer, which is on another thread, reads + your newly recorded action if you have readActions == true, and then + ch->start kicks in right after it (as done on line 165). + The result: Mixer plays the channel (due to the new action) but ch->start + kills it right away (because the sample is playing). Fix: call ch->start + only if you are not recording anything, i.e. let Mixer play it. */ + + if (ch->readActions) + return; + } + } + + /* This is a user-generated event, so it's on frame 0 */ + + ch->start(0, true, G_Mixer.quantize, G_Mixer.running, false, true); + } + + /* the GUI update is done by gui_refresh() */ +} + + +/* -------------------------------------------------------------------------- */ + + +void glue_keyRelease(SampleChannel *ch, bool ctrl, bool shift) +{ + if (ctrl || shift) + return; + + ch->stop(); + + /* record a key release only if channel is single_press. For any + * other mode the KEY REL is meaningless. */ + + if (ch->mode == SINGLE_PRESS && G_Recorder.canRec(ch, &G_Mixer)) + G_Recorder.stopOverdub(&G_Mixer); + + /* the GUI update is done by gui_refresh() */ + +} + + +/* -------------------------------------------------------------------------- */ + + +void glue_startStopActionRec(bool gui) +{ + G_Recorder.active ? glue_stopActionRec(gui) : glue_startActionRec(gui); +} + + +/* -------------------------------------------------------------------------- */ + + +void glue_startActionRec(bool gui) +{ + if (G_audio_status == false) + return; + + G_Recorder.active = true; + + if (!G_Mixer.running) + glue_startSeq(false); // update gui ayway + + if (!gui) { + Fl::lock(); + G_MainWin->mainTransport->updateRecAction(1); + Fl::unlock(); + } +} + + +/* -------------------------------------------------------------------------- */ + + +void glue_stopActionRec(bool gui) +{ + /* stop the recorder and sort new actions */ + + G_Recorder.active = false; + G_Recorder.sortActions(); + + for (unsigned i=0; itype == CHANNEL_MIDI) + continue; + SampleChannel *ch = (SampleChannel*) G_Mixer.channels.at(i); + G_MainWin->keyboard->setChannelWithActions((geSampleChannel*)ch->guiChannel); + if (!ch->readActions && ch->hasActions) + glue_startReadingRecs(ch, false); + } + + if (!gui) { + Fl::lock(); + G_MainWin->mainTransport->updateRecAction(0); + Fl::unlock(); + } + + gu_refreshActionEditor(); // in case it's open +} + + +/* -------------------------------------------------------------------------- */ + + +void glue_startStopInputRec(bool gui) +{ + if (G_Mixer.recording) + glue_stopInputRec(gui); + else + if (!glue_startInputRec(gui)) + gdAlert("No channels armed/available for audio recording."); +} + + +/* -------------------------------------------------------------------------- */ + + +int glue_startInputRec(bool gui) +{ + if (G_audio_status == false) + return false; + + if (!mh_startInputRec()) { + Fl::lock(); + G_MainWin->mainTransport->updateRecInput(0); // set it off, anyway + Fl::unlock(); + return false; + } + + if (!G_Mixer.running) + glue_startSeq(false); // update gui anyway + + Fl::lock(); + if (!gui) + G_MainWin->mainTransport->updateRecInput(1); + G_MainWin->mainTimer->setLock(true); + Fl::unlock(); + + /* Update sample name inside sample channels' main button. This is useless for + midi channel, but let's do it anyway. */ + + for (unsigned i=0; iguiChannel->update(); + + return true; +} + + +/* -------------------------------------------------------------------------- */ + + +int glue_stopInputRec(bool gui) +{ + mh_stopInputRec(); + + /* Start all sample channels in loop mode that were armed, i.e. that were + recording stuff and not yet in play. They are also started in force mode, i.e. + they must start playing right away at the current frame, not at the next first + beat. */ + + for (unsigned i=0; itype == CHANNEL_MIDI) + continue; + SampleChannel *ch = (SampleChannel*) G_Mixer.channels.at(i); + if (ch->mode & (LOOP_ANY) && ch->status == STATUS_OFF && ch->armed) + ch->start(G_Mixer.currentFrame, true, G_Mixer.quantize, G_Mixer.running, true, true); + } + + Fl::lock(); + if (!gui) + G_MainWin->mainTransport->updateRecInput(0); + G_MainWin->mainTimer->setLock(false); + Fl::unlock(); + + return 1; +} diff --git a/src/glue/io.h b/src/glue/io.h new file mode 100644 index 0000000..b56160f --- /dev/null +++ b/src/glue/io.h @@ -0,0 +1,67 @@ +/* ----------------------------------------------------------------------------- + * + * 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-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License 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_IO_H +#define GLUE_IO_H + + +/* keyPress / keyRelease + * handle the key pressure, either via mouse/keyboard or MIDI. If gui + * is true it means that the event comes from the main window (mouse, + * keyb or MIDI), otherwise the event comes from the action recorder. */ + +void glue_keyPress (class Channel *ch, bool ctrl=0, bool shift=0); +void glue_keyPress (class SampleChannel *ch, bool ctrl=0, bool shift=0); +void glue_keyPress (class MidiChannel *ch, bool ctrl=0, bool shift=0); +void glue_keyRelease(class Channel *ch, bool ctrl=0, bool shift=0); +void glue_keyRelease(class SampleChannel *ch, bool ctrl=0, bool shift=0); + +/* start/stopActionRec +Handles the action recording. If gui == true the signal comes from an user +interaction, otherwise it's a MIDI/Jack/external signal. */ + +void glue_startStopActionRec(bool gui=true); +void glue_startActionRec(bool gui=true); +void glue_stopActionRec(bool gui=true); + +/* start/stopInputRec +Handles the input recording (take). If gui == true the signal comes from an +internal interaction on the GUI, otherwise it's a MIDI/Jack/external signal. */ + +void glue_startStopInputRec(bool gui=true); +int glue_startInputRec (bool gui=true); +int glue_stopInputRec (bool gui=true); + + +#endif diff --git a/src/glue/main.cpp b/src/glue/main.cpp new file mode 100644 index 0000000..d9033d0 --- /dev/null +++ b/src/glue/main.cpp @@ -0,0 +1,434 @@ +/* ----------------------------------------------------------------------------- + * + * 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-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include +#include "../gui/elems/ge_waveform.h" +#include "../gui/elems/ge_mixed.h" +#include "../gui/elems/ge_waveTools.h" +#include "../gui/elems/mainWindow/mainTransport.h" +#include "../gui/elems/mainWindow/mainIO.h" +#include "../gui/elems/mainWindow/mainTimer.h" +#include "../gui/elems/mainWindow/keyboard/channel.h" +#include "../gui/elems/mainWindow/keyboard/sampleChannel.h" +#include "../gui/elems/mainWindow/keyboard/keyboard.h" +#include "../gui/dialogs/gd_mainWindow.h" +#include "../gui/dialogs/gd_editor.h" +#include "../gui/dialogs/gd_warnings.h" +#include "../utils/gui.h" +#include "../utils/fs.h" +#include "../utils/log.h" +#include "../core/mixerHandler.h" +#include "../core/mixer.h" +#include "../core/recorder.h" +#include "../core/wave.h" +#include "../core/pluginHost.h" +#include "../core/channel.h" +#include "../core/sampleChannel.h" +#include "../core/midiChannel.h" +#include "../core/kernelMidi.h" +#include "../core/patch_DEPR_.h" +#include "../core/conf.h" +#include "main.h" + + +extern gdMainWindow *G_MainWin; +extern Mixer G_Mixer; +extern Recorder G_Recorder; +extern KernelAudio G_KernelAudio; +extern KernelMidi G_KernelMidi; +extern Patch_DEPR_ G_Patch_DEPR_; +extern Conf G_Conf; +extern bool G_audio_status; +#ifdef WITH_VST +extern PluginHost G_PluginHost; +#endif + + +void glue_setBpm(const char *v1, const char *v2) +{ + /* Never change this stuff while recording audio */ + + if (G_Mixer.recording) + return; + + 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 */ + + G_Recorder.updateBpm(old_bpm, value, G_Mixer.quanto); + gu_refreshActionEditor(); + + G_MainWin->mainTimer->setBpm(buf); + gu_log("[glue] Bpm changed to %s (real=%f)\n", buf, G_Mixer.bpm); +} + + +/* -------------------------------------------------------------------------- */ + + +void glue_setBeats(int beats, int bars, bool expand) +{ + /* Never change this stuff while recording audio */ + + if (G_Mixer.recording) + return; + + /* 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) + G_Recorder.expand(oldfpb, G_Mixer.totalFrames); + //else if (G_Mixer.beats < oldvalue) + // G_Recorder.shrink(G_Mixer.totalFrames); + } + + G_MainWin->mainTimer->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__ + G_KernelAudio.jackStart(); +#endif + } + + if (G_Conf.midiSync == MIDI_SYNC_CLOCK_M) { + G_KernelMidi.send(MIDI_START, -1, -1); + G_KernelMidi.send(MIDI_POSITION_PTR, 0, 0); + } + + if (!gui) { + Fl::lock(); + G_MainWin->mainTransport->updatePlay(1); + Fl::unlock(); + } +} + + +/* -------------------------------------------------------------------------- */ + + +void glue_stopSeq(bool gui) +{ + mh_stopSequencer(); + + if (G_Conf.midiSync == MIDI_SYNC_CLOCK_M) + G_KernelMidi.send(MIDI_STOP, -1, -1); + +#ifdef __linux__ + if (gui) + G_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 (G_Recorder.active) { + G_Recorder.active = false; + Fl::lock(); + G_MainWin->mainTransport->updateRecAction(0); + Fl::unlock(); + } + + /* if input recs are active (who knows why) we must deactivate them. + * One might stop the sequencer while an input rec is running. */ + + if (G_Mixer.recording) { + mh_stopInputRec(); + Fl::lock(); + G_MainWin->mainTransport->updateRecInput(0); + Fl::unlock(); + } + + if (!gui) { + Fl::lock(); + G_MainWin->mainTransport->updatePlay(0); + Fl::unlock(); + } +} + + +/* -------------------------------------------------------------------------- */ + + +void glue_rewindSeq() +{ + mh_rewindSequencer(); + if (G_Conf.midiSync == MIDI_SYNC_CLOCK_M) + G_KernelMidi.send(MIDI_POSITION_PTR, 0, 0); +} + + +/* -------------------------------------------------------------------------- */ + + +void glue_startStopReadingRecs(SampleChannel *ch, bool gui) +{ + /* When you call glue_startReadingRecs with G_Conf.treatRecsAsLoops, the + member value ch->readActions actually is not set to true immediately, because + the channel is in wait mode (REC_WAITING). ch->readActions will become true on + the next first beat. So a 'stop rec' command should occur also when + ch->readActions is false but the channel is in wait mode; this check will + handle the case of when you press 'R', the channel goes into REC_WAITING and + then you press 'R' again to undo the status. */ + + if (ch->readActions || (!ch->readActions && ch->recStatus == REC_WAITING)) + glue_stopReadingRecs(ch, gui); + else + glue_startReadingRecs(ch, gui); +} + + +/* -------------------------------------------------------------------------- */ + + +void glue_startReadingRecs(SampleChannel *ch, bool gui) +{ + if (G_Conf.treatRecsAsLoops) + ch->recStatus = REC_WAITING; + else + ch->setReadActions(true, G_Conf.recsStopOnChanHalt); + if (!gui) { + Fl::lock(); + ((geSampleChannel*)ch->guiChannel)->readActions->value(1); + Fl::unlock(); + } +} + + +/* -------------------------------------------------------------------------- */ + + +void glue_stopReadingRecs(SampleChannel *ch, bool gui) +{ + /* First of all, if the mixer is not running just stop and disable everything. + Then if "treatRecsAsLoop" wait until the sequencer reaches beat 0, so put the + channel in REC_ENDING status. */ + + if (!G_Mixer.running) { + ch->recStatus = REC_STOPPED; + ch->readActions = false; + } + else + if (G_Conf.treatRecsAsLoops) + ch->recStatus = REC_ENDING; + else + ch->setReadActions(false, G_Conf.recsStopOnChanHalt); + + if (!gui) { + Fl::lock(); + ((geSampleChannel*)ch->guiChannel)->readActions->value(0); + Fl::unlock(); + } +} + + +/* -------------------------------------------------------------------------- */ + + +void glue_quantize(int val) +{ + G_Mixer.quantize = val; + G_Mixer.updateQuanto(); +} + + +/* -------------------------------------------------------------------------- */ + + +void glue_setOutVol(float v, bool gui) +{ + G_Mixer.outVol = v; + if (!gui) { + Fl::lock(); + G_MainWin->mainIO->setOutVol(v); + Fl::unlock(); + } +} + + +/* -------------------------------------------------------------------------- */ + + +void glue_setInVol(float v, bool gui) +{ + G_Mixer.inVol = v; + if (!gui) { + Fl::lock(); + G_MainWin->mainIO->setInVol(v); + Fl::unlock(); + } +} + + +/* -------------------------------------------------------------------------- */ + + +void glue_clearAllSamples() +{ + G_Mixer.running = false; + for (unsigned i=0; iempty(); + G_Mixer.channels.at(i)->guiChannel->reset(); + } + G_Recorder.init(); + return; +} + + +/* -------------------------------------------------------------------------- */ + + +void glue_clearAllRecs() +{ + G_Recorder.init(); + gu_updateControls(); +} + + +/* -------------------------------------------------------------------------- */ + + +void glue_resetToInitState(bool resetGui, bool createColumns) +{ + G_Patch_DEPR_.setDefault(); + G_Mixer.close(); + G_Mixer.init(); + G_Recorder.init(); +#ifdef WITH_VST + G_PluginHost.freeAllStacks(&G_Mixer.channels, &G_Mixer.mutex_plugins); +#endif + + G_MainWin->keyboard->clear(); + if (createColumns) + G_MainWin->keyboard->init(); + + gu_updateMainWinLabel(G_DEFAULT_PATCH_NAME); + + if (resetGui) + gu_updateControls(); +} + + +/* -------------------------------------------------------------------------- */ + + +void glue_startStopMetronome(bool gui) +{ + G_Mixer.metronome = !G_Mixer.metronome; + if (!gui) { + Fl::lock(); + G_MainWin->mainTransport->updateMetronome(G_Mixer.metronome); + 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/main.h b/src/glue/main.h new file mode 100644 index 0000000..422f422 --- /dev/null +++ b/src/glue/main.h @@ -0,0 +1,81 @@ +/* --------------------------------------------------------------------- + * + * 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-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License 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 + + +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 user interaction on the GUI, +otherwise it's a MIDI/Jack/external signal. */ + +void glue_startStopSeq(bool gui=true); +void glue_startSeq (bool gui=true); +void glue_stopSeq (bool gui=true); +void glue_rewindSeq (); + +/* start/stopReadingRecs +Handles the 'R' button. If gui == true the signal comes from an user interaction +on the GUI, otherwise it's a MIDI/Jack/external signal. */ + +void glue_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_setOutVol (float v, bool gui=true); +void glue_setInVol (float v, bool gui=true); + +void glue_clearAllSamples(); +void glue_clearAllRecs(); + +/* resetToInitState + * reset Giada to init state. If resetGui also refresh all widgets. If + * createColumns also build initial empty columns. */ + +void glue_resetToInitState(bool resetGui=true, bool createColumns=true); + +void glue_startStopMetronome(bool gui=true); + +/* beatsDivide/Multiply + * shrinks or enlarges the number of beats by 2. */ + +void glue_beatsMultiply(); +void glue_beatsDivide(); + +#endif diff --git a/src/glue/plugin.cpp b/src/glue/plugin.cpp new file mode 100644 index 0000000..8c4ee23 --- /dev/null +++ b/src/glue/plugin.cpp @@ -0,0 +1,73 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * glue + * Intermediate layer GUI <-> CORE. + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#ifdef WITH_VST + + +#include "../core/pluginHost.h" +#include "../core/mixer.h" +#include "../core/plugin.h" +#include "../core/channel.h" +#include "plugin.h" + + +extern Mixer G_Mixer; +extern PluginHost G_PluginHost; + + +Plugin *glue_addPlugin(Channel *ch, int index, int stackType) +{ + if (index >= G_PluginHost.countAvailablePlugins()) + return nullptr; + + return G_PluginHost.addPlugin(index, stackType, &G_Mixer.mutex_plugins, ch); +} + + +/* -------------------------------------------------------------------------- */ + + +void glue_swapPlugins(Channel *ch, int index1, int index2, int stackType) +{ + G_PluginHost.swapPlugin(index1, index2, stackType, &G_Mixer.mutex_plugins, + ch); +} + + +/* -------------------------------------------------------------------------- */ + + +void glue_freePlugin(Channel *ch, int index, int stackType) +{ + G_PluginHost.freePlugin(index, stackType, &G_Mixer.mutex_plugins, ch); +} + + +#endif diff --git a/src/glue/plugin.h b/src/glue/plugin.h new file mode 100644 index 0000000..02e8278 --- /dev/null +++ b/src/glue/plugin.h @@ -0,0 +1,46 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * glue + * Intermediate layer GUI <-> CORE. + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License 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_PLUGIN_H__ +#define __GLUE_PLUGIN_H__ + + +#ifdef WITH_VST + +class Plugin *glue_addPlugin(class Channel *ch, int index, int stackType); + +void glue_swapPlugins(class Channel *ch, int indexP1, int indexP2, int stackType); + +void glue_freePlugin(class Channel *ch, int index, int stackType); + +#endif + + +#endif diff --git a/src/glue/storage.cpp b/src/glue/storage.cpp new file mode 100644 index 0000000..997389a --- /dev/null +++ b/src/glue/storage.cpp @@ -0,0 +1,514 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * glue + * Intermediate layer GUI <-> CORE. + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include "../core/mixer.h" +#include "../core/mixerHandler.h" +#include "../core/channel.h" +#include "../core/pluginHost.h" +#include "../core/plugin.h" +#include "../core/conf.h" +#include "../core/patch.h" +#include "../core/patch_DEPR_.h" // TODO - remove, used only for DEPR calls +#include "../core/sampleChannel.h" +#include "../core/midiChannel.h" +#include "../core/wave.h" +#include "../utils/gui.h" +#include "../utils/log.h" +#include "../gui/elems/mainWindow/keyboard/column.h" +#include "../gui/elems/mainWindow/keyboard/keyboard.h" +#include "../gui/dialogs/gd_mainWindow.h" +#include "../gui/dialogs/gd_warnings.h" +#include "../gui/dialogs/gd_browser.h" +#include "main.h" // TODO - remove, used only for DEPR calls +#include "channel.h" +#include "storage.h" + + +using std::string; +using std::vector; + + +extern gdMainWindow *G_MainWin; +extern Mixer G_Mixer; +extern Recorder G_Recorder; +extern Patch G_Patch; +extern Conf G_Conf; +extern Patch_DEPR_ G_Patch_DEPR_; // TODO - remove, used only for DEPR calls +#ifdef WITH_VST +extern PluginHost G_PluginHost; +#endif + + +#ifdef WITH_VST + +static void __glue_fillPatchGlobalsPlugins__(vector *host, vector *patch) +{ + for (unsigned i=0; isize(); i++) { + Plugin *pl = host->at(i); + Patch::plugin_t ppl; + ppl.path = pl->getUniqueId(); + ppl.bypass = pl->isBypassed(); + int numParams = pl->getNumParameters(); + for (int k=0; kgetParameter(k)); + patch->push_back(ppl); + } +} + +#endif + + +/* -------------------------------------------------------------------------- */ + + +static void __glue_fillPatchColumns__() +{ + for (unsigned i=0; ikeyboard->getTotalColumns(); i++) { + geColumn *gCol = G_MainWin->keyboard->getColumn(i); + Patch::column_t pCol; + pCol.index = gCol->getIndex(); + pCol.width = gCol->w(); + for (int k=0; kcountChannels(); k++) { + Channel *colChannel = gCol->getChannel(k); + for (unsigned j=0; jindex); + break; + } + } + } + G_Patch.columns.push_back(pCol); + } +} + + +/* -------------------------------------------------------------------------- */ + + +static void __glue_fillPatchChannels__(bool isProject) +{ + for (unsigned i=0; iwritePatch(i, isProject, &G_Patch); + } +} + + +/* -------------------------------------------------------------------------- */ + + +static void __glue_fillPatchGlobals__(const string &name) +{ + G_Patch.version = G_VERSION_STR; + G_Patch.versionMajor = G_VERSION_MAJOR; + G_Patch.versionMinor = G_VERSION_MINOR; + G_Patch.versionPatch = G_VERSION_PATCH; + G_Patch.name = name; + G_Patch.bpm = G_Mixer.bpm; + G_Patch.bars = G_Mixer.bars; + G_Patch.beats = G_Mixer.beats; + G_Patch.quantize = G_Mixer.quantize; + G_Patch.masterVolIn = G_Mixer.inVol; + G_Patch.masterVolOut = G_Mixer.outVol; + G_Patch.metronome = G_Mixer.metronome; + +#ifdef WITH_VST + + __glue_fillPatchGlobalsPlugins__(G_PluginHost.getStack(PluginHost::MASTER_IN), + &G_Patch.masterInPlugins); + __glue_fillPatchGlobalsPlugins__(G_PluginHost.getStack(PluginHost::MASTER_OUT), + &G_Patch.masterOutPlugins); + +#endif +} + + +/* -------------------------------------------------------------------------- */ + + +static bool __glue_savePatch__(const string &fullPath, const string &name, + bool isProject) +{ + G_Patch.init(); + + __glue_fillPatchGlobals__(name); + __glue_fillPatchChannels__(isProject); + __glue_fillPatchColumns__(); + + if (G_Patch.write(fullPath)) { + gu_updateMainWinLabel(name); + gu_log("[glue_savePatch] patch saved as %s\n", fullPath.c_str()); + return true; + } + return false; +} + + +/* -------------------------------------------------------------------------- */ + + +void glue_savePatch(void *data) +{ + gdSaveBrowser *browser = (gdSaveBrowser*) data; + string name = browser->getName(); + string fullPath = browser->getCurrentPath() + G_SLASH + gu_stripExt(name) + ".gptc"; + + if (name == "") { + gdAlert("Please choose a file name."); + return; + } + + if (gu_fileExists(fullPath)) + if (!gdConfirmWin("Warning", "File exists: overwrite?")) + return; + + if (__glue_savePatch__(fullPath, name, false)) { // false == not a project + G_Conf.patchPath = gu_dirname(fullPath); + browser->do_callback(); + } + else + gdAlert("Unable to save the patch!"); +} + + +/* -------------------------------------------------------------------------- */ + + +void glue_loadPatch(void *data) +{ + gdLoadBrowser *browser = (gdLoadBrowser*) data; + string fullPath = browser->getSelectedItem(); + bool isProject = gu_isProject(browser->getSelectedItem()); + + browser->showStatusBar(); + + gu_log("[glue] loading %s...\n", fullPath.c_str()); + + string fileToLoad = fullPath; // patch file to read from + string basePath = ""; // base path, in case of reading from a project + if (isProject) { + fileToLoad = fullPath + G_SLASH + gu_stripExt(gu_basename(fullPath)) + ".gptc"; + basePath = fullPath + G_SLASH; + } + + /* try to load the new JSON-based patch. If it fails, fall back to deprecated + * one. */ + + int res = G_Patch.read(fileToLoad); + bool deprecated = false; + + if (res == PATCH_UNREADABLE) { + gu_log("[glue] failed reading JSON-based patch. Trying with the deprecated method\n"); + deprecated = true; + res = glue_loadPatch__DEPR__(gu_basename(fileToLoad).c_str(), fileToLoad.c_str(), + browser->getStatusBar(), isProject); + } + + if (res != PATCH_READ_OK) { + if (res == PATCH_UNREADABLE) + isProject ? gdAlert("This project is unreadable.") : gdAlert("This patch is unreadable."); + else + if (res == PATCH_INVALID) + isProject ? gdAlert("This project is not valid.") : gdAlert("This patch is not valid."); + + browser->hideStatusBar(); + return; + } + else + if (deprecated) { + browser->do_callback(); + return; + } + + /* close all other windows. This prevents segfault if plugin + * windows GUIs are on. */ + + gu_closeAllSubwindows(); + + /* reset the system. False(1): don't update the gui right now. False(2): do + * not create empty columns. */ + + glue_resetToInitState(false, false); + + browser->setStatusBar(0.1f); + //__glue_setProgressBar__(status, 0.1f); + + /* Add common stuff, columns and channels. Also increment the progress bar + * by 0.8 / total_channels steps. */ + + float steps = 0.8 / G_Patch.channels.size(); + for (unsigned i=0; ikeyboard->addColumn(col->width); + for (unsigned k=0; kindex) { + Channel *ch = glue_addChannel(G_Patch.channels.at(k).column, + G_Patch.channels.at(k).type); + ch->readPatch(basePath, k, &G_Patch, &G_Mixer.mutex_plugins, + G_Conf.samplerate, G_Conf.rsmpQuality); + } + //__glue_setProgressBar__(status, steps); + browser->setStatusBar(steps); + } + } + + /* fill Mixer */ + + mh_readPatch(); + + /* let recorder recompute the actions' positions if the current + * samplerate != patch samplerate */ + + G_Recorder.updateSamplerate(G_Conf.samplerate, G_Patch.samplerate); + + /* save patchPath by taking the last dir of the broswer, in order to + * reuse it the next time */ + + G_Conf.patchPath = gu_dirname(fullPath); + + /* refresh GUI */ + + gu_updateControls(); + gu_updateMainWinLabel(G_Patch.name); + + browser->setStatusBar(0.1f); + //__glue_setProgressBar__(status, 1.0f); + + gu_log("[glue] patch loaded successfully\n"); + +#ifdef WITH_VST + + if (G_PluginHost.hasMissingPlugins()) + gdAlert("Some plugins were not loaded successfully.\nCheck the plugin browser to know more."); + +#endif + + browser->do_callback(); +} + + +/* -------------------------------------------------------------------------- */ + + +int glue_loadPatch__DEPR__(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_DEPR_.open(fpath); + if (res != PATCH_READ_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_DEPR_(isProject, gu_dirname(fpath).c_str()); + + /* take the patch name and update the main window's title */ + + G_Patch_DEPR_.getName(); + gu_updateMainWinLabel(G_Patch_DEPR_.name); + + status->value(0.4f); // progress status: 0.4 + //Fl::check(); + Fl::wait(0); + + G_Patch_DEPR_.readRecs(); + status->value(0.6f); // progress status: 0.6 + //Fl::check(); + Fl::wait(0); + +#ifdef WITH_VST + int resPlugins = G_Patch_DEPR_.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 */ + + G_Recorder.updateSamplerate(G_Conf.samplerate, G_Patch_DEPR_.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.patchPath = gu_dirname(fpath).c_str(); + + gu_log("[glue] patch %s loaded\n", fname); + +#ifdef WITH_VST + if (resPlugins != 1) + gdAlert("Some VST plugins were not loaded successfully."); +#endif + + gdAlert("This patch is using a deprecated format.\nPlease save it again to store it properly."); + + return res; +} + + +/* -------------------------------------------------------------------------- */ + + +void glue_saveProject(void *data) +{ + gdSaveBrowser *browser = (gdSaveBrowser*) data; + string name = browser->getName(); + string folderPath = browser->getCurrentPath(); //browser->getSelectedItem(); + string fullPath = folderPath + G_SLASH + gu_stripExt(name) + ".gprj"; + + if (name == "") { + gdAlert("Please choose a project name."); + return; + } + + if (gu_isProject(fullPath) && !gdConfirmWin("Warning", "Project exists: overwrite?")) + return; + + if (!gu_dirExists(fullPath) && !gu_mkdir(fullPath)) { + gu_log("[glue_saveProject] unable to make project directory!\n"); + return; + } + + gu_log("[glue_saveProject] project dir created: %s\n", fullPath.c_str()); + + /* copy all samples inside the folder. Takes and logical ones are saved + * via glue_saveSample() */ + + for (unsigned i=0; itype == CHANNEL_MIDI) + continue; + + 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). Also remove any existing file. */ + + string samplePath = fullPath + G_SLASH + ch->wave->basename(true); + + if (gu_fileExists(samplePath)) + remove(samplePath.c_str()); + if (ch->save(samplePath.c_str())) + ch->wave->pathfile = samplePath; + } + + string gptcPath = fullPath + G_SLASH + gu_stripExt(name) + ".gptc"; + if (__glue_savePatch__(gptcPath, name, true)) // true == it's a project + browser->do_callback(); + else + gdAlert("Unable to save the project!"); +} + + +/* -------------------------------------------------------------------------- */ + + +void glue_loadSample(void *data) +{ + gdLoadBrowser *browser = (gdLoadBrowser*) data; + string fullPath = browser->getSelectedItem(); + + if (fullPath.empty()) + return; + + int res = glue_loadChannel((SampleChannel*) browser->getChannel(), fullPath.c_str()); + + if (res == SAMPLE_LOADED_OK) { + G_Conf.samplePath = gu_dirname(fullPath); + browser->do_callback(); + G_MainWin->delSubWindow(WID_SAMPLE_EDITOR); // if editor is open + } + else + G_MainWin->keyboard->printChannelMessage(res); +} + + +/* -------------------------------------------------------------------------- */ + + +void glue_saveSample(void *data) +{ + gdSaveBrowser *browser = (gdSaveBrowser*) data; + string name = browser->getName(); + string folderPath = browser->getSelectedItem(); + + if (name == "") { + gdAlert("Please choose a file name."); + return; + } + + /* bruteforce check extension. */ + + string filePath = folderPath + G_SLASH + gu_stripExt(name) + ".wav"; + + if (gu_fileExists(filePath)) + if (!gdConfirmWin("Warning", "File exists: overwrite?")) + return; + + if (((SampleChannel*)browser->getChannel())->save(filePath.c_str())) { + G_Conf.samplePath = gu_dirname(folderPath); + browser->do_callback(); + } + else + gdAlert("Unable to save this sample!"); +} diff --git a/src/glue/storage.h b/src/glue/storage.h new file mode 100644 index 0000000..a95ec0a --- /dev/null +++ b/src/glue/storage.h @@ -0,0 +1,43 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * glue + * Intermediate layer GUI <-> CORE. + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License 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_STORAGE_H +#define GLUE_STORAGE_H + + +void glue_loadPatch (void *data); +int glue_loadPatch__DEPR__(const char *fname, const char *fpath, class gProgress *status, bool isProject); +void glue_savePatch (void *data); +void glue_saveProject(void *data); +void glue_saveSample (void *data); +void glue_loadSample (void *data); + + +#endif diff --git a/src/gui/dialogs/gd_about.cpp b/src/gui/dialogs/gd_about.cpp new file mode 100644 index 0000000..e47076d --- /dev/null +++ b/src/gui/dialogs/gd_about.cpp @@ -0,0 +1,148 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * gd_about + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include +#include "../../core/conf.h" +#include "../../core/const.h" +#include "../../core/kernelAudio.h" +#include "../../core/kernelMidi.h" +#include "../../core/graphics.h" +#ifdef WITH_VST + #include "../../deps/juce-config.h" +#endif +#include "../../utils/gui.h" +#include "../elems/ge_mixed.h" +#include "gd_about.h" + + +extern Conf G_Conf; +extern KernelAudio G_KernelAudio; +extern KernelMidi G_KernelMidi; + + +gdAbout::gdAbout() +#ifdef WITH_VST +: gWindow(340, 435, "About Giada") +{ +#else +: gWindow(340, 350, "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 " G_VERSION_STR " (" BUILD_DATE ")\n\n" + "Developed by Monocasual\n" + "Based on FLTK (%d.%d.%d), RtAudio (%s),\n" + "RtMidi (%s), Libsamplerate, Jansson (%s),\n" + "Libsndfile" +#ifdef WITH_VST + ", JUCE (%d.%d.%d)\n\n" +#else + "\n\n" +#endif + "Released under the terms of the GNU General\n" + "Public License (GPL v3)\n\n" + "News, infos, contacts and documentation:\n" + "www.giadamusic.com", + FL_MAJOR_VERSION, FL_MINOR_VERSION, FL_PATCH_VERSION, + G_KernelAudio.getRtAudioVersion().c_str(), + G_KernelMidi.getRtMidiVersion().c_str(), + JANSSON_VERSION +#ifdef WITH_VST + , JUCE_MAJOR_VERSION, JUCE_MINOR_VERSION, JUCE_BUILDNUMBER); +#else + ); +#endif + + 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/gui/dialogs/gd_about.h b/src/gui/dialogs/gd_about.h new file mode 100644 index 0000000..2b8b6d3 --- /dev/null +++ b/src/gui/dialogs/gd_about.h @@ -0,0 +1,59 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * gd_about + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License 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 "../elems/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/gui/dialogs/gd_actionEditor.cpp b/src/gui/dialogs/gd_actionEditor.cpp new file mode 100644 index 0000000..cf860be --- /dev/null +++ b/src/gui/dialogs/gd_actionEditor.cpp @@ -0,0 +1,473 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * gd_actionEditor + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include +#include "../../utils/gui.h" +#include "../../core/graphics.h" +#include "../../core/mixer.h" +#include "../../core/recorder.h" +#include "../../core/conf.h" +#include "../../core/channel.h" +#include "../../core/sampleChannel.h" +#include "../elems/actionEditor.h" +#include "../elems/envelopeEditor.h" +#include "../elems/muteEditor.h" +#include "../elems/noteEditor.h" +#include "../elems/ge_mixed.h" +#include "../elems/basics/scroll.h" +#include "gd_actionEditor.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 geScroll(8, 36, w()-16, h()-44); + + if (chan->type == CHANNEL_SAMPLE) { + + SampleChannel *ch = (SampleChannel*) chan; + + ac = new geActionEditor (scroller->x(), upperArea->y()+upperArea->h()+8, this, ch); + mc = new geMuteEditor (scroller->x(), ac->y()+ac->h()+8, this); + vc = new geEnvelopeEditor(scroller->x(), mc->y()+mc->h()+8, this, 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 geNoteEditor(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.push_back(i); + frames.push_back(j); + } + if (j % G_Mixer.framesPerBeat == 0) + beats.push_back(i); + if (j % G_Mixer.framesPerBar == 0 && i != 1) + bars.push_back(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/gui/dialogs/gd_actionEditor.h b/src/gui/dialogs/gd_actionEditor.h new file mode 100644 index 0000000..c7f60f2 --- /dev/null +++ b/src/gui/dialogs/gd_actionEditor.h @@ -0,0 +1,135 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * gd_actionEditor + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + + +#ifndef GD_ACTIONEDITOR_H +#define GD_ACTIONEDITOR_H + +#include +#include +#include +#include +#include "../elems/ge_window.h" + + +using std::vector; + + +/* 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 geScroll *scroller; // widget container + + class geActionEditor *ac; + class geMuteEditor *mc; + class geEnvelopeEditor *vc; + class geNoteEditor *pr; + + vector 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(); + + vector points; // points of the grid + vector frames; // frames of the grid + + vector bars; + vector beats; +}; + + +#endif diff --git a/src/gui/dialogs/gd_beatsInput.cpp b/src/gui/dialogs/gd_beatsInput.cpp new file mode 100644 index 0000000..35ca62e --- /dev/null +++ b/src/gui/dialogs/gd_beatsInput.cpp @@ -0,0 +1,103 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * gd_beatsInput + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + + +#include "../../utils/gui.h" +#include "../../core/mixer.h" +#include "../../core/conf.h" +#include "../../core/const.h" +#include "../../glue/main.h" +#include "../elems/ge_mixed.h" +#include "gd_beatsInput.h" +#include "gd_mainWindow.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/gui/dialogs/gd_beatsInput.h b/src/gui/dialogs/gd_beatsInput.h new file mode 100644 index 0000000..a20fc4a --- /dev/null +++ b/src/gui/dialogs/gd_beatsInput.h @@ -0,0 +1,56 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * gd_beatsInput + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License 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 "../elems/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/gui/dialogs/gd_bpmInput.cpp b/src/gui/dialogs/gd_bpmInput.cpp new file mode 100644 index 0000000..4ad1da2 --- /dev/null +++ b/src/gui/dialogs/gd_bpmInput.cpp @@ -0,0 +1,106 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * gd_bpmInput + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + + +#include "../../core/conf.h" +#include "../../core/const.h" +#include "../../core/mixer.h" +#include "../../glue/main.h" +#include "../../utils/gui.h" +#include "../elems/ge_mixed.h" +#include "gd_bpmInput.h" +#include "gd_mainWindow.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/gui/dialogs/gd_bpmInput.h b/src/gui/dialogs/gd_bpmInput.h new file mode 100644 index 0000000..cfca7d8 --- /dev/null +++ b/src/gui/dialogs/gd_bpmInput.h @@ -0,0 +1,51 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * gd_bpmInput + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License 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 "../elems/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/gui/dialogs/gd_browser.cpp b/src/gui/dialogs/gd_browser.cpp new file mode 100644 index 0000000..cbb7e46 --- /dev/null +++ b/src/gui/dialogs/gd_browser.cpp @@ -0,0 +1,315 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * gd_browser + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include "../../core/graphics.h" +#include "../../core/conf.h" +#include "../../core/const.h" +#include "../../utils/gui.h" +#include "../elems/ge_mixed.h" +#include "../elems/browser.h" +#include "gd_browser.h" + + +using std::string; + + +extern Conf G_Conf; + + +gdBaseBrowser::gdBaseBrowser(int x, int y, int w, int h, const string &title, + const string &path, void (*callback)(void*)) + : gWindow(x, y, w, h, title.c_str()), callback(callback) +{ + set_non_modal(); + + groupTop = new Fl_Group(8, 8, w-16, 40); + hiddenFiles = new gCheck(groupTop->x(), groupTop->y(), 400, 20, "Show hidden files"); + where = new gInput(groupTop->x(), hiddenFiles->y()+hiddenFiles->h(), 152, 20); + updir = new gClick(groupTop->x()+groupTop->w()-20, where->y(), 20, 20, "", updirOff_xpm, updirOn_xpm); + groupTop->end(); + groupTop->resizable(where); + + hiddenFiles->callback(cb_toggleHiddenFiles, (void*) this); + + where->readonly(true); + where->cursor_color(COLOR_BG_DARK); + where->value(path.c_str()); + + updir->callback(cb_up, (void*) this); + + browser = new geBrowser(8, groupTop->y()+groupTop->h()+8, w-16, h-93); + browser->loadDir(path); + if (path == G_Conf.browserLastPath) + browser->preselect(G_Conf.browserPosition, G_Conf.browserLastValue); + + Fl_Group *groupButtons = new Fl_Group(8, browser->y()+browser->h()+8, w-16, 20); + ok = new gClick(w-88, groupButtons->y(), 80, 20); + cancel = new gClick(w-ok->w()-96, groupButtons->y(), 80, 20, "Cancel"); + status = new gProgress(8, groupButtons->y(), cancel->x()-16, 20); + status->minimum(0); + status->maximum(1); + status->hide(); // show the bar only if necessary + groupButtons->resizable(status); + groupButtons->end(); + + end(); + + cancel->callback(cb_close, (void*) this); + + resizable(browser); + size_range(320, 200); + + gu_setFavicon(this); + show(); +} + + +/* -------------------------------------------------------------------------- */ + + +gdBaseBrowser::~gdBaseBrowser() +{ + G_Conf.browserX = x(); + G_Conf.browserY = y(); + G_Conf.browserW = w(); + G_Conf.browserH = h(); + G_Conf.browserPosition = browser->position(); + G_Conf.browserLastPath = gu_dirname(browser->getSelectedItem()); + G_Conf.browserLastValue = browser->value(); +} + + +/* -------------------------------------------------------------------------- */ + + +void gdBaseBrowser::cb_up (Fl_Widget *v, void *p) { ((gdBaseBrowser*)p)->__cb_up(); } +void gdBaseBrowser::cb_close(Fl_Widget *v, void *p) { ((gdBaseBrowser*)p)->__cb_close(); } +void gdBaseBrowser::cb_toggleHiddenFiles(Fl_Widget *v, void *p) { ((gdBaseBrowser*)p)->__cb_toggleHiddenFiles(); } + + +/* -------------------------------------------------------------------------- */ + + +void gdBaseBrowser::__cb_up() +{ + string dir = browser->getCurrentDir(); + dir = dir.substr(0, dir.find_last_of(G_SLASH_STR)); // remove up to the next slash + browser->loadDir(dir); + where->value(browser->getCurrentDir().c_str()); +} + + +/* -------------------------------------------------------------------------- */ + + +void gdBaseBrowser::__cb_close() +{ + do_callback(); +} + + +/* -------------------------------------------------------------------------- */ + + +void gdBaseBrowser::__cb_toggleHiddenFiles() +{ + browser->toggleHiddenFiles(); +} + + +/* -------------------------------------------------------------------------- */ + + +void gdBaseBrowser::setStatusBar(float v) +{ + status->value(status->value() + v); + Fl::wait(0); +} + + +/* -------------------------------------------------------------------------- */ + + +void gdBaseBrowser::showStatusBar() +{ + status->show(); +} + + +/* -------------------------------------------------------------------------- */ + + +void gdBaseBrowser::hideStatusBar() +{ + status->hide(); +} + + +/* -------------------------------------------------------------------------- */ + + +string gdBaseBrowser::getCurrentPath() +{ + return where->value(); +} + + +/* -------------------------------------------------------------------------- */ + + +string gdBaseBrowser::getSelectedItem() +{ + return browser->getSelectedItem(); +} + + +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ + + +gdSaveBrowser::gdSaveBrowser(int x, int y, int w, int h, const string &title, + const string &path, const string &_name, void (*cb)(void*), Channel *ch) + : gdBaseBrowser(x, y, w, h, title, path, cb) +{ + channel = ch; + + where->size(groupTop->w()-236, 20); + + name = new gInput(where->x()+where->w()+8, where->y(), 200, 20); + name->value(_name.c_str()); + groupTop->add(name); + + browser->callback(cb_down, (void*) this); + + ok->label("Save"); + ok->callback(cb_save, (void*) this); + ok->shortcut(FL_ENTER); +} + + +/* -------------------------------------------------------------------------- */ + + +void gdSaveBrowser::cb_save(Fl_Widget *v, void *p) { ((gdSaveBrowser*)p)->__cb_save(); } +void gdSaveBrowser::cb_down(Fl_Widget *v, void *p) { ((gdSaveBrowser*)p)->__cb_down(); } + + +/* -------------------------------------------------------------------------- */ + + +void gdSaveBrowser::__cb_down() +{ + string path = browser->getSelectedItem(); + + if (path.empty()) // when click on an empty area + return; + + /* if the selected item is a directory just load its content. If it's a file + * use it as the file name (i.e. fill name->value()). */ + + if (gu_isDir(path)) { + browser->loadDir(path); + where->value(browser->getCurrentDir().c_str()); + } + else + name->value(browser->getSelectedItem(false).c_str()); +} + + +/* -------------------------------------------------------------------------- */ + + +string gdSaveBrowser::getName() +{ + return name->value(); +} + + +/* -------------------------------------------------------------------------- */ + + +void gdSaveBrowser::__cb_save() +{ + callback((void*) this); +} + + +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ + + +gdLoadBrowser::gdLoadBrowser(int x, int y, int w, int h, const string &title, + const string &path, void (*cb)(void*), Channel *ch) + : gdBaseBrowser(x, y, w, h, title, path, cb) +{ + channel = ch; + + where->size(groupTop->w()-updir->w()-8, 20); + + browser->callback(cb_down, (void*) this); + + ok->label("Load"); + ok->callback(cb_load, (void*) this); + ok->shortcut(FL_ENTER); +} + + + + +/* -------------------------------------------------------------------------- */ + + +void gdLoadBrowser::cb_load(Fl_Widget *v, void *p) { ((gdLoadBrowser*)p)->__cb_load(); } +void gdLoadBrowser::cb_down(Fl_Widget *v, void *p) { ((gdLoadBrowser*)p)->__cb_down(); } + + +/* -------------------------------------------------------------------------- */ + + +void gdLoadBrowser::__cb_load() +{ + callback((void*) this); +} + + +/* -------------------------------------------------------------------------- */ + + +void gdLoadBrowser::__cb_down() +{ + string path = browser->getSelectedItem(); + + if (path.empty() || !gu_isDir(path)) // when click on an empty area or not a dir + return; + + browser->loadDir(path); + where->value(browser->getCurrentDir().c_str()); +} diff --git a/src/gui/dialogs/gd_browser.h b/src/gui/dialogs/gd_browser.h new file mode 100644 index 0000000..5606b52 --- /dev/null +++ b/src/gui/dialogs/gd_browser.h @@ -0,0 +1,136 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * gd_browser + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License 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 "../elems/ge_window.h" + + +class gdBaseBrowser : public gWindow +{ +protected: + + class Fl_Group *groupTop; + class gCheck *hiddenFiles; + class geBrowser *browser; + class gClick *ok; + class gClick *cancel; + class gInput *where; + class gClick *updir; + class gProgress *status; + + static void cb_up (Fl_Widget *v, void *p); + static void cb_close (Fl_Widget *w, void *p); + static void cb_toggleHiddenFiles(Fl_Widget *w, void *p); + + inline void __cb_up (); + inline void __cb_close (); + inline void __cb_toggleHiddenFiles(); + + /* Callback + * Fired when the save/load button is pressed. */ + + void (*callback)(void*); + + class Channel *channel; + +public: + + gdBaseBrowser(int x, int y, int w, int h, const string &title, + const string &path, void (*callback)(void*)); + + ~gdBaseBrowser(); + + /* getSelectedItem + * Return the full path of the selected file. */ + + string getSelectedItem(); + + /* setStatusBar + * Increment status bar for progress tracking. */ + + void setStatusBar(float v); + + gProgress *getStatusBar() { return status; } // TODO - remove with Patch_DEPR_ + void showStatusBar(); + void hideStatusBar(); + string getCurrentPath(); + + Channel *getChannel() { return channel; } + void fireCallback() { callback((void*) this); } +}; + + +/* -------------------------------------------------------------------------- */ + + +class gdSaveBrowser : public gdBaseBrowser +{ +private: + + class gInput *name; + + static void cb_down(Fl_Widget *v, void *p); + static void cb_save(Fl_Widget *w, void *p); + + inline void __cb_down(); + inline void __cb_save(); + +public: + + gdSaveBrowser(int x, int y, int w, int h, const string &title, + const string &path, const string &name, void (*callback)(void*), + class Channel *ch); + + string getName(); +}; + + +/* -------------------------------------------------------------------------- */ + + +class gdLoadBrowser : public gdBaseBrowser +{ +private: + + static void cb_load(Fl_Widget *w, void *p); + static void cb_down(Fl_Widget *v, void *p); + + inline void __cb_load(); + inline void __cb_down(); + +public: + + gdLoadBrowser(int x, int y, int w, int h, const string &title, + const string &path, void (*callback)(void*), class Channel *ch); +}; + +#endif diff --git a/src/gui/dialogs/gd_config.cpp b/src/gui/dialogs/gd_config.cpp new file mode 100644 index 0000000..10c49ad --- /dev/null +++ b/src/gui/dialogs/gd_config.cpp @@ -0,0 +1,1025 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * gd_config + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include +#include +#include "../../core/conf.h" +#include "../../core/midiMapConf.h" +#include "../../core/patch_DEPR_.h" +#include "../../core/kernelAudio.h" +#include "../../core/kernelMidi.h" +#include "../../core/pluginHost.h" +#include "../../utils/gui.h" +#include "../../utils/log.h" +#include "../../utils/string.h" +#include "../elems/ge_mixed.h" +#include "../elems/basics/boxtypes.h" +#include "gd_config.h" +#include "gd_keyGrabber.h" +#include "gd_devInfo.h" +#include "gd_browser.h" + + +extern Patch_DEPR_ G_Patch_DEPR_; +extern Conf G_Conf; +extern KernelAudio G_KernelAudio; +extern KernelMidi G_KernelMidi; +extern bool G_audio_status; +extern MidiMapConf G_MidiMap; + +#ifdef WITH_VST +extern PluginHost G_PluginHost; +#endif + + +using std::string; + + +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(GUI_FONT_SIZE_BASE); + + 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(GUI_FONT_SIZE_BASE); + + soundsys->add("(none)"); + +#if defined(__linux__) + + if (G_KernelAudio.hasAPI(RtAudio::LINUX_ALSA)) + soundsys->add("ALSA"); + if (G_KernelAudio.hasAPI(RtAudio::UNIX_JACK)) + soundsys->add("Jack"); + if (G_KernelAudio.hasAPI(RtAudio::LINUX_PULSE)) + soundsys->add("PulseAudio"); + + switch (G_Conf.soundSystem) { + case SYS_API_NONE: + soundsys->showItem("(none)"); + break; + case SYS_API_ALSA: + soundsys->showItem("ALSA"); + break; + case SYS_API_JACK: + soundsys->showItem("Jack"); + buffersize->deactivate(); + samplerate->deactivate(); + break; + case SYS_API_PULSE: + soundsys->showItem("PulseAudio"); + break; + } + +#elif defined(_WIN32) + + if (G_KernelAudio.hasAPI(RtAudio::WINDOWS_DS)) + soundsys->add("DirectSound"); + if (G_KernelAudio.hasAPI(RtAudio::WINDOWS_ASIO)) + soundsys->add("ASIO"); + if (G_KernelAudio.hasAPI(RtAudio::WINDOWS_WASAPI)) + soundsys->add("WASAPI"); + + switch (G_Conf.soundSystem) { + case SYS_API_NONE: + soundsys->showItem("(none)"); + break; + case SYS_API_DS: + soundsys->showItem("DirectSound"); + break; + case SYS_API_ASIO: + soundsys->showItem("ASIO"); + break; + case SYS_API_WASAPI: + soundsys->showItem("WASAPI"); + break; + } + +#elif defined (__APPLE__) + + if (G_KernelAudio.hasAPI(RtAudio::MACOSX_CORE)) + soundsys->add("CoreAudio"); + + switch (G_Conf.soundSystem) { + case SYS_API_NONE: + soundsys->showItem("(none)"); + break; + case SYS_API_CORE: + soundsys->showItem("CoreAudio"); + break; + } + +#endif + + soundsysInitValue = soundsys->value(); + + soundsys->callback(cb_deactivate_sounddev, (void*)this); + + sounddevIn->callback(cb_fetchInChans, this); + sounddevOut->callback(cb_fetchOutChans, this); + + devOutInfo->callback(cb_showOutputInfo, this); + devInInfo->callback(cb_showInputInfo, this); + + if (G_Conf.soundSystem != SYS_API_NONE) { + fetchSoundDevs(); + fetchOutChans(sounddevOut->value()); + fetchInChans(sounddevIn->value()); + + /* fill frequency dropdown menu */ + /* TODO - add fetchFrequencies() */ + + int nfreq = G_KernelAudio.getTotalFreqs(sounddevOut->value()); + for (int i=0; ivalue(), i); + samplerate->add(gu_itoa(freq).c_str()); + if (freq == G_Conf.samplerate) + samplerate->value(i); + } + } + else { + sounddevIn->deactivate(); + sounddevOut->deactivate(); + channelsIn->deactivate(); + channelsOut->deactivate(); + devOutInfo->deactivate(); + devInInfo->deactivate(); + samplerate->deactivate(); + } + + buffersize->add("8"); + buffersize->add("16"); + buffersize->add("32"); + buffersize->add("64"); + buffersize->add("128"); + buffersize->add("256"); + buffersize->add("512"); + buffersize->add("1024"); + buffersize->add("2048"); + buffersize->add("4096"); + buffersize->showItem(gu_itoa(G_Conf.buffersize).c_str()); + + rsmpQuality->add("Sinc best quality (very slow)"); + rsmpQuality->add("Sinc medium quality (slow)"); + rsmpQuality->add("Sinc basic quality (medium)"); + rsmpQuality->add("Zero Order Hold (fast)"); + rsmpQuality->add("Linear (very fast)"); + rsmpQuality->value(G_Conf.rsmpQuality); + + delayComp->value(gu_itoa(G_Conf.delayComp).c_str()); + delayComp->type(FL_INT_INPUT); + delayComp->maximum_size(5); + + limitOutput->value(G_Conf.limitOutput); +} + + +/* -------------------------------------------------------------------------- */ + + +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 = G_KernelAudio.getDeviceByName(sounddevIn->text(sounddevIn->value())); + new gdDevInfo(dev); +} + + +/* -------------------------------------------------------------------------- */ + + +void gTabAudio::__cb_showOutputInfo() +{ + unsigned dev = G_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. Watch out if soundsysInitValue == 0: you don't want + * to query kernelAudio for '(none)' soundsystem! */ + + if (soundsysInitValue == soundsys->value() && soundsysInitValue != 0) { + sounddevOut->clear(); + sounddevIn->clear(); + + fetchSoundDevs(); + + /* the '?' button is added by fetchSoundDevs */ + + fetchOutChans(sounddevOut->value()); + sounddevOut->activate(); + channelsOut->activate(); + + /* chan menus and '?' button are activated by fetchInChans(...) */ + + fetchInChans(sounddevIn->value()); + sounddevIn->activate(); + samplerate->activate(); + } + else { + sounddevOut->deactivate(); + sounddevOut->clear(); + sounddevOut->add("-- restart to fetch device(s) --"); + sounddevOut->value(0); + channelsOut->deactivate(); + devOutInfo->deactivate(); + samplerate->deactivate(); + + sounddevIn->deactivate(); + sounddevIn->clear(); + sounddevIn->add("-- restart to fetch device(s) --"); + sounddevIn->value(0); + channelsIn->deactivate(); + devInInfo->deactivate(); + } +} + +/* -------------------------------------------------------------------------- */ + + +void 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 = G_KernelAudio.getDeviceByName(sounddevIn->text(sounddevIn->value())); + unsigned chs = G_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 = G_KernelAudio.getDeviceByName(sounddevOut->text(sounddevOut->value())); + unsigned chs = G_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 (G_KernelAudio.getDeviceName(device) == "") + continue; + if (m->text(i) == NULL) + continue; + if (m->text(i) == G_KernelAudio.getDeviceName(device)) + return i; + } + + return 0; +} + + +/* -------------------------------------------------------------------------- */ + + +void gTabAudio::fetchSoundDevs() +{ + if (G_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 (G_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() +{ + string text = soundsys->text(soundsys->value()); + + if (text == "(none)") { + G_Conf.soundSystem = SYS_API_NONE; + return; + } + +#if defined(__linux__) + + else if (text == "ALSA") + G_Conf.soundSystem = SYS_API_ALSA; + else if (text == "Jack") + G_Conf.soundSystem = SYS_API_JACK; + else if (text == "PulseAudio") + G_Conf.soundSystem = SYS_API_PULSE; + +#elif defined(_WIN32) + + else if (text == "DirectSound") + G_Conf.soundSystem = SYS_API_DS; + else if (text == "ASIO") + G_Conf.soundSystem = SYS_API_ASIO; + else if (text == "WASAPI") + G_Conf.soundSystem = SYS_API_WASAPI; + +#elif defined (__APPLE__) + + else if (text == "CoreAudio") + G_Conf.soundSystem = SYS_API_CORE; + +#endif + + /* use the device name to search into the drop down menu's */ + + G_Conf.soundDeviceOut = G_KernelAudio.getDeviceByName(sounddevOut->text(sounddevOut->value())); + G_Conf.soundDeviceIn = G_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()); + + G_Conf.delayComp = atoi(delayComp->value()); +} + + +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ + + +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"); + midiMap = new gChoice(x()+92, noNoteOff->y()+noNoteOff->h(), 253, 20, "Output Midi Map"); + sync = new gChoice(x()+92, midiMap->y()+midiMap->h()+8, 253, 20, "Sync"); + new gBox(x(), sync->y()+sync->h()+8, w(), h()-125, "Restart Giada for the changes to take effect."); + end(); + + labelsize(GUI_FONT_SIZE_BASE); + + system->callback(cb_changeSystem, (void*)this); + + fetchSystems(); + fetchOutPorts(); + fetchInPorts(); + fetchMidiMaps(); + + 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 (G_KernelMidi.numOutPorts == 0) { + portOut->add("-- no ports found --"); + portOut->value(0); + portOut->deactivate(); + } + else { + + portOut->add("(disabled)"); + + for (unsigned i=0; iadd(gu_removeFltkChars(G_KernelMidi.getOutPortName(i)).c_str()); + + portOut->value(G_Conf.midiPortOut+1); // +1 because midiPortOut=-1 is '(disabled)' + } +} + +/* -------------------------------------------------------------------------- */ + + +void gTabMidi::fetchInPorts() +{ + if (G_KernelMidi.numInPorts == 0) { + portIn->add("-- no ports found --"); + portIn->value(0); + portIn->deactivate(); + } + else { + + portIn->add("(disabled)"); + + for (unsigned i=0; iadd(gu_removeFltkChars(G_KernelMidi.getInPortName(i)).c_str()); + + portIn->value(G_Conf.midiPortIn+1); // +1 because midiPortIn=-1 is '(disabled)' + } +} + + +/* -------------------------------------------------------------------------- */ + + +void gTabMidi::fetchMidiMaps() +{ + if (G_MidiMap.maps.size() == 0) { + midiMap->add("(no MIDI maps available)"); + midiMap->value(0); + midiMap->deactivate(); + return; + } + + for (unsigned i=0; iadd(imap); + if (G_Conf.midiMapPath == imap) + midiMap->value(i); + } + + /* Preselect the 0 midimap if nothing is selected but midimaps exist. */ + + if (midiMap->value() == -1 && G_MidiMap.maps.size() > 0) + midiMap->value(0); +} + + +/* -------------------------------------------------------------------------- */ + + +void gTabMidi::save() +{ + string text = system->text(system->value()); + + if (text == "ALSA") + G_Conf.midiSystem = RtMidi::LINUX_ALSA; + else if (text == "Jack") + G_Conf.midiSystem = RtMidi::UNIX_JACK; + else if (text == "Multimedia MIDI") + G_Conf.midiSystem = RtMidi::WINDOWS_MM; + else if (text == "OSX Core MIDI") + 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(); + G_Conf.midiMapPath = G_MidiMap.maps.size() == 0 ? "" : midiMap->text(midiMap->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 (G_KernelMidi.hasAPI(RtMidi::LINUX_ALSA)) + system->add("ALSA"); + if (G_KernelMidi.hasAPI(RtMidi::UNIX_JACK)) + system->add("Jack"); + +#elif defined(_WIN32) + + if (G_KernelMidi.hasAPI(RtMidi::WINDOWS_MM)) + system->add("Multimedia MIDI"); + +#elif defined (__APPLE__) + + system->add("OSX Core MIDI"); + +#endif + + switch (G_Conf.midiSystem) { + case RtMidi::LINUX_ALSA: system->showItem("ALSA"); break; + case RtMidi::UNIX_JACK: system->showItem("Jack"); break; + case RtMidi::WINDOWS_MM: system->showItem("Multimedia MIDI"); break; + case RtMidi::MACOSX_CORE: system->showItem("OSX Core MIDI"); break; + default: system->value(0); break; + } +} + + +/* -------------------------------------------------------------------------- */ + + +void 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(); + portIn->clear(); + fetchInPorts(); + portIn->activate(); + noNoteOff->activate(); + sync->activate(); + } + else { + portOut->deactivate(); + portOut->clear(); + portOut->add("-- restart to fetch device(s) --"); + portOut->value(0); + portIn->deactivate(); + portIn->clear(); + portIn->add("-- restart to fetch device(s) --"); + portIn->value(0); + noNoteOff->deactivate(); + sync->deactivate(); + } + +} + + +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ + + +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(GUI_FONT_SIZE_BASE); + + 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; +} + + +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ + + +#ifdef WITH_VST + +gTabPlugins::gTabPlugins(int X, int Y, int W, int H) + : Fl_Group(X, Y, W, H, "Plugins") +{ + folderPath = new gInput(x()+w()-250, y()+8, 250, 20); + scanButton = new gClick(x()+w()-120, folderPath->y()+folderPath->h()+8, 120, 20); + info = new gBox(x(), scanButton->y()+scanButton->h()+8, w(), 242); + + end(); + + labelsize(GUI_FONT_SIZE_BASE); + + info->label("Scan in progress. Please wait..."); + info->hide(); + + folderPath->value(G_Conf.pluginPath.c_str()); + folderPath->label("Plugins folder"); + + scanButton->callback(cb_scan, (void*) this); + + updateCount(); +} + + +/* -------------------------------------------------------------------------- */ + + +void gTabPlugins::updateCount() +{ + string scanLabel = "Scan (" + gu_itoa(G_PluginHost.countAvailablePlugins()) + " found)"; + scanButton->label(scanLabel.c_str()); +} + + +/* -------------------------------------------------------------------------- */ + + +void gTabPlugins::cb_scan(Fl_Widget *w, void *p) { ((gTabPlugins*)p)->__cb_scan(w); } + + +/* -------------------------------------------------------------------------- */ + + +void gTabPlugins::cb_onScan(float progress, void *p) +{ + string l = "Scan in progress (" + gu_itoa((int)(progress*100)) + "%). Please wait..."; + ((gTabPlugins *)p)->info->label(l.c_str()); + Fl::wait(); +} + + +/* -------------------------------------------------------------------------- */ + + +void gTabPlugins::__cb_scan(Fl_Widget *w) +{ + info->show(); + G_PluginHost.scanDir(folderPath->value(), cb_onScan, (void*) this); + G_PluginHost.saveList(gu_getHomePath() + G_SLASH + "plugins.xml"); + info->hide(); + updateCount(); +} + + +/* -------------------------------------------------------------------------- */ + + +void gTabPlugins::save() +{ + G_Conf.pluginPath = folderPath->value(); +} + + +#endif // if WITH_VST + + +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ + + +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); + tabs->box(G_CUSTOM_BORDER_BOX); + tabs->labelcolor(COLOR_TEXT_0); + tabs->begin(); + + 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); +#ifdef WITH_VST + tabPlugins = new gTabPlugins(tabs->x()+10, tabs->y()+20, tabs->w()-20, tabs->h()-40); +#endif + + tabs->end(); + + save = new gClick (w-88, h-28, 80, 20, "Save"); + cancel = new gClick (w-176, h-28, 80, 20, "Cancel"); + + end(); + + save->callback(cb_save_config, (void*)this); + cancel->callback(cb_cancel, (void*)this); + + gu_setFavicon(this); + setId(WID_CONFIG); + show(); +} + + +/* -------------------------------------------------------------------------- */ + + +gdConfig::~gdConfig() +{ + 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(); +#ifdef WITH_VST + tabPlugins->save(); +#endif + do_callback(); +} + + +/* -------------------------------------------------------------------------- */ + + +void gdConfig::__cb_cancel() +{ + do_callback(); +} diff --git a/src/gui/dialogs/gd_config.h b/src/gui/dialogs/gd_config.h new file mode 100644 index 0000000..fc7b64a --- /dev/null +++ b/src/gui/dialogs/gd_config.h @@ -0,0 +1,208 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * gd_config + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License 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 "../elems/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; +#ifdef WITH_VST + class gTabPlugins *tabPlugins; +#endif + class gClick *save; + class gClick *cancel; +}; + + +/* -------------------------------------------------------------------------- */ + + +class gTabMidi : public Fl_Group +{ +private: + + void fetchSystems(); + void fetchOutPorts(); + void fetchInPorts(); + void fetchMidiMaps(); + + static void cb_changeSystem (Fl_Widget *w, void *p); + inline void __cb_changeSystem(); + + int systemInitValue; + +public: + + class gChoice *system; + class gChoice *portOut; + class gChoice *portIn; + class gCheck *noNoteOff; + class gChoice *midiMap; + 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(); +}; + + +/* -------------------------------------------------------------------------- */ + + +#ifdef WITH_VST + +class gTabPlugins : public Fl_Group +{ +private: + + static void cb_scan (Fl_Widget *w, void *p); + static void cb_onScan(float progress, void *p); + inline void __cb_scan(Fl_Widget *w); + + void updateCount(); + +public: + + class gInput *folderPath; + class gClick *scanButton; + class gBox *info; + + gTabPlugins(int x, int y, int w, int h); + + void save(); +}; + +#endif + +#endif diff --git a/src/gui/dialogs/gd_devInfo.cpp b/src/gui/dialogs/gd_devInfo.cpp new file mode 100644 index 0000000..3f901a3 --- /dev/null +++ b/src/gui/dialogs/gd_devInfo.cpp @@ -0,0 +1,87 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * gd_devInfo + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + + +#include "../../core/kernelAudio.h" +#include "../../utils/gui.h" +#include "../../utils/string.h" +#include "../elems/ge_mixed.h" +#include "gd_devInfo.h" + + +extern KernelAudio G_KernelAudio; + + +using std::string; + + +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(); + + string body = ""; + int lines = 7; + + body = "Device name: " + G_KernelAudio.getDeviceName(dev) + "\n"; + body += "Total output(s): " + gu_itoa(G_KernelAudio.getMaxOutChans(dev)) + "\n"; + body += "Total intput(s): " + gu_itoa(G_KernelAudio.getMaxInChans(dev)) + "\n"; + body += "Duplex channel(s): " + gu_itoa(G_KernelAudio.getDuplexChans(dev)) + "\n"; + body += "Default output: " + string(G_KernelAudio.isDefaultOut(dev) ? "yes" : "no") + "\n"; + body += "Default input: " + string(G_KernelAudio.isDefaultIn(dev) ? "yes" : "no") + "\n"; + + int totalFreq = G_KernelAudio.getTotalFreqs(dev); + body += "Supported frequencies: " + gu_itoa(totalFreq); + + for (int i=0; icopy_label(body.c_str()); + + /* resize the window to fit the content. fl_height() returns the height + * of a line. fl_height() * total lines + margins + button size */ + + resize(x(), y(), w(), (lines * fl_height()) + 8 + 8 + 8 + 20); + close->position(close->x(), (lines * fl_height()) + 8 + 8); + + close->callback(__cb_window_closer, (void*)this); + gu_setFavicon(this); + show(); +} + + +gdDevInfo::~gdDevInfo() {} diff --git a/src/gui/dialogs/gd_devInfo.h b/src/gui/dialogs/gd_devInfo.h new file mode 100644 index 0000000..79a4ad2 --- /dev/null +++ b/src/gui/dialogs/gd_devInfo.h @@ -0,0 +1,47 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * gd_devInfo + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License 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/gui/dialogs/gd_editor.cpp b/src/gui/dialogs/gd_editor.cpp new file mode 100644 index 0000000..881b4ab --- /dev/null +++ b/src/gui/dialogs/gd_editor.cpp @@ -0,0 +1,486 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * gd_editor + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include "../../glue/channel.h" +#include "../../core/waveFx.h" +#include "../../core/conf.h" +#include "../../core/const.h" +#include "../../core/graphics.h" +#include "../../core/sampleChannel.h" +#include "../../core/mixer.h" +#include "../../core/wave.h" +#include "../elems/ge_waveform.h" +#include "../elems/ge_mixed.h" +#include "../elems/ge_waveTools.h" +#include "../elems/mainWindow/keyboard/channel.h" +#include "gd_warnings.h" +#include "gd_editor.h" + + +extern Mixer G_Mixer; +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()+50, 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()+108, tools->y(), 20, 20, "Boost"); + boostNum = new gInput(boost->x()+boost->w()+4, tools->y(), 44, 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()+50, 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, 60, 20, "To bar"); + pitchToSong = new gClick(pitchToBar->x()+pitchToBar->w()+4, volume->y()+volume->h()+4, 60, 20, "To song"); + pitchHalf = new gClick(pitchToSong->x()+pitchToSong->w()+4, volume->y()+volume->h()+4, 20, 20, "", divideOff_xpm, divideOn_xpm); + pitchDouble = new gClick(pitchHalf->x()+pitchHalf->w()+4, volume->y()+volume->h()+4, 20, 20, "", multiplyOff_xpm, multiplyOn_xpm); + 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()+60, pitch->y()+pitch->h()+4, 60, 20, "Range"); + chanEnd = new gInput(chanStart->x()+chanStart->w()+4, pitch->y()+pitch->h()+4, 60, 20, ""); + resetStartEnd = new gClick(chanEnd->x()+chanEnd->w()+4, pitch->y()+pitch->h()+4, 60, 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", (int) std::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", (int) std::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(), G_Conf.samplerate, G_Conf.rsmpQuality); + + glue_setBoost(this, ch, DEFAULT_BOOST, true); + glue_setPitch(this, ch, G_DEFAULT_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/gui/dialogs/gd_editor.h b/src/gui/dialogs/gd_editor.h new file mode 100644 index 0000000..c9ffe74 --- /dev/null +++ b/src/gui/dialogs/gd_editor.h @@ -0,0 +1,116 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * gd_editor + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License 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 "../elems/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/gui/dialogs/gd_keyGrabber.cpp b/src/gui/dialogs/gd_keyGrabber.cpp new file mode 100644 index 0000000..d76e41f --- /dev/null +++ b/src/gui/dialogs/gd_keyGrabber.cpp @@ -0,0 +1,145 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * gd_keyGrabber + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include "../../utils/gui.h" +#include "../../core/conf.h" +#include "../../core/channel.h" +#include "../../core/sampleChannel.h" +#include "../../core/midiChannel.h" +#include "../../utils/log.h" +#include "../elems/ge_mixed.h" +#include "../elems/mainWindow/keyboard/keyboard.h" +#include "../elems/mainWindow/keyboard/channel.h" +#include "../elems/mainWindow/keyboard/channelButton.h" +#include "gd_keyGrabber.h" +#include "gd_config.h" +#include "gd_mainWindow.h" + + +extern 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->mainButton->setKey(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 != ' ') + { + gu_log("set key '%c' (%d) for channel %d\n", x, x, ch->index); + setButtonLabel(x); + updateText(x); + break; + } + else + gu_log("invalid key\n"); + } + } + return(ret); +} diff --git a/src/gui/dialogs/gd_keyGrabber.h b/src/gui/dialogs/gd_keyGrabber.h new file mode 100644 index 0000000..ddee3a6 --- /dev/null +++ b/src/gui/dialogs/gd_keyGrabber.h @@ -0,0 +1,63 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * gd_keyGrabber + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License 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 "../elems/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/gui/dialogs/gd_mainWindow.cpp b/src/gui/dialogs/gd_mainWindow.cpp new file mode 100644 index 0000000..0220f7a --- /dev/null +++ b/src/gui/dialogs/gd_mainWindow.cpp @@ -0,0 +1,124 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * gd_mainWindow + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include +#include "../../core/const.h" +#include "../../core/init.h" +#include "../../utils/gui.h" +#include "../elems/ge_mixed.h" +#include "../elems/basics/boxtypes.h" +#include "../elems/mainWindow/mainIO.h" +#include "../elems/mainWindow/mainMenu.h" +#include "../elems/mainWindow/mainTimer.h" +#include "../elems/mainWindow/mainTransport.h" +#include "../elems/mainWindow/beatMeter.h" +#include "../elems/mainWindow/keyboard/keyboard.h" +#include "gd_warnings.h" +#include "gd_mainWindow.h" + + +extern gdMainWindow *G_MainWin; + + +gdMainWindow::gdMainWindow(int W, int H, const char *title, int argc, char **argv) + : gWindow(W, H, title) +{ + Fl::visible_focus(0); + + Fl::background(25, 25, 25); + + Fl::set_boxtype(G_CUSTOM_BORDER_BOX, g_customBorderBox, 1, 1, 2, 2); + Fl::set_boxtype(G_CUSTOM_UP_BOX, g_customUpBox, 1, 1, 2, 2); + Fl::set_boxtype(G_CUSTOM_DOWN_BOX, g_customDownBox, 1, 1, 2, 2); + + Fl::set_boxtype(FL_BORDER_BOX, G_CUSTOM_BORDER_BOX); + Fl::set_boxtype(FL_UP_BOX, G_CUSTOM_UP_BOX); + Fl::set_boxtype(FL_DOWN_BOX, G_CUSTOM_DOWN_BOX); + + size_range(GUI_WIDTH, GUI_HEIGHT); + + mainMenu = new geMainMenu(8, -1); + mainIO = new geMainIO(412, 8); + mainTransport = new geMainTransport(8, 39); + mainTimer = new geMainTimer(628, 44); + beatMeter = new geBeatMeter(100, 83, 609, 20); + keyboard = new geKeyboard(8, 122, w()-16, 380); + + /* zone 1 - menus, and I/O tools */ + + Fl_Group *zone1 = new Fl_Group(8, 8, W-16, 20); + zone1->add(mainMenu); + zone1->resizable(new Fl_Box(300, 8, 80, 20)); + zone1->add(mainIO); + + /* zone 2 - mainTransport and timing tools */ + + Fl_Group *zone2 = new Fl_Group(8, mainTransport->y(), W-16, mainTransport->h()); + zone2->add(mainTransport); + zone2->resizable(new Fl_Box(mainTransport->x()+mainTransport->w()+4, zone2->y(), 80, 20)); + zone2->add(mainTimer); + + /* zone 3 - beat meter */ + + Fl_Group *zone3 = new Fl_Group(8, beatMeter->y(), W-16, beatMeter->h()); + zone3->add(beatMeter); + + /* zone 4 - the keyboard (Fl_Group is unnecessary here, keyboard is + * a group by itself) */ + + resizable(keyboard); + + add(zone1); + add(zone2); + add(zone3); + add(keyboard); + callback(cb_endprogram); + gu_setFavicon(this); + + show(argc, argv); +} + + +/* -------------------------------------------------------------------------- */ + + +void gdMainWindow::cb_endprogram(Fl_Widget *v, void *p) { G_MainWin->__cb_endprogram(); } + + +/* -------------------------------------------------------------------------- */ + + +void gdMainWindow::__cb_endprogram() +{ + if (!gdConfirmWin("Warning", "Quit Giada: are you sure?")) + return; + init_shutdown(); + hide(); + delete this; +} diff --git a/src/gui/dialogs/gd_mainWindow.h b/src/gui/dialogs/gd_mainWindow.h new file mode 100644 index 0000000..6b07917 --- /dev/null +++ b/src/gui/dialogs/gd_mainWindow.h @@ -0,0 +1,56 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * gd_mainWindow + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License 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 "../elems/ge_window.h" + + +class gdMainWindow : public gWindow +{ +private: + + static void cb_endprogram (class Fl_Widget *v, void *p); + inline void __cb_endprogram(); + +public: + + class geKeyboard *keyboard; + class geBeatMeter *beatMeter; + class geMainMenu *mainMenu; + class geMainIO *mainIO; + class geMainTimer *mainTimer; + class geMainTransport *mainTransport; + + gdMainWindow(int w, int h, const char *title, int argc, char **argv); +}; + + +#endif diff --git a/src/gui/dialogs/gd_pluginChooser.cpp b/src/gui/dialogs/gd_pluginChooser.cpp new file mode 100644 index 0000000..0e20dc1 --- /dev/null +++ b/src/gui/dialogs/gd_pluginChooser.cpp @@ -0,0 +1,138 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * gd_pluginChooser + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#ifdef WITH_VST + + +#include "../../glue/plugin.h" +#include "../../utils/gui.h" +#include "../../core/channel.h" +#include "../../core/conf.h" +#include "../../core/pluginHost.h" +#include "../elems/ge_pluginBrowser.h" +#include "../elems/ge_mixed.h" +#include "gd_pluginChooser.h" + + +extern PluginHost G_PluginHost; +extern Conf G_Conf; + + +gdPluginChooser::gdPluginChooser(int X, int Y, int W, int H, int stackType, class Channel *ch) + : gWindow(X, Y, W, H, "Available plugins"), ch(ch), stackType(stackType) +{ + /* top area */ + Fl_Group *group_top = new Fl_Group(8, 8, w()-16, 20); + sortMethod = new gChoice(group_top->x() + 45, group_top->y(), 100, 20, "Sort by"); + gBox *b1 = new gBox(sortMethod->x()+sortMethod->w(), group_top->y(), 100, 20); // spacer window border <-> menu + group_top->resizable(b1); + group_top->end(); + + /* center browser */ + browser = new gePluginBrowser(8, 36, w()-16, h()-70); + + /* ok/cancel buttons */ + Fl_Group *group_btn = new Fl_Group(8, browser->y()+browser->h()+8, w()-16, h()-browser->h()-16); + gBox *b2 = new gBox(8, browser->y()+browser->h(), 100, 20); // spacer window border <-> buttons + addBtn = new gClick(w()-88, group_btn->y(), 80, 20, "Add"); + cancelBtn = new gClick(addBtn->x()-88, group_btn->y(), 80, 20, "Cancel"); + group_btn->resizable(b2); + group_btn->end(); + + end(); + + sortMethod->add("Name"); + sortMethod->add("Category"); + sortMethod->add("Manufacturer"); + sortMethod->callback(cb_sort, (void*) this); + sortMethod->value(G_Conf.pluginSortMethod); + + addBtn->callback(cb_add, (void*) this); + addBtn->shortcut(FL_Enter); + cancelBtn->callback(cb_close, (void*) this); + + resizable(browser); + gu_setFavicon(this); + show(); +} + + +/* -------------------------------------------------------------------------- */ + + +gdPluginChooser::~gdPluginChooser() +{ + G_Conf.pluginChooserX = x(); + G_Conf.pluginChooserY = y(); + G_Conf.pluginChooserW = w(); + G_Conf.pluginChooserH = h(); + G_Conf.pluginSortMethod = sortMethod->value(); +} + + +/* -------------------------------------------------------------------------- */ + + +void gdPluginChooser::cb_close(Fl_Widget *v, void *p) { ((gdPluginChooser*)p)->__cb_close(); } +void gdPluginChooser::cb_add(Fl_Widget *v, void *p) { ((gdPluginChooser*)p)->__cb_add(); } +void gdPluginChooser::cb_sort(Fl_Widget *v, void *p) { ((gdPluginChooser*)p)->__cb_sort(); } + + +/* -------------------------------------------------------------------------- */ + + +void gdPluginChooser::__cb_close() +{ + do_callback(); +} + + +/* -------------------------------------------------------------------------- */ + + +void gdPluginChooser::__cb_sort() +{ + G_PluginHost.sortPlugins(sortMethod->value()); + browser->refresh(); +} + + +/* -------------------------------------------------------------------------- */ + + +void gdPluginChooser::__cb_add() +{ + int index = browser->value() - 3; // subtract header lines + if (index < 0) + return; + glue_addPlugin(ch, index, stackType); + do_callback(); +} + +#endif // #ifdef WITH_VST diff --git a/src/gui/dialogs/gd_pluginChooser.h b/src/gui/dialogs/gd_pluginChooser.h new file mode 100644 index 0000000..5d44985 --- /dev/null +++ b/src/gui/dialogs/gd_pluginChooser.h @@ -0,0 +1,68 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * gd_pluginChooser + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#ifdef WITH_VST + +#ifndef __GD_PLUGIN_CHOOSER_H__ +#define __GD_PLUGIN_CHOOSER_H__ + +#include +#include +#include "../elems/ge_window.h" + + +class gdPluginChooser : public gWindow { + +private: + + class Channel *ch; // ch == NULL ? masterOut + int stackType; + + class gChoice *sortMethod; + class gClick *addBtn; + class gClick *cancelBtn; + class gePluginBrowser *browser; + + static void cb_close(Fl_Widget *w, void *p); + static void cb_add (Fl_Widget *w, void *p); + static void cb_sort (Fl_Widget *w, void *p); + inline void __cb_close(); + inline void __cb_add (); + inline void __cb_sort (); + +public: + + gdPluginChooser(int x, int y, int w, int h, int stackType, class Channel *ch=NULL); + ~gdPluginChooser(); +}; + + +#endif + +#endif // #ifdef WITH_VST diff --git a/src/gui/dialogs/gd_pluginList.cpp b/src/gui/dialogs/gd_pluginList.cpp new file mode 100644 index 0000000..65d8e6d --- /dev/null +++ b/src/gui/dialogs/gd_pluginList.cpp @@ -0,0 +1,377 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * gd_pluginList + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#ifdef WITH_VST + + +#include "../../utils/gui.h" +#include "../../utils/fs.h" +#include "../../core/conf.h" +#include "../../core/const.h" +#include "../../core/graphics.h" +#include "../../core/pluginHost.h" +#include "../../core/plugin.h" +#include "../../core/mixer.h" +#include "../../core/channel.h" +#include "../../glue/plugin.h" +#include "../../utils/log.h" +#include "../../utils/string.h" +#include "../elems/ge_mixed.h" +#include "../elems/basics/boxtypes.h" +#include "../elems/mainWindow/mainIO.h" +#include "../elems/mainWindow/keyboard/channel.h" +#include "gd_pluginList.h" +#include "gd_pluginChooser.h" +#include "gd_pluginWindow.h" +#include "gd_pluginWindowGUI.h" +#include "gd_browser.h" +#include "gd_mainWindow.h" + + +extern Conf G_Conf; +extern Mixer G_Mixer; +extern PluginHost G_PluginHost; +extern gdMainWindow *G_MainWin; + + +using std::string; + + +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_CUSTOM_BORDER_BOX); + + list->begin(); + refreshList(); + list->end(); + + end(); + set_non_modal(); + + /* TODO - awful stuff... we should subclass into gdPluginListChannel and + gdPluginListMaster */ + + if (stackType == PluginHost::MASTER_OUT) + label("Master Out Plugins"); + else + if (stackType == PluginHost::MASTER_IN) + label("Master In Plugins"); + else { + string l = "Channel " + gu_itoa(ch->index+1) + " Plugins"; + copy_label(l.c_str()); + } + + 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 gdPluginChooser. It does exactly what we need. */ + + gdPluginChooser *pc = new gdPluginChooser(G_Conf.pluginChooserX, + G_Conf.pluginChooserY, G_Conf.pluginChooserW, G_Conf.pluginChooserH, + stackType, ch); + addSubWindow(pc); + pc->callback(cb_refreshList, (void*)this); // 'this' refers to gdPluginList +} + + +/* -------------------------------------------------------------------------- */ + + +void gdPluginList::refreshList() +{ + /* delete the previous list */ + + list->clear(); + list->scroll_to(0, 0); + + /* add new buttons, as many as the plugin in pluginHost::stack + 1, + * the 'add new' button. Warning: if ch == 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) { + G_MainWin->mainIO->setMasterFxOutFull( + G_PluginHost.countPlugins(stackType, ch) > 0); + } + else + if (stackType == PluginHost::MASTER_IN) { + G_MainWin->mainIO->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(); + + button->copy_label(pPlugin->getName().c_str()); + button->callback(cb_openPluginWindow, (void*)this); + + program->callback(cb_setProgram, (void*)this); + + for (int i=0; igetNumPrograms(); i++) + program->add(gu_removeFltkChars(pPlugin->getProgramName(i)).c_str()); + + if (program->size() == 0) { + program->add("-- no programs --\0"); + program->deactivate(); + } + else + program->value(pPlugin->getCurrentProgram()); + + bypass->callback(cb_setBypass, (void*)this); + bypass->type(FL_TOGGLE_BUTTON); + bypass->value(pPlugin->isBypassed() ? 0 : 1); + + shiftUp->callback(cb_shiftUp, (void*)this); + shiftDown->callback(cb_shiftDown, (void*)this); + remove->callback(cb_removePlugin, (void*)this); +} + + +/* -------------------------------------------------------------------------- */ + + +void gdPlugin::cb_removePlugin (Fl_Widget *v, void *p) { ((gdPlugin*)p)->__cb_removePlugin(); } +void gdPlugin::cb_openPluginWindow(Fl_Widget *v, void *p) { ((gdPlugin*)p)->__cb_openPluginWindow(); } +void gdPlugin::cb_setBypass (Fl_Widget *v, void *p) { ((gdPlugin*)p)->__cb_setBypass(); } +void gdPlugin::cb_shiftUp (Fl_Widget *v, void *p) { ((gdPlugin*)p)->__cb_shiftUp(); } +void gdPlugin::cb_shiftDown (Fl_Widget *v, void *p) { ((gdPlugin*)p)->__cb_shiftDown(); } +void gdPlugin::cb_setProgram (Fl_Widget *v, void *p) { ((gdPlugin*)p)->__cb_setProgram(); } + + +/* -------------------------------------------------------------------------- */ + + +void gdPlugin::__cb_shiftUp() +{ + /*nothing to do if there's only one plugin */ + + if (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; + + glue_swapPlugins(pParent->ch, pluginIndex, pluginIndex-1, pParent->stackType); + 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; + + glue_swapPlugins(pParent->ch, pluginIndex, pluginIndex+1, pParent->stackType); + pParent->refreshList(); +} + + +/* -------------------------------------------------------------------------- */ + + +void gdPlugin::__cb_removePlugin() +{ + /* any subwindow linked to the plugin must be destroyed first */ + + pParent->delSubWindow(pPlugin->getId()); + glue_freePlugin(pParent->ch, pPlugin->getId(), pParent->stackType); + pParent->refreshList(); +} + + +/* -------------------------------------------------------------------------- */ + + +void gdPlugin::__cb_openPluginWindow() +{ + /* the new pluginWindow has id = id_plugin + 1, because id=0 is reserved + * for the parent window 'add plugin'. */ + + gWindow *w; + if (pPlugin->hasEditor()) { + if (pPlugin->isEditorOpen()) { + gu_log("[gdPlugin::__cb_openPluginWindow] plugin has editor but it's already visible\n"); + return; + } + + int pwid = pPlugin->getId()+1; + + gu_log("[gdPlugin::__cb_openPluginWindow] plugin has editor, open window id=%d\n", pwid); + + if (pParent->hasWindow(pwid)) + pParent->delSubWindow(pwid); + w = new gdPluginWindowGUI(pPlugin); + w->setId(pwid); + pParent->addSubWindow(w); + } + else { + w = new gdPluginWindow(pPlugin); + } +} + + +/* -------------------------------------------------------------------------- */ + + +void gdPlugin::__cb_setBypass() +{ + pPlugin->toggleBypass(); +} + + +/* -------------------------------------------------------------------------- */ + + +void gdPlugin::__cb_setProgram() +{ + pPlugin->setCurrentProgram(program->value()); +} + + +#endif // #ifdef WITH_VST diff --git a/src/gui/dialogs/gd_pluginList.h b/src/gui/dialogs/gd_pluginList.h new file mode 100644 index 0000000..cbe947f --- /dev/null +++ b/src/gui/dialogs/gd_pluginList.h @@ -0,0 +1,104 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * gd_pluginList + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License 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 "../elems/ge_window.h" + + +class gdPluginList : public gWindow +{ +private: + + class gClick *addPlugin; + Fl_Scroll *list; + + 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/gui/dialogs/gd_pluginWindow.cpp b/src/gui/dialogs/gd_pluginWindow.cpp new file mode 100644 index 0000000..d3b7136 --- /dev/null +++ b/src/gui/dialogs/gd_pluginWindow.cpp @@ -0,0 +1,127 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * gd_pluginWindow + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#ifdef WITH_VST + + +#include +#include "../../utils/gui.h" +#include "../../core/plugin.h" +#include "../elems/ge_mixed.h" +#include "../elems/basics/boxtypes.h" +#include "gd_pluginWindow.h" + + +using std::string; + + +Parameter::Parameter(int paramIndex, Plugin *p, int X, int Y, int W) + : Fl_Group(X, Y, W-24, 20), paramIndex(paramIndex), pPlugin(p) +{ + begin(); + + label = new gBox(x(), y(), 60, 20); + label->copy_label(pPlugin->getParameterName(paramIndex).c_str()); + label->align(FL_ALIGN_LEFT | FL_ALIGN_INSIDE); + + slider = new gSlider(label->x()+label->w()+8, y(), W-200, 20); + slider->value(pPlugin->getParameter(paramIndex)); + slider->callback(cb_setValue, (void *)this); + + value = new gBox(slider->x()+slider->w()+8, y(), 100, 20); + value->align(FL_ALIGN_LEFT | FL_ALIGN_INSIDE); + value->box(G_CUSTOM_BORDER_BOX); + updateValue(); + + resizable(slider); + + end(); +} + + +/* -------------------------------------------------------------------------- */ + + +void Parameter::cb_setValue(Fl_Widget *v, void *p) { ((Parameter*)p)->__cb_setValue(); } + + +/* -------------------------------------------------------------------------- */ + + +void Parameter::__cb_setValue() +{ + pPlugin->setParameter(paramIndex, slider->value()); + updateValue(); + value->redraw(); +} + + +/* -------------------------------------------------------------------------- */ + + +void Parameter::updateValue() +{ + string v = pPlugin->getParameterText(paramIndex) + " " + + pPlugin->getParameterLabel(paramIndex); + value->copy_label(v.c_str()); +} + + +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ + + +gdPluginWindow::gdPluginWindow(Plugin *p) + : gWindow(400, 156), pPlugin(p) // 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->getNumParameters(); + for (int i=0; ix(), list->y()+(i*24), list->w()); + list->end(); + + end(); + + label(pPlugin->getName().c_str()); + + size_range(400, (24*1)+12); + resizable(list); + + gu_setFavicon(this); + show(); +} + + +#endif // #ifdef WITH_VST diff --git a/src/gui/dialogs/gd_pluginWindow.h b/src/gui/dialogs/gd_pluginWindow.h new file mode 100644 index 0000000..ace781a --- /dev/null +++ b/src/gui/dialogs/gd_pluginWindow.h @@ -0,0 +1,77 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * gd_pluginWindow + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + +#ifdef WITH_VST + +#ifndef __GD_PLUGIN_WINDOW_H__ +#define __GD_PLUGIN_WINDOW_H__ + + +#include +#include +#include "../elems/ge_window.h" + + +class gdPluginWindow : public gWindow +{ +private: + class Plugin *pPlugin; + +public: + int id; + + gdPluginWindow(Plugin *pPlugin); +}; + + +/* -------------------------------------------------------------------------- */ + + +class Parameter : public Fl_Group +{ +private: + int paramIndex; + class Plugin *pPlugin; + + static void cb_setValue(Fl_Widget *v, void *p); + inline void __cb_setValue(); + + void updateValue(); + +public: + class gBox *label; + class gSlider *slider; + class gBox *value; + + Parameter(int paramIndex, class Plugin *p, int x, int y, int w); +}; + + +#endif + +#endif // #ifdef WITH_VST diff --git a/src/gui/dialogs/gd_pluginWindowGUI.cpp b/src/gui/dialogs/gd_pluginWindowGUI.cpp new file mode 100644 index 0000000..7a5c772 --- /dev/null +++ b/src/gui/dialogs/gd_pluginWindowGUI.cpp @@ -0,0 +1,124 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * gd_pluginWindowGUI + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#ifdef WITH_VST + + +#include "../../utils/log.h" +#include "../../utils/gui.h" +#include "../../core/pluginHost.h" +#include "../../core/plugin.h" +#include "../../core/const.h" +#include "../elems/ge_mixed.h" +#include "gd_pluginWindowGUI.h" + +#ifdef __APPLE__ +#import "../../utils/cocoa.h" // objective-c +#endif + + +extern PluginHost G_PluginHost; + + +gdPluginWindowGUI::gdPluginWindowGUI(Plugin *pPlugin) + : gWindow(450, 300), pPlugin(pPlugin) +{ + show(); + +#ifndef __APPLE__ + + Fl::check(); + +#endif + + gu_log("[gdPluginWindowGUI] opening GUI, this=%p, xid=%p\n", + (void*) this, (void*) fl_xid(this)); + +#if defined(__APPLE__) + + void *cocoaWindow = (void*) fl_xid(this); + cocoa_setWindowSize(cocoaWindow, pPlugin->getEditorW(), pPlugin->getEditorH()); + pPlugin->showEditor(cocoa_getViewFromWindow(cocoaWindow)); + +#else + + pPlugin->showEditor((void*) fl_xid(this)); + +#endif + + int pluginW = pPlugin->getEditorW(); + int pluginH = pPlugin->getEditorH(); + + resize((Fl::w() - pluginW) / 2, (Fl::h() - pluginH) / 2, pluginW, pluginH); + + Fl::add_timeout(GUI_PLUGIN_RATE, cb_refresh, (void*) this); + + copy_label(pPlugin->getName().c_str()); + +} + + +/* -------------------------------------------------------------------------- */ + + +void gdPluginWindowGUI::cb_close(Fl_Widget *v, void *p) { ((gdPluginWindowGUI*)p)->__cb_close(); } +void gdPluginWindowGUI::cb_refresh(void *data) { ((gdPluginWindowGUI*)data)->__cb_refresh(); } + + +/* -------------------------------------------------------------------------- */ + + +void gdPluginWindowGUI::__cb_close() +{ + Fl::remove_timeout(cb_refresh); + pPlugin->closeEditor(); + gu_log("[gdPluginWindowGUI::__cb_close] GUI closed, this=%p\n", (void*) this); +} + + +/* -------------------------------------------------------------------------- */ + + +void gdPluginWindowGUI::__cb_refresh() +{ + //gu_log("[gdPluginWindowGUI::__cb_refresh] refresh!\n"); + G_PluginHost.runDispatchLoop(); + Fl::repeat_timeout(GUI_PLUGIN_RATE, cb_refresh, (void*) this); +} + + +/* -------------------------------------------------------------------------- */ + + +gdPluginWindowGUI::~gdPluginWindowGUI() +{ + __cb_close(); +} + +#endif // #ifdef WITH_VST diff --git a/src/gui/dialogs/gd_pluginWindowGUI.h b/src/gui/dialogs/gd_pluginWindowGUI.h new file mode 100644 index 0000000..b510d56 --- /dev/null +++ b/src/gui/dialogs/gd_pluginWindowGUI.h @@ -0,0 +1,91 @@ + +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * gd_pluginWindowGUI + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + + +#ifdef WITH_VST + + +#ifndef __GD_PLUGIN_WINDOW_GUI_H__ +#define __GD_PLUGIN_WINDOW_GUI_H__ + + +#include +#include +#include "../elems/ge_window.h" +#if defined(__APPLE__) + #include +#endif + + +class gdPluginWindowGUI : public gWindow +{ +private: + + class Plugin *pPlugin; + + static void cb_close (Fl_Widget *v, void *p); + static void cb_refresh (void *data); + inline void __cb_close (); + inline void __cb_refresh(); + +public: + + gdPluginWindowGUI(Plugin *pPlugin); + ~gdPluginWindowGUI(); +}; + + +/* -------------------------------------------------------------------------- */ + +#if 0 +#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 + +#endif // include guard + +#endif // #ifdef WITH_VST diff --git a/src/gui/dialogs/gd_warnings.cpp b/src/gui/dialogs/gd_warnings.cpp new file mode 100644 index 0000000..c53d501 --- /dev/null +++ b/src/gui/dialogs/gd_warnings.cpp @@ -0,0 +1,79 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * gd_warnings + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + + +#include "../../core/const.h" +#include "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(GUI_FONT_SIZE_BASE); + b->callback(__cb_window_closer, (void *)modal); + b->shortcut(FL_Enter); + gu_setFavicon(modal); + modal->show(); +} + + +int gdConfirmWin(const char *title, const char *msg) { + Fl_Window *win = new Fl_Window( + (Fl::w() / 2) - 150, + (Fl::h() / 2) - 47, + 300, 90, title); + win->set_modal(); + win->begin(); + new 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/gui/dialogs/gd_warnings.h b/src/gui/dialogs/gd_warnings.h new file mode 100644 index 0000000..13d3b4e --- /dev/null +++ b/src/gui/dialogs/gd_warnings.h @@ -0,0 +1,43 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * gd_warnings + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License 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 "../elems/ge_mixed.h" +#include "../../utils/gui.h" + + +void gdAlert(const char *c); + +int gdConfirmWin(const char *title, const char *msg); + +#endif diff --git a/src/gui/dialogs/midiIO/midiInputBase.cpp b/src/gui/dialogs/midiIO/midiInputBase.cpp new file mode 100644 index 0000000..256c9d5 --- /dev/null +++ b/src/gui/dialogs/midiIO/midiInputBase.cpp @@ -0,0 +1,103 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * midiInputBase + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include "../../../core/kernelMidi.h" +#include "../../../utils/log.h" +#include "../../elems/midiLearner.h" +#include "midiInputBase.h" + + +extern KernelMidi G_KernelMidi; + + +using std::string; + + +gdMidiInputBase::gdMidiInputBase(int x, int y, int w, int h, const char *title) + : gWindow(x, y, w, h, title) +{ +} + + +/* -------------------------------------------------------------------------- */ + + +gdMidiInputBase::~gdMidiInputBase() +{ + G_KernelMidi.stopMidiLearn(); +} + + +/* -------------------------------------------------------------------------- */ + + +void gdMidiInputBase::stopMidiLearn(geMidiLearner *learner) +{ + G_KernelMidi.stopMidiLearn(); + learner->updateValue(); +} + + +/* -------------------------------------------------------------------------- */ + + +void gdMidiInputBase::__cb_learn(uint32_t *param, uint32_t msg, geMidiLearner *l) +{ + *param = msg; + stopMidiLearn(l); + gu_log("[gdMidiGrabber] MIDI learn done - message=0x%X\n", msg); +} + + +/* -------------------------------------------------------------------------- */ + + +void gdMidiInputBase::cb_learn(uint32_t msg, void *d) +{ + geMidiLearner::cbData_t *data = (geMidiLearner::cbData_t *) d; + gdMidiInputBase *window = (gdMidiInputBase*) data->window; + geMidiLearner *learner = data->learner; + uint32_t *param = learner->param; + window->__cb_learn(param, msg, learner); +} + + +/* -------------------------------------------------------------------------- */ + + +void gdMidiInputBase::cb_close(Fl_Widget *w, void *p) { ((gdMidiInputBase*)p)->__cb_close(); } + + +/* -------------------------------------------------------------------------- */ + + +void gdMidiInputBase::__cb_close() +{ + do_callback(); +} diff --git a/src/gui/dialogs/midiIO/midiInputBase.h b/src/gui/dialogs/midiIO/midiInputBase.h new file mode 100644 index 0000000..48f213b --- /dev/null +++ b/src/gui/dialogs/midiIO/midiInputBase.h @@ -0,0 +1,61 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * midiInputBase + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#ifndef GD_MIDI_INPUT_BASE_H +#define GD_MIDI_INPUT_BASE_H + + +#include "../../elems/ge_window.h" + + +class gdMidiInputBase : public gWindow +{ +protected: + + class gClick *ok; + + void stopMidiLearn(class geMidiLearner *l); + + /* cb_learn + * callback attached to kernelMidi to learn various actions. */ + + static void cb_learn (uint32_t msg, void *data); + inline void __cb_learn(uint32_t *param, uint32_t msg, geMidiLearner *l); + + static void cb_close (Fl_Widget *w, void *p); + inline void __cb_close(); + +public: + + gdMidiInputBase(int x, int y, int w, int h, const char *title); + ~gdMidiInputBase(); +}; + + +#endif diff --git a/src/gui/dialogs/midiIO/midiInputChannel.cpp b/src/gui/dialogs/midiIO/midiInputChannel.cpp new file mode 100644 index 0000000..6bb75e3 --- /dev/null +++ b/src/gui/dialogs/midiIO/midiInputChannel.cpp @@ -0,0 +1,172 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * midiInputChannel + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include +#include "../../../utils/gui.h" +#include "../../../core/const.h" +#include "../../../core/conf.h" +#include "../../../core/sampleChannel.h" +#ifdef WITH_VST + #include "../../../core/pluginHost.h" + #include "../../../core/plugin.h" +#endif +#include "../../../utils/string.h" +#include "../../elems/ge_mixed.h" +#include "../../elems/midiLearner.h" +#include "../../elems/basics/scroll.h" +#include "midiInputChannel.h" + + +#ifdef WITH_VST +extern PluginHost G_PluginHost; +#endif +extern Conf G_Conf; + + +using std::string; + + +gdMidiInputChannel::gdMidiInputChannel(Channel *ch) + : gdMidiInputBase(G_Conf.midiInputX, G_Conf.midiInputY, G_Conf.midiInputW, + G_Conf.midiInputH, "MIDI Input Setup"), + ch(ch) +{ + string title = "MIDI Input Setup (channel " + gu_itoa(ch->index+1) + ")"; + label(title.c_str()); + size_range(G_DEFAULT_MIDI_INPUT_UI_W, G_DEFAULT_MIDI_INPUT_UI_H); + + enable = new gCheck(8, 8, 120, 20, "enable MIDI input"); + + container = new geScroll(8, enable->y()+enable->h()+4, w()-16, h()-70); + container->begin(); + + addChannelLearners(); +#ifdef WITH_VST + addPluginLearners(); +#endif + + container->end(); + + ok = new gButton(w()-88, container->y()+container->h()+8, 80, 20, "Close"); + ok->callback(cb_close, (void*)this); + + enable->value(ch->midiIn); + enable->callback(cb_enable, (void*)this); + + resizable(container); + + gu_setFavicon(this); + set_modal(); + show(); +} + + +/* -------------------------------------------------------------------------- */ + + +gdMidiInputChannel::~gdMidiInputChannel() +{ + G_Conf.midiInputX = x(); + G_Conf.midiInputY = y(); + G_Conf.midiInputW = w(); + G_Conf.midiInputH = h(); +} + + +/* -------------------------------------------------------------------------- */ + + +void gdMidiInputChannel::addChannelLearners() +{ + Fl_Pack *pack = new Fl_Pack(container->x(), container->y(), LEARNER_WIDTH, 200); + pack->spacing(4); + pack->begin(); + + gBox *header = new gBox(0, 0, LEARNER_WIDTH, 20, "channel"); + header->box(FL_BORDER_BOX); + new geMidiLearner(0, 0, LEARNER_WIDTH, "key press", cb_learn, &ch->midiInKeyPress); + new geMidiLearner(0, 0, LEARNER_WIDTH, "key release", cb_learn, &ch->midiInKeyRel); + new geMidiLearner(0, 0, LEARNER_WIDTH, "key kill", cb_learn, &ch->midiInKill); + new geMidiLearner(0, 0, LEARNER_WIDTH, "arm", cb_learn, &ch->midiInArm); + new geMidiLearner(0, 0, LEARNER_WIDTH, "mute", cb_learn, &ch->midiInMute); + new geMidiLearner(0, 0, LEARNER_WIDTH, "solo", cb_learn, &ch->midiInSolo); + new geMidiLearner(0, 0, LEARNER_WIDTH, "volume", cb_learn, &ch->midiInVolume); + if (ch->type == CHANNEL_SAMPLE) { + new geMidiLearner(0, 0, LEARNER_WIDTH, "pitch", cb_learn, &((SampleChannel*)ch)->midiInPitch); + new geMidiLearner(0, 0, LEARNER_WIDTH, "read actions", cb_learn, &((SampleChannel*)ch)->midiInReadActions); + } + + pack->end(); +} + + +/* -------------------------------------------------------------------------- */ + + +#ifdef WITH_VST + +void gdMidiInputChannel::addPluginLearners() +{ + vector *plugins = G_PluginHost.getStack(PluginHost::CHANNEL, ch); + for (unsigned i=0; isize(); i++) { + + Fl_Pack *pack = new Fl_Pack(container->x() + ((i + 1) * (LEARNER_WIDTH + 8)), + container->y(), LEARNER_WIDTH, 200); + pack->spacing(4); + pack->begin(); + + Plugin *plugin = plugins->at(i); + + gBox *header = new gBox(0, 0, LEARNER_WIDTH, 20, plugin->getName().c_str()); + header->box(FL_BORDER_BOX); + + for (int k=0; kgetNumParameters(); k++) + new geMidiLearner(0, 0, LEARNER_WIDTH, plugin->getParameterName(k).c_str(), + cb_learn, &plugin->midiInParams.at(k)); + + pack->end(); + } +} + +#endif + + +/* -------------------------------------------------------------------------- */ + + +void gdMidiInputChannel::cb_enable(Fl_Widget *w, void *p) { ((gdMidiInputChannel*)p)->__cb_enable(); } + + +/* -------------------------------------------------------------------------- */ + + +void gdMidiInputChannel::__cb_enable() +{ + ch->midiIn = enable->value(); +} diff --git a/src/gui/dialogs/midiIO/midiInputChannel.h b/src/gui/dialogs/midiIO/midiInputChannel.h new file mode 100644 index 0000000..462a158 --- /dev/null +++ b/src/gui/dialogs/midiIO/midiInputChannel.h @@ -0,0 +1,68 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * midiInputChannel + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#ifndef GD_MIDI_INPUT_CHANNEL_H +#define GD_MIDI_INPUT_CHANNEL_H + + +#include "midiInputBase.h" + + +class gdMidiInputChannel : public gdMidiInputBase +{ +private: + + static const int LEARNER_WIDTH = 284; + + class Channel *ch; + + class geScroll *container; + class gCheck *enable; + + //gVector items; // plugins parameters + + static void cb_enable (Fl_Widget *w, void *p); + inline void __cb_enable(); + + void addChannelLearners(); + +#ifdef WITH_VST + + void addPluginLearners(); + +#endif + +public: + + gdMidiInputChannel(class Channel *ch); + ~gdMidiInputChannel(); +}; + + +#endif diff --git a/src/gui/dialogs/midiIO/midiInputMaster.cpp b/src/gui/dialogs/midiIO/midiInputMaster.cpp new file mode 100644 index 0000000..e7a8def --- /dev/null +++ b/src/gui/dialogs/midiIO/midiInputMaster.cpp @@ -0,0 +1,60 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * midiInputMaster + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include "../../../utils/gui.h" +#include "../../../core/conf.h" +#include "../../elems/ge_mixed.h" +#include "../../elems/midiLearner.h" +#include "midiInputMaster.h" + + +extern Conf G_Conf; + + +gdMidiInputMaster::gdMidiInputMaster() + : gdMidiInputBase(0, 0, 300, 256, "MIDI Input Setup (global)") +{ + set_modal(); + + new geMidiLearner(8, 8, w()-16, "rewind", &cb_learn, &G_Conf.midiInRewind); + new geMidiLearner(8, 32, w()-16, "play/stop", &cb_learn, &G_Conf.midiInStartStop); + new geMidiLearner(8, 56, w()-16, "action recording", &cb_learn, &G_Conf.midiInActionRec); + new geMidiLearner(8, 80, w()-16, "input recording", &cb_learn, &G_Conf.midiInInputRec); + new geMidiLearner(8, 104, w()-16, "metronome", &cb_learn, &G_Conf.midiInMetronome); + new geMidiLearner(8, 128, w()-16, "input volume", &cb_learn, &G_Conf.midiInVolumeIn); + new geMidiLearner(8, 152, w()-16, "output volume", &cb_learn, &G_Conf.midiInVolumeOut); + new geMidiLearner(8, 176, w()-16, "sequencer ×2", &cb_learn, &G_Conf.midiInBeatDouble); + new geMidiLearner(8, 200, w()-16, "sequencer ÷2", &cb_learn, &G_Conf.midiInBeatHalf); + ok = new gButton(w()-88, 228, 80, 20, "Close"); + + ok->callback(cb_close, (void*)this); + + gu_setFavicon(this); + show(); +} diff --git a/src/gui/dialogs/midiIO/midiInputMaster.h b/src/gui/dialogs/midiIO/midiInputMaster.h new file mode 100644 index 0000000..55a4cd5 --- /dev/null +++ b/src/gui/dialogs/midiIO/midiInputMaster.h @@ -0,0 +1,45 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * midiInputMaster + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#ifndef GD_MIDI_INPUT_MASTER_H +#define GD_MIDI_INPUT_MASTER_H + + +#include "midiInputBase.h" + + +class gdMidiInputMaster : public gdMidiInputBase +{ +public: + + gdMidiInputMaster(); +}; + + +#endif diff --git a/src/gui/dialogs/midiIO/midiOutputBase.cpp b/src/gui/dialogs/midiIO/midiOutputBase.cpp new file mode 100644 index 0000000..d8afd77 --- /dev/null +++ b/src/gui/dialogs/midiIO/midiOutputBase.cpp @@ -0,0 +1,116 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * gd_midiOutput + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include "../../../utils/log.h" +#include "../../elems/midiLearner.h" +#include "midiOutputBase.h" + + +extern KernelMidi G_KernelMidi; + + +gdMidiOutputBase::gdMidiOutputBase(int w, int h) + : gWindow(w, h, "Midi Output Setup") +{ +} + + +/* -------------------------------------------------------------------------- */ + + +void gdMidiOutputBase::stopMidiLearn(geMidiLearner *learner) +{ + G_KernelMidi.stopMidiLearn(); + learner->updateValue(); +} + + +/* -------------------------------------------------------------------------- */ + + +void gdMidiOutputBase::__cb_learn(uint32_t *param, uint32_t msg, geMidiLearner *l) +{ + *param = msg; + stopMidiLearn(l); + gu_log("[gdMidiGrabber] MIDI learn done - message=0x%X\n", msg); +} + + +/* -------------------------------------------------------------------------- */ + + +void gdMidiOutputBase::cb_learn(uint32_t msg, void *d) +{ + geMidiLearner::cbData_t *data = (geMidiLearner::cbData_t*) d; + gdMidiOutputBase *window = (gdMidiOutputBase*) data->window; + geMidiLearner *learner = data->learner; + uint32_t *param = learner->param; + window->__cb_learn(param, msg, learner); +} + + +/* -------------------------------------------------------------------------- */ + + +void gdMidiOutputBase::cb_close(Fl_Widget *w, void *p) { ((gdMidiOutputBase*)p)->__cb_close(); } + + +/* -------------------------------------------------------------------------- */ + + +void gdMidiOutputBase::__cb_close() +{ + do_callback(); +} + + +/* -------------------------------------------------------------------------- */ + + +void gdMidiOutputBase::cb_enableLightning(Fl_Widget *w, void *p) +{ + ((gdMidiOutputBase*)p)->__cb_enableLightning(); +} + + +/* -------------------------------------------------------------------------- */ + + +void gdMidiOutputBase::__cb_enableLightning() {} + + +/* -------------------------------------------------------------------------- */ + + +void gdMidiOutputBase::setTitle(int chanNum) +{ + char title[64]; + sprintf(title, "MIDI Output Setup (channel %d)", chanNum); + copy_label(title); +} diff --git a/src/gui/dialogs/midiIO/midiOutputBase.h b/src/gui/dialogs/midiIO/midiOutputBase.h new file mode 100644 index 0000000..fd639d4 --- /dev/null +++ b/src/gui/dialogs/midiIO/midiOutputBase.h @@ -0,0 +1,86 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * gd_midiOutput + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#ifndef GD_MIDI_OUTPUT_BASE_H +#define GD_MIDI_OUTPUT_BASE_H + + +#include +#include "../../elems/ge_window.h" + + +/* There's no such thing as a gdMidiOutputMaster vs gdMidiOutputChannel. MIDI +output master is managed by the configuration window, hence gdMidiOutput deals +only with channels. + +Both MidiOutputMidiCh and MidiOutputSampleCh have the MIDI lighting widget set. +In addition MidiOutputMidiCh has the MIDI message output box. */ + +/* TODO - gdMidiOutput is almost the same thing of gdMidiInput. Create another +parent class gdMidiIO to inherit from */ + +class gdMidiOutputBase : public gWindow +{ +protected: + + class gClick *close; + class gCheck *enableLightning; + + void stopMidiLearn(class geMidiLearner *l); + + /* cb_learn + * callback attached to kernelMidi to learn various actions. */ + + static void cb_learn (uint32_t msg, void *data); + inline void __cb_learn(uint32_t *param, uint32_t msg, class geMidiLearner *l); + + /* cb_close + close current window. */ + + static void cb_close (Fl_Widget *w, void *p); + inline void __cb_close(); + + /* cb_enableLightning + enable MIDI lightning output. */ + + static void cb_enableLightning (Fl_Widget *w, void *p); + inline void __cb_enableLightning(); + + /* setTitle + * set window title. */ + + void setTitle(int chanNum); + +public: + + gdMidiOutputBase(int w, int h); +}; + + +#endif diff --git a/src/gui/dialogs/midiIO/midiOutputMidiCh.cpp b/src/gui/dialogs/midiIO/midiOutputMidiCh.cpp new file mode 100644 index 0000000..08954dd --- /dev/null +++ b/src/gui/dialogs/midiIO/midiOutputMidiCh.cpp @@ -0,0 +1,119 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * midiOutputMidiCh + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include "../../../core/midiChannel.h" +#include "../../../utils/gui.h" +#include "../../elems/ge_mixed.h" +#include "../../elems/midiLearner.h" +#include "../../elems/mainWindow/keyboard/channel.h" +#include "midiOutputMidiCh.h" + + +gdMidiOutputMidiCh::gdMidiOutputMidiCh(MidiChannel *ch) + : gdMidiOutputBase(300, 168), ch(ch) +{ + setTitle(ch->index+1); + begin(); + + enableOut = new gCheck(x()+8, y()+8, 150, 20, "Enable MIDI output"); + chanListOut = new gChoice(w()-108, y()+8, 100, 20); + + enableLightning = new gCheck(x()+8, chanListOut->y()+chanListOut->h()+8, 120, 20, "Enable MIDI lightning output"); + new geMidiLearner(x()+8, enableLightning->y()+enableLightning->h()+8, w()-16, "playing", cb_learn, &ch->midiOutLplaying); + new geMidiLearner(x()+8, enableLightning->y()+enableLightning->h()+32, w()-16, "mute", cb_learn, &ch->midiOutLmute); + new geMidiLearner(x()+8, enableLightning->y()+enableLightning->h()+56, w()-16, "solo", cb_learn, &ch->midiOutLsolo); + + close = new gButton(w()-88, enableLightning->y()+enableLightning->h()+84, 80, 20, "Close"); + + end(); + + chanListOut->add("Channel 1"); + chanListOut->add("Channel 2"); + chanListOut->add("Channel 3"); + chanListOut->add("Channel 4"); + chanListOut->add("Channel 5"); + chanListOut->add("Channel 6"); + chanListOut->add("Channel 7"); + chanListOut->add("Channel 8"); + chanListOut->add("Channel 9"); + chanListOut->add("Channel 10"); + chanListOut->add("Channel 11"); + chanListOut->add("Channel 12"); + chanListOut->add("Channel 13"); + chanListOut->add("Channel 14"); + chanListOut->add("Channel 15"); + chanListOut->add("Channel 16"); + chanListOut->value(0); + + if (ch->midiOut) + enableOut->value(1); + else + chanListOut->deactivate(); + + if (ch->midiOutL) + enableLightning->value(1); + + chanListOut->value(ch->midiOutChan); + + enableOut->callback(cb_enableChanList, (void*)this); + close->callback(cb_close, (void*)this); + + set_modal(); + gu_setFavicon(this); + show(); +} + + +/* -------------------------------------------------------------------------- */ + + +void gdMidiOutputMidiCh::cb_close (Fl_Widget *w, void *p) { ((gdMidiOutputMidiCh*)p)->__cb_close(); } +void gdMidiOutputMidiCh::cb_enableChanList(Fl_Widget *w, void *p) { ((gdMidiOutputMidiCh*)p)->__cb_enableChanList(); } + + +/* -------------------------------------------------------------------------- */ + + +void gdMidiOutputMidiCh::__cb_enableChanList() +{ + enableOut->value() ? chanListOut->activate() : chanListOut->deactivate(); +} + + +/* -------------------------------------------------------------------------- */ + + +void gdMidiOutputMidiCh::__cb_close() +{ + ch->midiOut = enableOut->value(); + ch->midiOutChan = chanListOut->value(); + ch->midiOutL = enableLightning->value(); + ch->guiChannel->update(); + do_callback(); +} diff --git a/src/gui/dialogs/midiIO/midiOutputMidiCh.h b/src/gui/dialogs/midiIO/midiOutputMidiCh.h new file mode 100644 index 0000000..a0deb21 --- /dev/null +++ b/src/gui/dialogs/midiIO/midiOutputMidiCh.h @@ -0,0 +1,61 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * midiOutputMidiCh + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#ifndef GD_MIDI_OUTPUT_MIDI_CH_H +#define GD_MIDI_OUTPUT_MIDI_CH_H + + +#include "midiOutputBase.h" + + +class gdMidiOutputMidiCh : public gdMidiOutputBase +{ +private: + + static void cb_enableChanList (Fl_Widget *w, void *p); + inline void __cb_enableChanList(); + + /* __cb_close + override parent method, we need to do more stuff on close. */ + + static void cb_close (Fl_Widget *w, void *p); + inline void __cb_close(); + + class gCheck *enableOut; + class gChoice *chanListOut; + + class MidiChannel *ch; + +public: + + gdMidiOutputMidiCh(class MidiChannel *ch); +}; + + +#endif diff --git a/src/gui/dialogs/midiIO/midiOutputSampleCh.cpp b/src/gui/dialogs/midiIO/midiOutputSampleCh.cpp new file mode 100644 index 0000000..4cbe682 --- /dev/null +++ b/src/gui/dialogs/midiIO/midiOutputSampleCh.cpp @@ -0,0 +1,72 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * midiOutputSampleCh + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include "../../../core/sampleChannel.h" +#include "../../../utils/gui.h" +#include "../../elems/ge_mixed.h" +#include "../../elems/midiLearner.h" +#include "midiOutputSampleCh.h" + + +gdMidiOutputSampleCh::gdMidiOutputSampleCh(SampleChannel *ch) + : gdMidiOutputBase(300, 140), ch(ch) +{ + setTitle(ch->index+1); + + enableLightning = new gCheck(8, 8, 120, 20, "Enable MIDI lightning output"); + new geMidiLearner(8, enableLightning->y()+enableLightning->h()+8, w()-16, "playing", cb_learn, &ch->midiOutLplaying); + new geMidiLearner(8, enableLightning->y()+enableLightning->h()+32, w()-16, "mute", cb_learn, &ch->midiOutLmute); + new geMidiLearner(8, enableLightning->y()+enableLightning->h()+56, w()-16, "solo", cb_learn, &ch->midiOutLsolo); + + close = new gButton(w()-88, enableLightning->y()+enableLightning->h()+84, 80, 20, "Close"); + close->callback(cb_close, (void*)this); + + enableLightning->value(ch->midiOutL); + enableLightning->callback(cb_enableLightning, (void*)this); + + set_modal(); + gu_setFavicon(this); + show(); +} + + +/* -------------------------------------------------------------------------- */ + + +void gdMidiOutputSampleCh::cb_close(Fl_Widget *w, void *p) { ((gdMidiOutputSampleCh*)p)->__cb_close(); } + + +/* -------------------------------------------------------------------------- */ + + +void gdMidiOutputSampleCh::__cb_close() +{ + ch->midiOutL = enableLightning->value(); + do_callback(); +} diff --git a/src/gui/dialogs/midiIO/midiOutputSampleCh.h b/src/gui/dialogs/midiIO/midiOutputSampleCh.h new file mode 100644 index 0000000..b229ca8 --- /dev/null +++ b/src/gui/dialogs/midiIO/midiOutputSampleCh.h @@ -0,0 +1,54 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * midiOutputSampleCh + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#ifndef GD_MIDI_OUTPUT_SAMPLE_CH_H +#define GD_MIDI_OUTPUT_SAMPLE_CH_H + + +#include "midiOutputBase.h" + + +class gdMidiOutputSampleCh : public gdMidiOutputBase +{ +private: + + class SampleChannel *ch; + + /* __cb_close + override parent method, we need to do more stuff on close. */ + + static void cb_close (Fl_Widget *w, void *p); + inline void __cb_close(); + +public: + + gdMidiOutputSampleCh(class SampleChannel *ch); +}; + +#endif diff --git a/src/gui/elems/actionEditor.cpp b/src/gui/elems/actionEditor.cpp new file mode 100644 index 0000000..065860d --- /dev/null +++ b/src/gui/elems/actionEditor.cpp @@ -0,0 +1,689 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ge_actionChannel + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include +#include "../../core/conf.h" +#include "../../core/channel.h" +#include "../../core/sampleChannel.h" +#include "../../glue/main.h" +#include "../../utils/log.h" +#include "../dialogs/gd_mainWindow.h" +#include "../dialogs/gd_actionEditor.h" +#include "mainWindow/keyboard/keyboard.h" +#include "actionEditor.h" + + +extern gdMainWindow *G_MainWin; +extern Mixer G_Mixer; +extern Conf G_Conf; +extern Recorder G_Recorder; + + +/* -------------------------------------------------------------------------- */ + + +geActionEditor::geActionEditor(int x, int y, gdActionEditor *pParent, SampleChannel *ch) + : geBaseActionEditor(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->index); + - that are covered by the grey area (> G_Mixer.totalFrames); + - of type ACTION_KILLCHAN in a SINGLE_PRESS channel. They cannot be + recorded in such mode, but they can exist if you change from another + mode to singlepress; + - of type ACTION_KEYREL in a SINGLE_PRESS channel. It's up to gAction to + find the other piece (namely frame_b) + - not of types ACTION_KEYPRESS | ACTION_KEYREL | ACTION_KILLCHAN */ + + if ((action->chan != pParent->chan->index) || + (G_Recorder.frames.at(i) > G_Mixer.totalFrames) || + (action->type == ACTION_KILLCHAN && ch->mode == SINGLE_PRESS) || + (action->type == ACTION_KEYREL && ch->mode == SINGLE_PRESS) || + (action->type & ~(ACTION_KEYPRESS | ACTION_KEYREL | ACTION_KILLCHAN)) + ) + continue; + + int ax = x + (action->frame / pParent->zoom); + gAction *a = new gAction( + ax, // x + y + 4, // y + h() - 8, // h + action->frame, // frame_a + i, // n. of recordings + pParent, // pointer to the pParent window + ch, // pointer to SampleChannel + false, // record = false: don't record it, we are just displaying it! + action->type); // type of action + add(a); + } + } + end(); // mandatory when you add widgets to a fl_group, otherwise mega malfunctions +} + + +/* -------------------------------------------------------------------------- */ + + +gAction *geActionEditor::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 geActionEditor::updateActions() +{ + /* when zooming, don't delete and re-add actions, just MOVE them. This + * function shifts the action by a zoom factor. Those singlepress are + * stretched, as well */ + + 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 geActionEditor::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 geActionEditor::handle(int e) +{ + int ret = Fl_Group::handle(e); + + /* do nothing if the widget is deactivated. It could happen for loopmode + * channels */ + + if (!active()) + return 1; + + switch (e) { + + case FL_DRAG: { + + if (selected == NULL) { // if you drag an empty area + ret = 1; + break; + } + + /* if onLeftEdge o onRightEdge are true it means that you're resizing + * an action. Otherwise move the widget. */ + + if (selected->onLeftEdge || selected->onRightEdge) { + + /* some checks: a) cannot resize an action < N pixels, b) no beyond zero, + * c) no beyond bar maxwidth. Checks for overlap are done in FL_RELEASE */ + + if (selected->onRightEdge) { + + int aw = Fl::event_x()-selected->x(); + int ah = selected->h(); + + if (Fl::event_x() < selected->x()+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 + G_Recorder.frames.size()-1, // n. of actions recorded + pParent, // pParent window pointer + ch, // pointer to SampleChannel + true, // record = true: record it! + pParent->getActionType()); // type of action + add(a); + G_MainWin->keyboard->setChannelWithActions((geSampleChannel*)ch->guiChannel); // mainWindow update + redraw(); + ret = 1; + } + else { + actionOriginalX = selected->x(); + actionOriginalW = selected->w(); + actionPickPoint = Fl::event_x() - selected->x(); + ret = 1; // for dragging + } + } + else + if (Fl::event_button3()) { + gAction *a = getSelectedAction(); + if (a != NULL) { + a->delAction(); + remove(a); + delete a; + G_MainWin->keyboard->setChannelWithActions((geSampleChannel*)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 geActionEditor::actionCollides(int frame) +{ + /* if SINGLE_PRESS we check that the tail (frame_b) of the action doesn't + * overlap the head (frame) of the new one. First the general case, yet. */ + + bool collision = false; + + for (int i=0; iframe_a == frame) + collision = true; + + if (ch->mode == SINGLE_PRESS) { + for (int i=0; iframe_b && frame >= c->frame_a) + collision = true; + } + } + + return collision; +} + + +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ + + +/** 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; + G_Recorder.getNextAction(ch->index, ACTION_KEYREL, frame_a, &a2); + if (a2) { + frame_b = a2->frame; + w((frame_b - frame_a)/parent->zoom); + } + else + gu_log("[geActionEditor] frame_b not found! [%d:???]\n", frame_a); + + /* a singlepress action narrower than 8 pixel is useless. So check it. + * Warning: if an action is 8 px narrow, it has no body space to drag + * it. It's up to the user to zoom in and drag it. */ + + if (w() < MIN_WIDTH) + size(MIN_WIDTH, h()); + } +} + + +/* -------------------------------------------------------------------------- */ + + +void gAction::draw() +{ + int color; + if (selected) /// && geActionEditor !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) { + G_Recorder.rec(parent->chan->index, ACTION_KEYPRESS, frame_a); + G_Recorder.rec(parent->chan->index, ACTION_KEYREL, frame_a+4096); + //gu_log("action added, [%d, %d]\n", frame_a, frame_a+4096); + } + else { + G_Recorder.rec(parent->chan->index, parent->getActionType(), frame_a); + //gu_log("action added, [%d]\n", frame_a); + } + + parent->chan->hasActions = true; + + G_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) { + G_Recorder.deleteAction(parent->chan->index, frame_a, ACTION_KEYPRESS, + false, &G_Mixer.mutex_recs); + G_Recorder.deleteAction(parent->chan->index, frame_b, ACTION_KEYREL, + false, &G_Mixer.mutex_recs); + } + else + G_Recorder.deleteAction(parent->chan->index, frame_a, type, false, + &G_Mixer.mutex_recs); + + parent->chan->hasActions = G_Recorder.hasActions(parent->chan->index); + + + /* restore the initial cursor shape, in case you delete an action and + * the double arrow (for resizing) is displayed */ + + fl_cursor(FL_CURSOR_DEFAULT, FL_WHITE, FL_BLACK); +} + + +/* -------------------------------------------------------------------------- */ + + +void 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++; + + G_Recorder.rec(parent->chan->index, type, this->frame_a); + + if (ch->mode == SINGLE_PRESS) { + frame_b = xToFrame_b(); + G_Recorder.rec(parent->chan->index, ACTION_KEYREL, frame_b); + } + + parent->chan->hasActions = true; + + G_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/gui/elems/actionEditor.h b/src/gui/elems/actionEditor.h new file mode 100644 index 0000000..56f0ce8 --- /dev/null +++ b/src/gui/elems/actionEditor.h @@ -0,0 +1,140 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ge_actionChannel + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License 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 "../../utils/gui.h" +#include "../../core/mixer.h" +#include "../../core/recorder.h" +#include "baseActionEditor.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 = 8; +}; + + +/* -------------------------------------------------------------------------- */ + + +class geActionEditor : public geBaseActionEditor +{ + +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: + + geActionEditor(int x, int y, gdActionEditor *pParent, class SampleChannel *ch); + void draw(); + int handle(int e); + void updateActions(); +}; + + +#endif diff --git a/src/gui/elems/baseActionEditor.cpp b/src/gui/elems/baseActionEditor.cpp new file mode 100644 index 0000000..bc7a412 --- /dev/null +++ b/src/gui/elems/baseActionEditor.cpp @@ -0,0 +1,102 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ge_actionWidget + * + * pParent class of any widget inside the action editor. + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include +#include "../../core/mixer.h" +#include "../dialogs/gd_actionEditor.h" +#include "baseActionEditor.h" +#include "ge_mixed.h" + + +extern Mixer G_Mixer; + + +geBaseActionEditor::geBaseActionEditor(int x, int y, int w, int h, + gdActionEditor *pParent) + : Fl_Group(x, y, w, h), pParent(pParent) {} + + +/* -------------------------------------------------------------------------- */ + + +geBaseActionEditor::~geBaseActionEditor() {} + + +/* -------------------------------------------------------------------------- */ + + +void geBaseActionEditor::baseDraw(bool clear) { + + /* clear the screen */ + + if (clear) + fl_rectf(x(), y(), w(), h(), 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/gui/elems/baseActionEditor.h b/src/gui/elems/baseActionEditor.h new file mode 100644 index 0000000..49c74b1 --- /dev/null +++ b/src/gui/elems/baseActionEditor.h @@ -0,0 +1,55 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ge_actionWidget + * + * parent class of any tool inside the action editor. + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License 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 "../../core/const.h" + + +class geBaseActionEditor : public Fl_Group +{ +protected: + + class gdActionEditor *pParent; + void baseDraw(bool clear=true); + +public: + + virtual void updateActions() = 0; + + geBaseActionEditor(int x, int y, int w, int h, gdActionEditor *pParent); + ~geBaseActionEditor(); +}; + +#endif diff --git a/src/gui/elems/basics/boxtypes.cpp b/src/gui/elems/basics/boxtypes.cpp new file mode 100644 index 0000000..547d1aa --- /dev/null +++ b/src/gui/elems/basics/boxtypes.cpp @@ -0,0 +1,59 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * boxtypes + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include +#include "../../../core/const.h" +#include "boxtypes.h" + + +void g_customBorderBox(int x, int y, int w, int h, Fl_Color c) +{ + fl_color(c); + fl_rectf(x, y, w, h); + fl_color(COLOR_BD_0); + fl_rect(x, y, w, h); +} + + +void g_customUpBox(int x, int y, int w, int h, Fl_Color c) +{ + fl_color(COLOR_BG_0); + fl_rectf(x, y, w, h); + fl_color(COLOR_BG_0); + fl_rect(x, y, w, h); +} + + +void g_customDownBox(int x, int y, int w, int h, Fl_Color c) +{ + fl_color(c); + fl_rectf(x, y, w, h); + fl_color(COLOR_BG_0); + fl_rect(x, y, w, h); +} diff --git a/src/gui/elems/basics/boxtypes.h b/src/gui/elems/basics/boxtypes.h new file mode 100644 index 0000000..d717c86 --- /dev/null +++ b/src/gui/elems/basics/boxtypes.h @@ -0,0 +1,47 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * boxtypes + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License 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 __BOXTYPES_H__ +#define __BOXTYPES_H__ + + +#include + + +#define G_CUSTOM_BORDER_BOX FL_FREE_BOXTYPE +#define G_CUSTOM_UP_BOX (Fl_Boxtype)(FL_FREE_BOXTYPE + 1) +#define G_CUSTOM_DOWN_BOX (Fl_Boxtype)(FL_FREE_BOXTYPE + 3) + + +void g_customBorderBox(int x, int y, int w, int h, Fl_Color c); +void g_customUpBox (int x, int y, int w, int h, Fl_Color c); +void g_customDownBox (int x, int y, int w, int h, Fl_Color c); + + +#endif diff --git a/src/gui/elems/basics/scroll.cpp b/src/gui/elems/basics/scroll.cpp new file mode 100644 index 0000000..9cee240 --- /dev/null +++ b/src/gui/elems/basics/scroll.cpp @@ -0,0 +1,50 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * geScroll + * Custom scroll with nice scrollbars and something else. + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include "../../../core/const.h" +#include "boxtypes.h" +#include "scroll.h" + + +geScroll::geScroll(int x, int y, int w, int h, int t) + : Fl_Scroll(x, y, w, h) +{ + type(t); + + scrollbar.color(COLOR_BG_0); + scrollbar.selection_color(COLOR_BG_1); + scrollbar.labelcolor(COLOR_BD_1); + scrollbar.slider(G_CUSTOM_BORDER_BOX); + + hscrollbar.color(COLOR_BG_0); + hscrollbar.selection_color(COLOR_BG_1); + hscrollbar.labelcolor(COLOR_BD_1); + hscrollbar.slider(G_CUSTOM_BORDER_BOX); +} diff --git a/src/gui/elems/basics/scroll.h b/src/gui/elems/basics/scroll.h new file mode 100644 index 0000000..b122d44 --- /dev/null +++ b/src/gui/elems/basics/scroll.h @@ -0,0 +1,46 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * geScroll + * Custom scroll with nice scrollbars and something else. + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#ifndef __GE_SCROLL_H__ +#define __GE_SCROLL_H__ + + +#include + + +class geScroll : public Fl_Scroll +{ +public: + + geScroll(int x, int y, int w, int h, int type=Fl_Scroll::BOTH); +}; + + +#endif diff --git a/src/gui/elems/browser.cpp b/src/gui/elems/browser.cpp new file mode 100644 index 0000000..812aaba --- /dev/null +++ b/src/gui/elems/browser.cpp @@ -0,0 +1,188 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ge_browser + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include "../../core/const.h" +#include "../../utils/string.h" +#include "../dialogs/gd_browser.h" +#include "../elems/ge_mixed.h" +#include "basics/boxtypes.h" +#include "browser.h" + + +geBrowser::geBrowser(int x, int y, int w, int h) + : Fl_File_Browser(x, y, w, h), + showHiddenFiles(false) +{ + box(G_CUSTOM_BORDER_BOX); + textsize(GUI_FONT_SIZE_BASE); + textcolor(COLOR_TEXT_0); + selection_color(COLOR_BG_1); + color(COLOR_BG_0); + type(FL_SELECT_BROWSER); + + this->scrollbar.color(COLOR_BG_0); + this->scrollbar.selection_color(COLOR_BG_1); + this->scrollbar.labelcolor(COLOR_BD_1); + this->scrollbar.slider(G_CUSTOM_BORDER_BOX); + + this->hscrollbar.color(COLOR_BG_0); + this->hscrollbar.selection_color(COLOR_BG_1); + this->hscrollbar.labelcolor(COLOR_BD_1); + this->hscrollbar.slider(G_CUSTOM_BORDER_BOX); + + take_focus(); // let it have focus on startup +} + + +/* -------------------------------------------------------------------------- */ + + +void geBrowser::toggleHiddenFiles() +{ + showHiddenFiles = !showHiddenFiles; + loadDir(currentDir); +} + + +/* -------------------------------------------------------------------------- */ + + +void geBrowser::loadDir(const string &dir) +{ + currentDir = dir; + load(currentDir.c_str()); + + /* Clean up unwanted elements. Hide "../" first, it just screws up things. + Also remove hidden files, if requested. */ + + for (int i=size(); i>=0; i--) { + if (text(i) == nullptr) + continue; + if (strcmp(text(i), "../") == 0 || (!showHiddenFiles && strncmp(text(i), ".", 1) == 0)) + remove(i); + } +} + + +/* -------------------------------------------------------------------------- */ + +int geBrowser::handle(int e) +{ + int ret = Fl_File_Browser::handle(e); + switch (e) { + case FL_FOCUS: + case FL_UNFOCUS: + ret = 1; // enables receiving Keyboard events + break; + case FL_KEYDOWN: // keyboard + if (Fl::event_key(FL_Down)) + select(value() + 1); + else + if (Fl::event_key(FL_Up)) + select(value() - 1); + else + if (Fl::event_key(FL_Enter)) + ((gdBaseBrowser*) parent())->fireCallback(); + ret = 1; + break; + case FL_PUSH: // mouse + if (Fl::event_clicks() > 0) // double click + ((gdBaseBrowser*) parent())->fireCallback(); + ret = 1; + break; + case FL_RELEASE: // mouse + /* nasty trick to keep the selection on mouse release */ + if (value() > 1) { + select(value() - 1); + select(value() + 1); + } + else { + select(value() + 1); + select(value() - 1); + } + ret = 1; + break; + } + return ret; +} + +/* -------------------------------------------------------------------------- */ + + +string geBrowser::getCurrentDir() +{ + return normalize(gu_getRealPath(currentDir)); +} + + +/* -------------------------------------------------------------------------- */ + + +string geBrowser::getSelectedItem(bool fullPath) +{ + if (!fullPath) // no full path requested? return the selected text + return normalize(text(value())); + else + if (value() == 0) // no rows selected? return current directory + return normalize(currentDir); + else + return normalize(gu_getRealPath(currentDir + G_SLASH + normalize(text(value())))); +} + + +/* -------------------------------------------------------------------------- */ + + +void geBrowser::preselect(int pos, int line) +{ + position(pos); + select(line); +} + + +/* -------------------------------------------------------------------------- */ + + +string geBrowser::normalize(const string &s) +{ + string out = s; + + /* If string ends with G_SLASH, remove it. Don't do it if has length > 1, it + means that the string is just '/'. Note: our crappy version of Clang doesn't + seem to support std::string::back() */ + +#ifdef __APPLE__ + if (out[out.length() - 1] == G_SLASH && out.length() > 1) +#else + if (out.back() == G_SLASH && out.length() > 1) +#endif + + out = out.substr(0, out.size()-1); + return out; +} diff --git a/src/gui/elems/browser.h b/src/gui/elems/browser.h new file mode 100644 index 0000000..4515dbf --- /dev/null +++ b/src/gui/elems/browser.h @@ -0,0 +1,77 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ge_browser + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License 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 + + +using std::string; + + +class geBrowser : public Fl_File_Browser +{ +private: + + string currentDir; + bool showHiddenFiles; + + /* normalize + * Make sure the string never ends with a trailing slash. */ + + string normalize(const string &s); + +public: + + geBrowser(int x, int y, int w, int h); + + void toggleHiddenFiles(); + + /* init + * Initialize browser and show 'dir' as initial directory. */ + + void loadDir(const string &dir); + + /* getSelectedItem + * Return the full path or just the displayed name of the i-th selected item. + * Always with the trailing slash! */ + + string getSelectedItem(bool fullPath=true); + + string getCurrentDir(); + + void preselect(int position, int line); + + int handle(int e); +}; + +#endif diff --git a/src/gui/elems/envelopeEditor.cpp b/src/gui/elems/envelopeEditor.cpp new file mode 100644 index 0000000..796a3fe --- /dev/null +++ b/src/gui/elems/envelopeEditor.cpp @@ -0,0 +1,413 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ge_envelopeWidget + * + * Parent class of any envelope controller, from volume to VST parameter + * automations. + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + + +#include +#include "../../core/channel.h" +#include "../../core/recorder.h" +#include "../../core/mixer.h" +#include "../dialogs/gd_actionEditor.h" +#include "../dialogs/gd_mainWindow.h" +#include "mainWindow/keyboard/keyboard.h" +#include "envelopeEditor.h" + + +extern Mixer G_Mixer; +extern Recorder G_Recorder; +extern gdMainWindow *G_MainWin; + + +geEnvelopeEditor::geEnvelopeEditor(int x, int y, gdActionEditor *pParent, + int type, int range, const char *l) + : geBaseActionEditor(x, y, 200, 80, pParent), + l (l), + type (type), + range (range), + selectedPoint (-1), + draggedPoint (-1) +{ + size(pParent->totalWidth, h()); +} + + +/* ------------------------------------------------------------------ */ + + +geEnvelopeEditor::~geEnvelopeEditor() { + clearPoints(); +} + + +/* ------------------------------------------------------------------ */ + + +void geEnvelopeEditor::addPoint(int frame, int iValue, float fValue, int px, int py) { + point p; + p.frame = frame; + p.iValue = iValue; + p.fValue = fValue; + p.x = px; + p.y = py; + points.push_back(p); +} + + +/* ------------------------------------------------------------------ */ + + +void geEnvelopeEditor::updateActions() { + for (unsigned i=0; izoom; +} + + +/* ------------------------------------------------------------------ */ + + +void geEnvelopeEditor::draw() { + + baseDraw(); + + /* print label */ + + fl_color(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 geEnvelopeEditor::handle(int e) { + + /* Adding an action: no further checks required, just record it on frame + * mx*pParent->zoom. Deleting action is trickier: find the active + * point and derive from it the corresponding frame. */ + + int ret = 0; + int mx = Fl::event_x()-x(); // mouse x + int my = Fl::event_y()-y(); // mouse y + + switch (e) { + + case FL_ENTER: { + ret = 1; + break; + } + + case FL_MOVE: { + selectedPoint = getSelectedPoint(); + redraw(); + ret = 1; + break; + } + + case FL_LEAVE: { + draggedPoint = -1; + selectedPoint = -1; + redraw(); + ret = 1; + break; + } + + case FL_PUSH: { + + /* left click on point: drag + * right click on point: delete + * left click on void: add */ + + if (Fl::event_button1()) { + + if (selectedPoint != -1) { + draggedPoint = selectedPoint; + } + else { + + /* top & border fix */ + + if (my > h()-8) my = h()-8; + if (mx > pParent->coverX-x()) mx = pParent->coverX-x(); + + if (range == 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); + G_Recorder.rec(pParent->chan->index, type, 0, 0, 1.0f); + addPoint(G_Mixer.totalFrames, 0, 1.0f, pParent->coverX, 1); + G_Recorder.rec(pParent->chan->index, type, G_Mixer.totalFrames, 0, 1.0f); + pParent->chan->hasActions = true; + } + + /* line between 2 points y = (x-a) / (b-a); a = h() - 8; b = 1 */ + + int frame = mx * pParent->zoom; + float value = (my - h() + 8) / (float) (1 - h() + 8); + addPoint(frame, 0, value, mx, my); + G_Recorder.rec(pParent->chan->index, type, frame, 0, value); + pParent->chan->hasActions = true; + G_Recorder.sortActions(); + sortPoints(); + } + else { + /// TODO + } + G_MainWin->keyboard->setChannelWithActions((geSampleChannel*)pParent->chan->guiChannel); // update mainWindow + redraw(); + } + } + else { + + /* right click on point 0 or point size-1 deletes the entire envelope */ + + if (selectedPoint != -1) { + if (selectedPoint == 0 || (unsigned) selectedPoint == points.size()-1) { + G_Recorder.clearAction(pParent->chan->index, type); + pParent->chan->hasActions = G_Recorder.hasActions(pParent->chan->index); + points.clear(); + } + else { + G_Recorder.deleteAction(pParent->chan->index, + points.at(selectedPoint).frame, type, false, &G_Mixer.mutex_recs); + pParent->chan->hasActions = G_Recorder.hasActions(pParent->chan->index); + G_Recorder.sortActions(); + points.erase(points.begin() + selectedPoint); + } + G_MainWin->keyboard->setChannelWithActions((geSampleChannel*)pParent->chan->guiChannel); // update mainWindow + redraw(); + } + } + + ret = 1; + break; + } + + case FL_RELEASE: { + if (draggedPoint != -1) { + + if (points.at(draggedPoint).x == previousXPoint) { + //gu_log("nothing to do\n"); + } + else { + int newFrame = points.at(draggedPoint).x * pParent->zoom; + + /* x edge correction */ + + if (newFrame < 0) + newFrame = 0; + else if (newFrame > 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 */ + + G_Recorder.deleteAction(pParent->chan->index, + points.at(draggedPoint).frame, type, false, &G_Mixer.mutex_recs); + pParent->chan->hasActions = G_Recorder.hasActions(pParent->chan->index); + + if (range == RANGE_FLOAT) { + float value = (points.at(draggedPoint).y - h() + 8) / (float) (1 - h() + 8); + G_Recorder.rec(pParent->chan->index, type, newFrame, 0, value); + pParent->chan->hasActions = true; + } + else { + /// TODO + } + + G_Recorder.sortActions(); + points.at(draggedPoint).frame = newFrame; + draggedPoint = -1; + selectedPoint = -1; + } + } + ret = 1; + break; + } + + case FL_DRAG: { + + if (draggedPoint != -1) { + + /* y constraint */ + + if (my > h()-8) + points.at(draggedPoint).y = h()-8; + else + if (my < 1) + points.at(draggedPoint).y = 1; + else + points.at(draggedPoint).y = my; + + /* x constraint + * constrain the point between two ends (leftBorder-point, point-point, + * point-rightBorder). First & last points cannot be shifted on x */ + + if (draggedPoint == 0) + points.at(draggedPoint).x = x()-8; + else + if ((unsigned) draggedPoint == points.size()-1) + points.at(draggedPoint).x = pParent->coverX; + else { + int prevPoint = points.at(draggedPoint-1).x; + int nextPoint = points.at(draggedPoint+1).x; + if (mx <= prevPoint) + points.at(draggedPoint).x = prevPoint; + else + if (mx >= nextPoint) + points.at(draggedPoint).x = nextPoint; + //else + // points.at(draggedPoint).x = mx; + else { + if (pParent->gridTool->isOn()) + points.at(draggedPoint).x = pParent->gridTool->getSnapPoint(mx)-1; + else + points.at(draggedPoint).x = mx; + } + } + redraw(); + } + + ret = 1; + break; + } + } + + return ret; +} + + +/* ------------------------------------------------------------------ */ + + +int geEnvelopeEditor::verticalPoint(const point &p) { + for (unsigned i=0; i points.at(i).x) + std::swap(points.at(j), points.at(i)); +} + + +/* ------------------------------------------------------------------ */ + + +int geEnvelopeEditor::getSelectedPoint() { + + /* point is a 7x7 dot */ + + for (unsigned i=0; i= points.at(i).x+x()-4 && + Fl::event_x() <= points.at(i).x+x()+4 && + Fl::event_y() >= points.at(i).y+y() && + Fl::event_y() <= points.at(i).y+y()+7) + return i; + } + return -1; +} + + +/* ------------------------------------------------------------------ */ + + +void geEnvelopeEditor::fill() { + points.clear(); + for (unsigned i=0; itype == type && a->chan == pParent->chan->index) { + if (range == 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/gui/elems/envelopeEditor.h b/src/gui/elems/envelopeEditor.h new file mode 100644 index 0000000..f7dade4 --- /dev/null +++ b/src/gui/elems/envelopeEditor.h @@ -0,0 +1,122 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ge_envelopeWidget + * + * parent class of any envelope controller, from volume to VST parameter + * automations. + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License 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 +#include "../../utils/fs.h" +#include "baseActionEditor.h" + + +using std::vector; + + +class geEnvelopeEditor : public geBaseActionEditor +{ + const char *l; // internal label + int type; // type of action + int range; + + /* point + * a single dot in the graph. x = relative frame, y = relative value */ + + struct point + { + int frame; + int iValue; + float fValue; + int x; + int y; + }; + + /* points + * array of points, filled by fillPoints() */ + + vector points; + + /* selectedPoint + * which point we are selecting? */ + + int selectedPoint; + + /* draggedPoint + * which point we are dragging? */ + + int draggedPoint; + + /* previousXPoint + * x coordinate of point at time t-1. Used to check effective shifts */ + + int previousXPoint; + + void draw(); + + int handle(int e); + + int getSelectedPoint(); + + void sortPoints(); + + /* verticalPoint + * check if two points form a vertical line. In that case the frame value + * would be the same and recorder would go crazy, so shift by a small value + * of frames to create a minimal fadein/fadeout level. Return 0: no + * vertical points; return 1: vertical with the next one, return -1: vertical + * with the previous one. */ + + int verticalPoint(const point &p); + +public: + + geEnvelopeEditor(int x, int y, gdActionEditor *pParent, int type, int range, const char *l); + ~geEnvelopeEditor(); + + /* addPoint + * add a point made of frame+value to internal points[]. */ + + void addPoint(int frame, int iValue=0, float fValue=0.0f, int x=-1, int y=-1); + + void updateActions(); + + /* fill + * parse recorder's stack and fill the widget with points. It's up to + * the caller to call this method as initialization. */ + + void fill(); + + inline void clearPoints() { points.clear(); } +}; + +#endif diff --git a/src/gui/elems/ge_mixed.cpp b/src/gui/elems/ge_mixed.cpp new file mode 100644 index 0000000..913f023 --- /dev/null +++ b/src/gui/elems/ge_mixed.cpp @@ -0,0 +1,603 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ge_mixed + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include +#include "../../core/const.h" +#include "../../core/mixer.h" +#include "../../core/graphics.h" +#include "../../core/recorder.h" +#include "../../core/channel.h" +#include "../../core/sampleChannel.h" +#include "../../utils/gui.h" +#include "../dialogs/gd_mainWindow.h" +#include "basics/boxtypes.h" +#include "ge_mixed.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, GUI_FONT_SIZE_BASE); + fl_draw(label(), x()+2, y(), w()-2, 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, GUI_FONT_SIZE_BASE); + 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_CUSTOM_BORDER_BOX, gDrawBox, 1, 1, 2, 2); + box(G_CUSTOM_BORDER_BOX); + labelsize(GUI_FONT_SIZE_BASE); + labelcolor(COLOR_TEXT_0); + color(COLOR_BG_DARK); + textcolor(COLOR_TEXT_0); + cursor_color(COLOR_TEXT_0); + selection_color(COLOR_BD_0); + textsize(GUI_FONT_SIZE_BASE); +} + + +/* -------------------------------------------------------------------------- */ + + +gDial::gDial(int x, int y, int w, int h, const char *L) +: Fl_Dial(x, y, w, h, L) +{ + labelsize(GUI_FONT_SIZE_BASE); + 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) +{ + copy_label(L); + labelsize(GUI_FONT_SIZE_BASE); + 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, GUI_FONT_SIZE_BASE); + fl_color(!active() ? FL_INACTIVE_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, GUI_FONT_SIZE_BASE); + 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_CUSTOM_BORDER_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), + 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); +} + + +/* -------------------------------------------------------------------------- */ + + +gChoice::gChoice(int x, int y, int w, int h, const char *l, bool ang) + : Fl_Choice(x, y, w, h, l), angle(ang) +{ + labelsize(GUI_FONT_SIZE_BASE); + labelcolor(COLOR_TEXT_0); + box(FL_BORDER_BOX); + textsize(GUI_FONT_SIZE_BASE); + textcolor(COLOR_TEXT_0); + color(COLOR_BG_0); +} + + +void gChoice::draw() +{ + fl_rectf(x(), y(), w(), h(), COLOR_BG_0); // bg + fl_rect(x(), y(), w(), h(), (Fl_Color) COLOR_BD_0); // border + if (angle) + fl_polygon(x()+w()-8, y()+h()-1, x()+w()-1, y()+h()-8, x()+w()-1, y()+h()-1); + + /* pick up the text() from the selected item (value()) and print it in + * the box and avoid overflows */ + + fl_color(!active() ? COLOR_BD_0 : COLOR_TEXT_0); + if (value() != -1) { + if (fl_width(text(value())) < w()-8) { + fl_draw(text(value()), x(), y(), w(), h(), FL_ALIGN_CENTER); + } + else { + std::string tmp = text(value()); + int size = tmp.size(); + while (fl_width(tmp.c_str()) >= w()-16) { + tmp.resize(size); + size--; + } + tmp += "..."; + fl_draw(tmp.c_str(), x(), y(), w(), h(), FL_ALIGN_CENTER); + } + + } +} + + +/* -------------------------------------------------------------------------- */ + + +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_CUSTOM_BORDER_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(GUI_FONT_SIZE_BASE); + align(FL_ALIGN_LEFT); + labelcolor(COLOR_TEXT_0); + + box(G_CUSTOM_BORDER_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() +{ + gu_log("------ 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); +} + + +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ + + +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/gui/elems/ge_mixed.h b/src/gui/elems/ge_mixed.h new file mode 100644 index 0000000..4b1090e --- /dev/null +++ b/src/gui/elems/ge_mixed.h @@ -0,0 +1,332 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ge_mixed + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License 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. */ + /* TODO - is this really useful? */ + +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 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 showItem(const char *c) {value(find_index(c)); } + + bool angle; + int id; +}; + + +/* -------------------------------------------------------------------------- */ + + +/* 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); +}; + + +/* -------------------------------------------------------------------------- */ + +/* 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/gui/elems/ge_pluginBrowser.cpp b/src/gui/elems/ge_pluginBrowser.cpp new file mode 100644 index 0000000..2754fea --- /dev/null +++ b/src/gui/elems/ge_pluginBrowser.cpp @@ -0,0 +1,127 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ge_pluginBrowser + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#ifdef WITH_VST + + +#include "../../core/plugin.h" +#include "../../core/const.h" +#include "../../core/pluginHost.h" +#include "ge_mixed.h" +#include "basics/boxtypes.h" +#include "ge_pluginBrowser.h" + + +extern PluginHost G_PluginHost; + + +using std::vector; + + +gePluginBrowser::gePluginBrowser(int x, int y, int w, int h) + : Fl_Browser(x, y, w, h) +{ + box(G_CUSTOM_BORDER_BOX); + textsize(GUI_FONT_SIZE_BASE); + 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_CUSTOM_BORDER_BOX); + + this->hscrollbar.color(COLOR_BG_0); + this->hscrollbar.selection_color(COLOR_BG_1); + this->hscrollbar.labelcolor(COLOR_BD_1); + this->hscrollbar.slider(G_CUSTOM_BORDER_BOX); + + type(FL_HOLD_BROWSER); + + computeWidths(); + + column_widths(widths); + column_char('\t'); // tabs as column delimiters + + refresh(); + + end(); +} + + +/* -------------------------------------------------------------------------- */ + + +void gePluginBrowser::refresh() +{ + clear(); + + add("NAME\tMANUFACTURER\tCATEGORY\tFORMAT\tUID"); + add("---\t---\t---\t---\t---"); + + for (int i=0; i widths[0]) widths[0] = w0; + if (w1 > widths[1]) widths[1] = w1; + if (w3 > widths[3]) widths[3] = w3; + } + widths[0] += 60; + widths[1] += 60; + widths[2] = fl_width("CATEGORY") + 60; + widths[3] += 60; + widths[4] = 0; +} + + +#endif diff --git a/src/gui/elems/ge_pluginBrowser.h b/src/gui/elems/ge_pluginBrowser.h new file mode 100644 index 0000000..79378b9 --- /dev/null +++ b/src/gui/elems/ge_pluginBrowser.h @@ -0,0 +1,61 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ge_pluginBrowser + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#ifdef WITH_VST + +#ifndef GE_PLUGIN_BROWSER_H +#define GE_PLUGIN_BROWSER_H + + +#include +#include +#include + + +using std::vector; + + +class gePluginBrowser : public Fl_Browser +{ +private: + + int widths[5] = {0}; + + void computeWidths(); + +public: + + gePluginBrowser(int x, int y, int w, int h); + + void refresh(); +}; + +#endif + +#endif diff --git a/src/gui/elems/ge_waveTools.cpp b/src/gui/elems/ge_waveTools.cpp new file mode 100644 index 0000000..e2ec4b7 --- /dev/null +++ b/src/gui/elems/ge_waveTools.cpp @@ -0,0 +1,104 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * gg_waveTools + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + + +#include "../../core/graphics.h" +#include "../../core/mixer.h" +#include "../../core/const.h" +#include "../elems/ge_mixed.h" +#include "../elems/ge_waveform.h" +#include "basics/boxtypes.h" +#include "ge_waveTools.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_CUSTOM_BORDER_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/gui/elems/ge_waveTools.h b/src/gui/elems/ge_waveTools.h new file mode 100644 index 0000000..051dca2 --- /dev/null +++ b/src/gui/elems/ge_waveTools.h @@ -0,0 +1,49 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * gg_waveTools + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License 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_WAVETOOLS_H +#define GE_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/gui/elems/ge_waveform.cpp b/src/gui/elems/ge_waveform.cpp new file mode 100644 index 0000000..022257f --- /dev/null +++ b/src/gui/elems/ge_waveform.cpp @@ -0,0 +1,840 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ge_waveform + * an element which represents a waveform. + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * ------------------------------------------------------------------ */ + + +#include +#include +#include +#include "../../core/wave.h" +#include "../../core/conf.h" +#include "../../core/const.h" +#include "../../core/mixer.h" +#include "../../core/waveFx.h" +#include "../../core/channel.h" +#include "../../core/sampleChannel.h" +#include "../../glue/channel.h" +#include "../dialogs/gd_editor.h" +#include "ge_waveTools.h" +#include "ge_mixed.h" +#include "basics/boxtypes.h" +#include "ge_waveform.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.push_back(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_CUSTOM_BORDER_BOX); + b->textsize(GUI_FONT_SIZE_BASE); + 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/gui/elems/ge_waveform.h b/src/gui/elems/ge_waveform.h new file mode 100644 index 0000000..77a528f --- /dev/null +++ b/src/gui/elems/ge_waveform.h @@ -0,0 +1,199 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ge_waveform + * an element which represents a waveform. + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License 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 +#include "../../utils/fs.h" + + +using std::vector; + + +#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; + vector 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/gui/elems/ge_window.cpp b/src/gui/elems/ge_window.cpp new file mode 100644 index 0000000..e14fcb8 --- /dev/null +++ b/src/gui/elems/ge_window.cpp @@ -0,0 +1,183 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ge_window + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License 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 "../../utils/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()) { + //gu_log("[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.push_back(w); + //debug(); +} + + +/* ------------------------------------------------------------------ */ + + +void gWindow::delSubWindow(gWindow *w) { + for (unsigned i=0; igetId() == subWindows.at(i)->getId()) { + delete subWindows.at(i); + subWindows.erase(subWindows.begin() + i); + //debug(); + return; + } + //debug(); +} + + +/* ------------------------------------------------------------------ */ + + +void gWindow::delSubWindow(int id) { + for (unsigned i=0; igetId() == id) { + delete subWindows.at(i); + subWindows.erase(subWindows.begin() + i); + //debug(); + return; + } + //debug(); +} + + +/* ------------------------------------------------------------------ */ + + +int gWindow::getId() { + return id; +} + + +/* ------------------------------------------------------------------ */ + + +void gWindow::setId(int id) { + this->id = id; +} + + +/* ------------------------------------------------------------------ */ + + +void gWindow::debug() { + gu_log("---- window stack (id=%d): ----\n", getId()); + for (unsigned i=0; igetId()); + gu_log("----\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/gui/elems/ge_window.h b/src/gui/elems/ge_window.h new file mode 100644 index 0000000..e986d8d --- /dev/null +++ b/src/gui/elems/ge_window.h @@ -0,0 +1,77 @@ +/* --------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ge_window + * A custom window. + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License 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 +#include "../../utils/fs.h" + + +using std::vector; + + +class gWindow : public Fl_Double_Window { + +protected: + vector 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/gui/elems/mainWindow/beatMeter.cpp b/src/gui/elems/mainWindow/beatMeter.cpp new file mode 100644 index 0000000..d0bb077 --- /dev/null +++ b/src/gui/elems/mainWindow/beatMeter.cpp @@ -0,0 +1,72 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * beatMeter + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include +#include "../../../core/const.h" +#include "../../../core/mixer.h" +#include "beatMeter.h" + + +extern Mixer G_Mixer; + + +geBeatMeter::geBeatMeter(int x, int y, int w, int h, const char *L) + : Fl_Box(x, y, w, h, L) {} + + +/* -------------------------------------------------------------------------- */ + + +void geBeatMeter::draw() +{ + int cursorW = w() / 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. + * + * -------------------------------------------------------------------------- */ + + +#ifndef GE_BEAT_METER_H +#define GE_BEAT_METER_H + + +#include + + +class geBeatMeter : public Fl_Box +{ +public: + + geBeatMeter(int X,int Y,int W,int H,const char *L=0); + void draw(); +}; + + +#endif diff --git a/src/gui/elems/mainWindow/keyboard/channel.cpp b/src/gui/elems/mainWindow/keyboard/channel.cpp new file mode 100644 index 0000000..1114d7c --- /dev/null +++ b/src/gui/elems/mainWindow/keyboard/channel.cpp @@ -0,0 +1,237 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ge_channel + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include "../../../../core/const.h" +#include "../../../../core/channel.h" +#include "../../../../core/graphics.h" +#include "../../../../core/pluginHost.h" +#include "../../../../utils/gui.h" +#include "../../../../glue/channel.h" +#include "../../../dialogs/gd_mainWindow.h" +#include "../../../dialogs/gd_pluginList.h" +#include "column.h" +#include "channelButton.h" +#include "channel.h" + + +extern gdMainWindow *G_MainWin; + + +geChannel::geChannel(int X, int Y, int W, int H, int type, Channel *ch) + : Fl_Group(X, Y, W, H, NULL), + ch (ch), + type (type) +{ +} + + +/* -------------------------------------------------------------------------- */ + + +void geChannel::cb_arm(Fl_Widget *v, void *p) { ((geChannel*)p)->__cb_arm(); } +void geChannel::cb_mute(Fl_Widget *v, void *p) { ((geChannel*)p)->__cb_mute(); } +void geChannel::cb_solo(Fl_Widget *v, void *p) { ((geChannel*)p)->__cb_solo(); } +void geChannel::cb_changeVol(Fl_Widget *v, void *p) { ((geChannel*)p)->__cb_changeVol(); } +#ifdef WITH_VST +void geChannel::cb_openFxWindow(Fl_Widget *v, void *p) { ((geChannel*)p)->__cb_openFxWindow(); } +#endif + + +/* -------------------------------------------------------------------------- */ + + +void geChannel::__cb_arm() +{ + glue_toggleArm(ch, true); +} + + +/* -------------------------------------------------------------------------- */ + + +void geChannel::__cb_mute() +{ + glue_setMute(ch); +} + + +/* -------------------------------------------------------------------------- */ + + +void geChannel::__cb_solo() +{ + solo->value() ? glue_setSoloOn(ch) : glue_setSoloOff(ch); +} + + +/* -------------------------------------------------------------------------- */ + + +void geChannel::__cb_changeVol() +{ + glue_setChanVol(ch, vol->value()); +} + + +/* -------------------------------------------------------------------------- */ + + +#ifdef WITH_VST +void geChannel::__cb_openFxWindow() +{ + gu_openSubWindow(G_MainWin, new gdPluginList(PluginHost::CHANNEL, ch), WID_FX_LIST); +} +#endif + + +/* -------------------------------------------------------------------------- */ + + +int geChannel::keyPress(int e) +{ + return handleKey(e, ch->key); +} + + +/* -------------------------------------------------------------------------- */ + + + +int geChannel::getColumnIndex() +{ + return ((geColumn*)parent())->getIndex(); +} + + +/* -------------------------------------------------------------------------- */ + + +void geChannel::blink() +{ + if (gu_getBlinker() > 6) + mainButton->setPlayMode(); + else + mainButton->setDefaultMode(); +} + + +/* -------------------------------------------------------------------------- */ + + +void geChannel::setColorsByStatus(int playStatus, int recStatus) +{ + switch (playStatus) { + case STATUS_OFF: + case STATUS_EMPTY: + mainButton->setDefaultMode(); + button->imgOn = channelPlay_xpm; + button->imgOff = channelStop_xpm; + button->redraw(); + break; + case STATUS_PLAY: + mainButton->setPlayMode(); + button->imgOn = channelStop_xpm; + button->imgOff = channelPlay_xpm; + button->redraw(); + break; + case STATUS_WAIT: + blink(); + break; + case STATUS_ENDING: + mainButton->setEndingMode(); + break; + } + + switch (recStatus) { + case REC_WAITING: + blink(); + break; + case REC_ENDING: + mainButton->setEndingMode(); + break; + } +} + + +/* -------------------------------------------------------------------------- */ + + +void geChannel::packWidgets() +{ + /* Count visible widgets and resize mainButton according to how many widgets + are visible. */ + + int visibles = 0; + for (int i=0; isize(20, 20); // also normalize widths + if (child(i)->visible()) + visibles++; + } + mainButton->size(w() - ((visibles - 1) * (24)), 20); // -1: exclude itself + + /* Reposition everything else */ + + for (int i=1, p=0; ivisible()) + continue; + for (int k=i-1; k>=0; k--) // Get the first visible item prior to i + if (child(k)->visible()) { + p = k; + break; + } + child(i)->position(child(p)->x() + child(p)->w() + 4, y()); + } + + init_sizes(); // Resets the internal array of widget sizes and positions +} + + +/* -------------------------------------------------------------------------- */ + + +int geChannel::handleKey(int e, int key) +{ + int ret; + if (e == FL_KEYDOWN && button->value()) // key already pressed! skip it + ret = 1; + else + if (Fl::event_key() == key && !button->value()) { + button->take_focus(); // move focus to this button + button->value((e == FL_KEYDOWN || e == FL_SHORTCUT) ? 1 : 0); // change the button's state + button->do_callback(); // invoke the button's callback + ret = 1; + } + else + ret = 0; + + if (Fl::event_key() == key) + button->value((e == FL_KEYDOWN || e == FL_SHORTCUT) ? 1 : 0); // change the button's state + + return ret; +} diff --git a/src/gui/elems/mainWindow/keyboard/channel.h b/src/gui/elems/mainWindow/keyboard/channel.h new file mode 100644 index 0000000..f76378f --- /dev/null +++ b/src/gui/elems/mainWindow/keyboard/channel.h @@ -0,0 +1,139 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ge_channel + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#ifndef GE_CHANNEL_H +#define GE_CHANNEL_H + + +#include + + +class geChannel : public Fl_Group +{ +protected: + + /* Define some breakpoints for dynamic resize. BREAK_DELTA: base amount of + pixels to shrink sampleButton. */ + +#ifdef WITH_VST + static const int BREAK_READ_ACTIONS = 240; + static const int BREAK_MODE_BOX = 216; + static const int BREAK_FX = 192; + static const int BREAK_ARM = 168; +#else + static const int BREAK_READ_ACTIONS = 216; + static const int BREAK_MODE_BOX = 192; + static const int BREAK_ARM = 168; +#endif + + static void cb_arm (Fl_Widget *v, void *p); + static void cb_mute (Fl_Widget *v, void *p); + static void cb_solo (Fl_Widget *v, void *p); + static void cb_changeVol (Fl_Widget *v, void *p); +#ifdef WITH_VST + static void cb_openFxWindow(Fl_Widget *v, void *p); +#endif + + inline void __cb_mute(); + inline void __cb_arm(); + inline void __cb_solo(); + inline void __cb_changeVol(); +#ifdef WITH_VST + inline void __cb_openFxWindow(); +#endif + + /* blink + * blink button when channel is in wait/ending status. */ + + void blink(); + + /* setColorByStatus + * update colors depending on channel status. */ + + void setColorsByStatus(int playStatus, int recStatus); + + /* handleKey + * method wrapped by virtual handle(int e). */ + + int handleKey(int e, int key); + + /* packWidgets + Spread widgets across available space. */ + + void packWidgets(); + +public: + + geChannel(int x, int y, int w, int h, int type, class Channel *ch); + + /* reset + * reset channel to initial status. */ + + virtual void reset() = 0; + + /* update + * update the label of sample button and everything else such as 'R' + * button, key box and so on, according to global values. */ + + virtual void update() = 0; + + /* refresh + * update graphics. */ + + virtual void refresh() = 0; + + /* keypress + * what to do when the corresponding key is pressed. */ + + int keyPress(int event); + + /* getColumnIndex + * return the numeric index of the column in which this channel is + * located. */ + + int getColumnIndex(); + + class Channel *ch; + + class gButton *button; + class geChannelStatus *status; + class gClick *arm; + class geChannelButton *mainButton; + class gClick *mute; + class gClick *solo; + class gDial *vol; +#ifdef WITH_VST + class gFxButton *fx; +#endif + + int type; +}; + + +#endif diff --git a/src/gui/elems/mainWindow/keyboard/channelButton.cpp b/src/gui/elems/mainWindow/keyboard/channelButton.cpp new file mode 100644 index 0000000..9a17eb4 --- /dev/null +++ b/src/gui/elems/mainWindow/keyboard/channelButton.cpp @@ -0,0 +1,136 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ge_channelButton + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include "../../../../core/const.h" +#include "channelButton.h" + + +using std::string; + + +geChannelButton::geChannelButton(int x, int y, int w, int h, const char *l) + : gClick(x, y, w, h, l), key("") {} + + +/* -------------------------------------------------------------------------- */ + + +void geChannelButton::setKey(const string &k) +{ + key = k; +} + + +/* -------------------------------------------------------------------------- */ + + +void geChannelButton::setKey(int k) +{ + if (k == 0) + key = ""; + else { + // FIXME - this crap won't work with unicode/utf-8 + char c = (char) k; + key = c; + } +} + + +/* -------------------------------------------------------------------------- */ + + +void geChannelButton::draw() +{ + gClick::draw(); + + if (key == "") + return; + + /* draw background */ + + fl_rectf(x()+1, y()+1, 18, h()-2, bgColor0); + + /* draw key */ + + fl_color(COLOR_TEXT_0); + fl_font(FL_HELVETICA, 11); + fl_draw(key.c_str(), x(), y(), 18, h(), FL_ALIGN_CENTER); +} + + +/* -------------------------------------------------------------------------- */ + + +void geChannelButton::setInputRecordMode() +{ + bgColor0 = COLOR_BG_3; +} + + +/* -------------------------------------------------------------------------- */ + + +void geChannelButton::setActionRecordMode() +{ + bgColor0 = COLOR_BG_4; + txtColor = COLOR_TEXT_0; +} + + +/* -------------------------------------------------------------------------- */ + + +void geChannelButton::setDefaultMode(const char *l) +{ + bgColor0 = COLOR_BG_0; + bdColor = COLOR_BD_0; + txtColor = COLOR_TEXT_0; + if (l) + label(l); +} + + +/* -------------------------------------------------------------------------- */ + + +void geChannelButton::setPlayMode() +{ + bgColor0 = COLOR_BG_2; + bdColor = COLOR_BD_1; + txtColor = COLOR_TEXT_1; +} + + +/* -------------------------------------------------------------------------- */ + + +void geChannelButton::setEndingMode() +{ + bgColor0 = COLOR_BD_0; +} diff --git a/src/gui/elems/mainWindow/keyboard/channelButton.h b/src/gui/elems/mainWindow/keyboard/channelButton.h new file mode 100644 index 0000000..5163983 --- /dev/null +++ b/src/gui/elems/mainWindow/keyboard/channelButton.h @@ -0,0 +1,60 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ge_channelButton + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#ifndef GE_CHANNEL_BUTTON_H +#define GE_CHANNEL_BUTTON_H + + +#include "../../ge_mixed.h" + + +class geChannelButton : public gClick +{ +private: + + std::string key; + +public: + + geChannelButton(int x, int y, int w, int h, const char *l=0); + + virtual int handle(int e) = 0; + + void draw(); + void setKey(const std::string &k); + void setKey(int k); + void setPlayMode(); + void setEndingMode(); + void setDefaultMode(const char *l=0); + void setInputRecordMode(); + void setActionRecordMode(); +}; + + +#endif diff --git a/src/gui/elems/mainWindow/keyboard/channelMode.cpp b/src/gui/elems/mainWindow/keyboard/channelMode.cpp new file mode 100644 index 0000000..5babd63 --- /dev/null +++ b/src/gui/elems/mainWindow/keyboard/channelMode.cpp @@ -0,0 +1,111 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ge_modeBox + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include +#include "../../../../utils/gui.h" +#include "../../../../core/graphics.h" +#include "../../../../core/sampleChannel.h" +#include "../../../../core/const.h" +#include "../../basics/boxtypes.h" +#include "channelMode.h" + + +geChannelMode::geChannelMode(int x, int y, int w, int h, SampleChannel *ch, + const char *L) + : Fl_Menu_Button(x, y, w, h, L), ch(ch) +{ + box(G_CUSTOM_BORDER_BOX); + textsize(GUI_FONT_SIZE_BASE); + textcolor(COLOR_TEXT_0); + color(COLOR_BG_0); + + add("Loop . basic", 0, cb_changeMode, (void *)LOOP_BASIC); + add("Loop . once", 0, cb_changeMode, (void *)LOOP_ONCE); + add("Loop . once . bar", 0, cb_changeMode, (void *)LOOP_ONCE_BAR); + add("Loop . repeat", 0, cb_changeMode, (void *)LOOP_REPEAT); + add("Oneshot . basic", 0, cb_changeMode, (void *)SINGLE_BASIC); + add("Oneshot . press", 0, cb_changeMode, (void *)SINGLE_PRESS); + add("Oneshot . retrig", 0, cb_changeMode, (void *)SINGLE_RETRIG); + add("Oneshot . endless", 0, cb_changeMode, (void *)SINGLE_ENDLESS); +} + + +/* -------------------------------------------------------------------------- */ + + +void geChannelMode::draw() { + fl_rect(x(), y(), w(), h(), 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 geChannelMode::cb_changeMode(Fl_Widget *v, void *p) { ((geChannelMode*)v)->__cb_changeMode((intptr_t)p); } + + +/* -------------------------------------------------------------------------- */ + + +void geChannelMode::__cb_changeMode(int mode) +{ + ch->mode = mode; + + /* what to do when the channel is playing and you change the mode? + * Nothing, since v0.5.3. Just refresh the action editor window, in + * case it's open */ + + gu_refreshActionEditor(); +} diff --git a/src/gui/elems/mainWindow/keyboard/channelMode.h b/src/gui/elems/mainWindow/keyboard/channelMode.h new file mode 100644 index 0000000..0829be9 --- /dev/null +++ b/src/gui/elems/mainWindow/keyboard/channelMode.h @@ -0,0 +1,54 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ge_modeBox + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#ifndef GE_CHANNEL_MODE_H +#define GE_CHANNEL_MODE_H + + +#include + + +class geChannelMode : public Fl_Menu_Button +{ +private: + + static void cb_changeMode (Fl_Widget *v, void *p); + inline void __cb_changeMode(int mode); + + class SampleChannel *ch; + +public: + + geChannelMode(int x, int y, int w, int h, class SampleChannel *ch, + const char *l=0); + void draw(); +}; + + +#endif diff --git a/src/gui/elems/mainWindow/keyboard/channelStatus.cpp b/src/gui/elems/mainWindow/keyboard/channelStatus.cpp new file mode 100644 index 0000000..bc89239 --- /dev/null +++ b/src/gui/elems/mainWindow/keyboard/channelStatus.cpp @@ -0,0 +1,84 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ge_status + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include +#include "../../../../core/mixer.h" +#include "../../../../core/sampleChannel.h" +#include "../../../../core/recorder.h" +#include "../../../../core/const.h" +#include "channelStatus.h" + + +extern Mixer G_Mixer; +extern Recorder G_Recorder; + + +geChannelStatus::geChannelStatus(int x, int y, int w, int h, SampleChannel *ch, + const char *L) + : Fl_Box(x, y, w, h, L), ch(ch) {} + + +/* -------------------------------------------------------------------------- */ + + +void geChannelStatus::draw() +{ + fl_rect(x(), y(), w(), h(), 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.recording && ch->armed) + fl_rectf(x()+1, y()+1, w()-2, h()-2, COLOR_BG_3); // take in progress + else + if (G_Recorder.active && G_Recorder.canRec(ch, &G_Mixer)) + 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); + } +} diff --git a/src/gui/elems/mainWindow/keyboard/channelStatus.h b/src/gui/elems/mainWindow/keyboard/channelStatus.h new file mode 100644 index 0000000..6e911cb --- /dev/null +++ b/src/gui/elems/mainWindow/keyboard/channelStatus.h @@ -0,0 +1,47 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ge_status + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#ifndef GE_CHANNEL_STATUS_H +#define GE_CHANNEL_STATUS_H + + +#include + + +class geChannelStatus : public Fl_Box +{ +public: + geChannelStatus(int X, int Y, int W, int H, class SampleChannel *ch, + const char *L=0); + void draw(); + class SampleChannel *ch; +}; + + +#endif diff --git a/src/gui/elems/mainWindow/keyboard/column.cpp b/src/gui/elems/mainWindow/keyboard/column.cpp new file mode 100644 index 0000000..6518aed --- /dev/null +++ b/src/gui/elems/mainWindow/keyboard/column.cpp @@ -0,0 +1,293 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ge_column + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include "../../../../core/sampleChannel.h" +#include "../../../../glue/channel.h" +#include "../../../../utils/log.h" +#include "../../../../utils/string.h" +#include "../../../dialogs/gd_warnings.h" +#include "../../../elems/basics/boxtypes.h" +#include "keyboard.h" +#include "sampleChannel.h" +#include "midiChannel.h" +#include "column.h" + + +geColumn::geColumn(int X, int Y, int W, int H, int index, geKeyboard *parent) + : Fl_Group(X, Y, W, H), parent(parent), index(index) +{ + /* geColumn does a bit of a mess: we pass a pointer to its parent (geKeyboard) and + the geColumn itself deals with the creation of another widget, outside geColumn + and inside geKeyboard, which handles the vertical resize bar (gResizerBar). + The resizer cannot stay inside geColumn: it needs a broader view on the other + side widgets. The view can be obtained from geKeyboard only (the upper level). + Unfortunately, parent() can be NULL: at this point (i.e the constructor) + geColumn is still detached from any parent. We use a custom geKeyboard *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(MIN_COLUMN_WIDTH); + parent->add(resizer); + + addChannelBtn->callback(cb_addChannel, (void*)this); +} + + +/* -------------------------------------------------------------------------- */ + + +geColumn::~geColumn() +{ + /* 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 geColumn::handle(int e) +{ + switch (e) { + case FL_RELEASE: { + if (Fl::event_button() == FL_RIGHT_MOUSE) { + __cb_addChannel(); + return 1; + } + } + case FL_DND_ENTER: // return(1) for these events to 'accept' dnd + case FL_DND_DRAG: + case FL_DND_RELEASE: { + return 1; + } + case FL_PASTE: { // handle actual drop (paste) operation + vector paths; + gu_split(Fl::event_text(), "\n", &paths); + bool fails = false; + int result = 0; + for (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 geColumn::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 geColumn::refreshChannels() +{ + for (int i=1; irefresh(); +} + + +/* -------------------------------------------------------------------------- */ + + +void geColumn::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 geColumn::cb_addChannel(Fl_Widget *v, void *p) { ((geColumn*)p)->__cb_addChannel(); } + + +/* -------------------------------------------------------------------------- */ + + +geChannel *geColumn::addChannel(Channel *ch) +{ + int currentY = y() + children() * 24; + geChannel *gch = NULL; + if (ch->type == CHANNEL_SAMPLE) + gch = (geSampleChannel*) new geSampleChannel(x(), currentY, w(), 20, (SampleChannel*) ch); + else + gch = (geMidiChannel*) new geMidiChannel(x(), currentY, w(), 20, (MidiChannel*) ch); + + 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 geColumn::deleteChannel(geChannel *gch) +{ + gch->hide(); + remove(gch); + delete gch; + + /* reposition all other channels and resize this group */ + /** TODO + * reposition is useless when called by geColumn::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 geColumn::__cb_addChannel() +{ + gu_log("[geColumn::__cb_addChannel] index = %d\n", index); + int type = openTypeMenu(); + if (type) + glue_addChannel(index, type); +} + + +/* -------------------------------------------------------------------------- */ + + +int geColumn::openTypeMenu() +{ + Fl_Menu_Item rclick_menu[] = { + {"Sample channel"}, + {"MIDI channel"}, + {0} + }; + + Fl_Menu_Button *b = new Fl_Menu_Button(0, 0, 100, 50); + b->box(G_CUSTOM_BORDER_BOX); + b->textsize(GUI_FONT_SIZE_BASE); + 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 geColumn::clear(bool full) +{ + if (full) + Fl_Group::clear(); + else { + while (children() >= 2) { // skip "add new channel" btn + int i = children()-1; + deleteChannel((geChannel*)child(i)); + } + } +} + + +/* -------------------------------------------------------------------------- */ + + +Channel *geColumn::getChannel(int i) +{ + geChannel *gch = (geChannel*) child(i); + if (gch->type == CHANNEL_SAMPLE) + return ((geSampleChannel*) child(i))->ch; + else + return ((geMidiChannel*) child(i))->ch; +} diff --git a/src/gui/elems/mainWindow/keyboard/column.h b/src/gui/elems/mainWindow/keyboard/column.h new file mode 100644 index 0000000..1d78540 --- /dev/null +++ b/src/gui/elems/mainWindow/keyboard/column.h @@ -0,0 +1,102 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ge_column + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#ifndef GE_COLUMN_H +#define GE_COLUMN_H + + +#include + + +class geColumn : 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 geKeyboard *parent; + + int index; + +public: + + geColumn(int x, int y, int w, int h, int index, class geKeyboard *parent); + ~geColumn(); + + /* addChannel + * add a new channel in this column and set the internal pointer + * to channel to 'ch'. */ + + class geChannel *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(geChannel *gch); + + /* refreshChannels + * update channels' graphical statues. Called on each GUI cycle. */ + + void refreshChannels(); + + /* getChannel */ + + Channel *getChannel(int i); + + /* 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; } + inline int countChannels() { return children(); } +}; + + +#endif diff --git a/src/gui/elems/mainWindow/keyboard/keyboard.cpp b/src/gui/elems/mainWindow/keyboard/keyboard.cpp new file mode 100644 index 0000000..00d2bef --- /dev/null +++ b/src/gui/elems/mainWindow/keyboard/keyboard.cpp @@ -0,0 +1,405 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * gg_keyboard + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include "../../../../core/sampleChannel.h" +#include "../../../../glue/main.h" +#include "../../../../glue/io.h" +#include "../../../../utils/log.h" +#include "../../../dialogs/gd_warnings.h" +#include "../../basics/boxtypes.h" +#include "column.h" +#include "sampleChannel.h" +#include "keyboard.h" + + +int geKeyboard::indexColumn = 0; + + +/* -------------------------------------------------------------------------- */ + + +geKeyboard::geKeyboard(int X, int Y, int W, int H) +: Fl_Scroll (X, Y, W, H), + bckspcPressed(false), + endPressed (false), + spacePressed (false), + addColumnBtn (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_CUSTOM_BORDER_BOX); + hscrollbar.color(COLOR_BG_0); + hscrollbar.selection_color(COLOR_BG_1); + hscrollbar.labelcolor(COLOR_BD_1); + hscrollbar.slider(G_CUSTOM_BORDER_BOX); + + addColumnBtn = new gClick(8, y(), 200, 20, "Add new column"); + addColumnBtn->callback(cb_addColumn, (void*) this); + add(addColumnBtn); + + init(); +} + + +/* -------------------------------------------------------------------------- */ + + +void geKeyboard::init() +{ + /* add 6 empty columns as init layout */ + + __cb_addColumn(); + __cb_addColumn(); + __cb_addColumn(); + __cb_addColumn(); + __cb_addColumn(); + __cb_addColumn(); +} + + +/* -------------------------------------------------------------------------- */ + + +void geKeyboard::freeChannel(geChannel *gch) +{ + gch->reset(); +} + + +/* -------------------------------------------------------------------------- */ + + +void geKeyboard::deleteChannel(geChannel *gch) +{ + for (unsigned i=0; ifind(gch); + if (k != columns.at(i)->children()) { + columns.at(i)->deleteChannel(gch); + return; + } + } +} + + +/* -------------------------------------------------------------------------- */ + + +void geKeyboard::updateChannel(geChannel *gch) +{ + gch->update(); +} + + +/* -------------------------------------------------------------------------- */ + + +void geKeyboard::organizeColumns() +{ + /* if 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.erase(columns.begin() + 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.back()->x() + columns.back()->w() + 16, y()); + + /* recompute col indexes */ + + refreshColIndexes(); + + redraw(); +} + + +/* -------------------------------------------------------------------------- */ + + +void geKeyboard::cb_addColumn(Fl_Widget *v, void *p) +{ + ((geKeyboard*)p)->__cb_addColumn(DEFAULT_COLUMN_WIDTH); +} + + +/* -------------------------------------------------------------------------- */ + + +geChannel *geKeyboard::addChannel(int colIndex, Channel *ch, bool build) +{ + geColumn *col = getColumnByIndex(colIndex); + + /* no column with index 'colIndex' found? Just create it and set its index + to 'colIndex'. */ + + if (!col) { + __cb_addColumn(); + col = columns.back(); + col->setIndex(colIndex); + gu_log("[geKeyboard::addChannel] created new column with index=%d\n", colIndex); + } + + gu_log("[geKeyboard::addChannel] add to column with index = %d\n", col->getIndex()); + return col->addChannel(ch); +} + + +/* -------------------------------------------------------------------------- */ + + +void geKeyboard::refreshColumns() +{ + for (unsigned i=0; irefreshChannels(); +} + + +/* -------------------------------------------------------------------------- */ + + +geColumn *geKeyboard::getColumnByIndex(int index) +{ + for (unsigned i=0; igetIndex() == index) + return columns.at(i); + return NULL; +} + + +/* -------------------------------------------------------------------------- */ + +/* TODO - the following event handling for play, stop, rewind, start rec and +so on should be moved to the proper widget: gdMainWindow or (better) geController. */ + +int geKeyboard::handle(int e) +{ + int ret = Fl_Group::handle(e); // assume the buttons won't handle the Keyboard events + switch (e) { + case FL_FOCUS: + case FL_UNFOCUS: { + ret = 1; // enables receiving Keyboard events + break; + } + case FL_SHORTCUT: // in case widget that isn't ours has focus + case FL_KEYDOWN: // Keyboard key pushed + case FL_KEYUP: { // Keyboard key released + + /* rewind session. Avoid retrigs */ + + if (e == FL_KEYDOWN) { + if (Fl::event_key() == FL_BackSpace && !bckspcPressed) { + bckspcPressed = true; + glue_rewindSeq(); + 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(false); // update gui + ret = 1; + break; + } + else if (Fl::event_key() == ' ' && !spacePressed) { + spacePressed = true; + glue_startStopSeq(false); // update gui + ret = 1; + break; + } + } + else if (e == FL_KEYUP) { + if (Fl::event_key() == FL_BackSpace) + bckspcPressed = false; + else if (Fl::event_key() == FL_End) + endPressed = false; + else if (Fl::event_key() == ' ') + spacePressed = false; + else if (Fl::event_key() == FL_Enter) + enterPressed = false; + } + + /* Walk button arrays, trying to match button's label with the Keyboard event. + * If found, set that button's value() based on up/down event, + * and invoke that button's callback() */ + + for (unsigned i=0; ichildren(); k++) + ret &= ((geChannel*)columns.at(i)->child(k))->keyPress(e); + break; + } + } + return ret; +} + + +/* -------------------------------------------------------------------------- */ + + +void geKeyboard::clear() +{ + for (unsigned i=0; iposition(8, y()); +} + + +/* -------------------------------------------------------------------------- */ + + +void geKeyboard::setChannelWithActions(geSampleChannel *gch) +{ + if (gch->ch->hasActions) + gch->showActionButton(); + else + gch->hideActionButton(); +} + + +/* -------------------------------------------------------------------------- */ + + +void geKeyboard::printChannelMessage(int res) +{ + if (res == 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 geKeyboard::__cb_addColumn(int width) +{ + int colx; + int colxw; + int gap = 16; + if (columns.size() == 0) { + colx = x() - xposition(); // mind the offset with xposition() + colxw = colx + width; + } + else { + geColumn *prev = columns.back(); + colx = prev->x()+prev->w() + gap; + colxw = colx + width; + } + + /* add geColumn to geKeyboard and to columns vector */ + + geColumn *gc = new geColumn(colx, y(), width, 2000, indexColumn, this); + add(gc); + columns.push_back(gc); + indexColumn++; + + /* move addColumn button */ + + addColumnBtn->position(colxw + gap, y()); + redraw(); + + gu_log("[geKeyboard::__cb_addColumn] new column added (index=%d, w=%d), total count=%d, addColumn(x)=%d\n", + gc->getIndex(), width, columns.size(), addColumnBtn->x()); + + /* recompute col indexes */ + + refreshColIndexes(); +} + + +/* -------------------------------------------------------------------------- */ + + +void geKeyboard::addColumn(int width) +{ + __cb_addColumn(width); +} + + +/* -------------------------------------------------------------------------- */ + + +void geKeyboard::refreshColIndexes() +{ + for (unsigned i=0; isetIndex(i); +} + + +/* -------------------------------------------------------------------------- */ + + +int geKeyboard::getColumnWidth(int i) +{ + return getColumnByIndex(i)->w(); +} + + +/* -------------------------------------------------------------------------- */ + + +geColumn *geKeyboard::getColumn(int i) +{ + return columns.at(i); +} diff --git a/src/gui/elems/mainWindow/keyboard/keyboard.h b/src/gui/elems/mainWindow/keyboard/keyboard.h new file mode 100644 index 0000000..8778c2a --- /dev/null +++ b/src/gui/elems/mainWindow/keyboard/keyboard.h @@ -0,0 +1,158 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * gg_keyboard + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#ifndef GE_KEYBOARD_H +#define GE_KEYBOARD_H + + +#include +#include +#include "../../../../core/const.h" + + +class geKeyboard : public Fl_Scroll +{ +private: + + /* refreshColIndexes + * Recompute all column indexes in order to avoid any gaps between them. + * Indexes must always be contiguous! */ + + void refreshColIndexes(); + + static void cb_addColumn (Fl_Widget *v, void *p); + inline void __cb_addColumn(int width=DEFAULT_COLUMN_WIDTH); + + 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. */ + + std::vector columns; + +public: + + geKeyboard(int X, int Y, int W, int H); + + int handle(int e); + + /* init + * build the initial setup of empty channels. */ + + void init(); + + /* addChannel + * add a new channel to geChannels. Used by callbacks and during + * patch loading. Requires Channel (and not geChannel). If build is + * set to true, also generate the corresponding column if column (index) does + * not exist yet. */ + + class geChannel *addChannel(int column, class Channel *ch, bool build=false); + + /* addColumn + * add a new column to the top of the stack. */ + + void addColumn(int width=380); + + /* deleteChannel + * delete a channel from geChannels<> where geChannel->ch == ch and remove + * it from the stack. */ + + void deleteChannel(geChannel *gch); + + /* freeChannel + * free a channel from geChannels<> where geChannel->ch == ch. No channels + * are deleted */ + + void freeChannel(geChannel *gch); + + /* updateChannel + * wrapper function to call gch->update(). */ + + void updateChannel(geChannel *gch); + + /* organizeColumns + * reorganize columns layout by removing empty gaps. */ + + void organizeColumns(); + + /* refreshColumns + * refresh each column's channel, called on each GUI cycle. */ + + void refreshColumns(); + + /* getColumnByIndex + * return the column with index 'index', or NULL if not found. */ + + geColumn *getColumnByIndex(int index); + + /* getColumn + * return the column with from columns->at(i). */ + + geColumn *getColumn(int i); + + /* clear + * delete all channels and groups. */ + + void clear(); + + /* setChannelWithActions + * add 'R' button if channel has actions, and set recorder to active. */ + + void setChannelWithActions(class geSampleChannel *gch); + + /* printChannelMessage + * given any output by glue_loadChannel, print the message on screen + * on a gdAlert subwindow. */ + + void printChannelMessage(int res); + + /* getTotalColumns */ + + unsigned getTotalColumns() { return columns.size(); } + + /* getColumnWidth + * return the width in pixel of i-th column. Warning: 'i' is the i-th column + * in the column array, NOT the index. */ + + int getColumnWidth(int i); +}; + + +#endif diff --git a/src/gui/elems/mainWindow/keyboard/midiChannel.cpp b/src/gui/elems/mainWindow/keyboard/midiChannel.cpp new file mode 100644 index 0000000..183bc71 --- /dev/null +++ b/src/gui/elems/mainWindow/keyboard/midiChannel.cpp @@ -0,0 +1,284 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ge_midiChannel + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include "../../../../core/const.h" +#include "../../../../core/graphics.h" +#include "../../../../core/midiChannel.h" +#include "../../../../glue/channel.h" +#include "../../../../glue/io.h" +#include "../../../dialogs/gd_mainWindow.h" +#include "../../../dialogs/gd_editor.h" +#include "../../../dialogs/gd_actionEditor.h" +#include "../../../dialogs/gd_warnings.h" +#include "../../../dialogs/gd_browser.h" +#include "../../../dialogs/gd_keyGrabber.h" +#include "../../../dialogs/gd_pluginList.h" +#include "../../../dialogs/midiIO/midiInputChannel.h" +#include "../../../dialogs/midiIO/midiOutputMidiCh.h" +#include "../../basics/boxtypes.h" +#include "midiChannel.h" + + +extern Recorder G_Recorder; +extern gdMainWindow *G_MainWin; + + +geMidiChannel::geMidiChannel(int X, int Y, int W, int H, MidiChannel *ch) + : geChannel(X, Y, W, H, CHANNEL_MIDI, ch) +{ + begin(); + +#if defined(WITH_VST) + int delta = 144; // (6 widgets * 20) + (6 paddings * 4) +#else + int delta = 120; // (5 widgets * 20) + (5 paddings * 4) +#endif + + button = new gButton(x(), y(), 20, 20, "", channelStop_xpm, channelPlay_xpm); + arm = new gClick(button->x()+button->w()+4, y(), 20, 20, "", armOff_xpm, armOn_xpm); + mainButton = new geMidiChannelButton(arm->x()+arm->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 + + arm->type(FL_TOGGLE_BUTTON); + arm->callback(cb_arm, (void*)this); + +#ifdef WITH_VST + fx->callback(cb_openFxWindow, (void*)this); +#endif + + mute->type(FL_TOGGLE_BUTTON); + mute->callback(cb_mute, (void*)this); + + solo->type(FL_TOGGLE_BUTTON); + solo->callback(cb_solo, (void*)this); + + mainButton->callback(cb_openMenu, (void*)this); + + vol->callback(cb_changeVol, (void*)this); + + ch->guiChannel = this; +} + + +/* -------------------------------------------------------------------------- */ + + +void geMidiChannel::cb_button (Fl_Widget *v, void *p) { ((geMidiChannel*)p)->__cb_button(); } +void geMidiChannel::cb_openMenu (Fl_Widget *v, void *p) { ((geMidiChannel*)p)->__cb_openMenu(); } + + +/* -------------------------------------------------------------------------- */ + + +void geMidiChannel::__cb_button() +{ + if (button->value()) + glue_keyPress(ch, Fl::event_ctrl(), Fl::event_shift()); +} + + +/* -------------------------------------------------------------------------- */ + + +void geMidiChannel::__cb_openMenu() +{ + Fl_Menu_Item rclick_menu[] = { + {"Edit actions..."}, // 0 + {"Clear actions", 0, 0, 0, FL_SUBMENU}, // 1 + {"All"}, // 2 + {0}, // 3 + {"Setup keyboard input..."}, // 5 + {"Setup MIDI input..."}, // 6 + {"Setup MIDI output..."}, // 7 + {"Clone channel"}, // 8 + {"Delete channel"}, // 9 + {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_CUSTOM_BORDER_BOX); + b->textsize(GUI_FONT_SIZE_BASE); + 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(), "Clone channel") == 0) { + glue_cloneChannel(ch); + return; + } + + if (strcmp(m->label(), "Setup keyboard input...") == 0) { + gu_openSubWindow(G_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; + G_Recorder.clearChan(ch->index); + ch->hasActions = false; + gu_refreshActionEditor(); // refresh a.editor window, it could be open + return; + } + + if (strcmp(m->label(), "Edit actions...") == 0) { + gu_openSubWindow(G_MainWin, new gdActionEditor(ch), WID_ACTION_EDITOR); + return; + } + + if (strcmp(m->label(), "Setup MIDI input...") == 0) { + gu_openSubWindow(G_MainWin, new gdMidiInputChannel(ch), 0); + return; + } + + if (strcmp(m->label(), "Setup MIDI output...") == 0) { + //gu_openSubWindow(G_MainWin, new gdMidiGrabberChannel(ch, GrabForOutput), 0); + gu_openSubWindow(G_MainWin, new gdMidiOutputMidiCh((MidiChannel*) ch), 0); + return; + } +} + + +/* -------------------------------------------------------------------------- */ + + +void geMidiChannel::refresh() +{ + setColorsByStatus(ch->status, ch->recStatus); + mainButton->redraw(); +} + + +/* -------------------------------------------------------------------------- */ + + +void geMidiChannel::reset() +{ + mainButton->setDefaultMode("-- MIDI --"); + mainButton->redraw(); +} + + +/* -------------------------------------------------------------------------- */ + + +void geMidiChannel::update() +{ + if (((MidiChannel*) ch)->midiOut) { + char tmp[32]; + sprintf(tmp, "-- MIDI (channel %d) --", ((MidiChannel*) ch)->midiOutChan+1); + mainButton->copy_label(tmp); + } + else + mainButton->label("-- MIDI --"); + + vol->value(ch->volume); + mute->value(ch->mute); + solo->value(ch->solo); + + mainButton->setKey(ch->key); + +#ifdef WITH_VST + fx->full = ch->plugins.size() > 0; + fx->redraw(); +#endif +} + + +/* -------------------------------------------------------------------------- */ + + +void geMidiChannel::resize(int X, int Y, int W, int H) +{ + geChannel::resize(X, Y, W, H); + + arm->hide(); +#ifdef WITH_VST + fx->hide(); +#endif + + if (w() > BREAK_ARM) + arm->show(); +#ifdef WITH_VST + if (w() > BREAK_FX) + fx->show(); +#endif + + packWidgets(); +} + + +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ + + +geMidiChannelButton::geMidiChannelButton(int x, int y, int w, int h, const char *l) + : geChannelButton(x, y, w, h, l) {} + + +/* -------------------------------------------------------------------------- */ + + +int geMidiChannelButton::handle(int e) +{ + // MIDI drag-n-drop does nothing so far. + return gClick::handle(e); +} diff --git a/src/gui/elems/mainWindow/keyboard/midiChannel.h b/src/gui/elems/mainWindow/keyboard/midiChannel.h new file mode 100644 index 0000000..cd06ddc --- /dev/null +++ b/src/gui/elems/mainWindow/keyboard/midiChannel.h @@ -0,0 +1,72 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ge_midiChannel + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#ifndef GE_MIDI_CHANNEL_H +#define GE_MIDI_CHANNEL_H + + +#include "channel.h" +#include "channelButton.h" + + +class geMidiChannel : public geChannel +{ +private: + + static void cb_button (Fl_Widget *v, void *p); + static void cb_openMenu (Fl_Widget *v, void *p); + + inline void __cb_button (); + inline void __cb_openMenu (); + inline void __cb_readActions (); + +public: + + geMidiChannel(int x, int y, int w, int h, class MidiChannel *ch); + + void reset (); + void update (); + void refresh (); + int keyPress(int event); // TODO - move to base class + void resize (int x, int y, int w, int h); +}; + + +/* -------------------------------------------------------------------------- */ + + +class geMidiChannelButton : public geChannelButton +{ +public: + geMidiChannelButton(int x, int y, int w, int h, const char *l=0); + int handle(int e); +}; + + +#endif diff --git a/src/gui/elems/mainWindow/keyboard/sampleChannel.cpp b/src/gui/elems/mainWindow/keyboard/sampleChannel.cpp new file mode 100644 index 0000000..fa3289c --- /dev/null +++ b/src/gui/elems/mainWindow/keyboard/sampleChannel.cpp @@ -0,0 +1,499 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ge_sampleChannel + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include "../../../../core/mixer.h" +#include "../../../../core/conf.h" +#include "../../../../core/recorder.h" +#include "../../../../core/graphics.h" +#include "../../../../core/wave.h" +#include "../../../../core/sampleChannel.h" +#include "../../../../glue/main.h" +#include "../../../../glue/io.h" +#include "../../../../glue/channel.h" +#include "../../../../glue/storage.h" +#include "../../../../utils/string.h" +#include "../../../dialogs/gd_mainWindow.h" +#include "../../../dialogs/gd_keyGrabber.h" +#include "../../../dialogs/gd_editor.h" +#include "../../../dialogs/gd_actionEditor.h" +#include "../../../dialogs/gd_warnings.h" +#include "../../../dialogs/gd_browser.h" +#include "../../../dialogs/midiIO/midiOutputSampleCh.h" +#include "../../../dialogs/midiIO/midiInputChannel.h" +#include "../../basics/boxtypes.h" +#include "channelStatus.h" +#include "channelMode.h" +#include "keyboard.h" +#include "sampleChannel.h" + + +extern Mixer G_Mixer; +extern Conf G_Conf; +extern Recorder G_Recorder; +extern gdMainWindow *G_MainWin; + + +geSampleChannel::geSampleChannel(int X, int Y, int W, int H, SampleChannel *ch) + : geChannel(X, Y, W, H, CHANNEL_SAMPLE, (Channel*) ch) +{ + begin(); + + button = new gButton(x(), y(), 20, 20, "", channelStop_xpm, channelPlay_xpm); + arm = new gClick(button->x()+button->w()+4, y(), 20, 20, "", armOff_xpm, armOn_xpm); + status = new geChannelStatus(arm->x()+arm->w()+4, y(), 20, 20, ch); + mainButton = new geSampleChannelButton(status->x()+status->w()+4, y(), 20, 20, "-- no sample --"); + readActions = new gClick(mainButton->x()+mainButton->w()+4, y(), 20, 20, "", readActionOff_xpm, readActionOn_xpm); + modeBox = new geChannelMode(readActions->x()+readActions->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); +#ifdef 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 + + arm->type(FL_TOGGLE_BUTTON); + arm->callback(cb_arm, (void*)this); + +#ifdef WITH_VST + fx->callback(cb_openFxWindow, (void*)this); +#endif + + mute->type(FL_TOGGLE_BUTTON); + mute->callback(cb_mute, (void*)this); + + solo->type(FL_TOGGLE_BUTTON); + solo->callback(cb_solo, (void*)this); + + mainButton->callback(cb_openMenu, (void*)this); + + readActions->type(FL_TOGGLE_BUTTON); + readActions->value(ch->readActions); + readActions->callback(cb_readActions, (void*)this); + + vol->callback(cb_changeVol, (void*)this); + + ch->guiChannel = this; +} + + +/* -------------------------------------------------------------------------- */ + + +void geSampleChannel::cb_button (Fl_Widget *v, void *p) { ((geSampleChannel*)p)->__cb_button(); } +void geSampleChannel::cb_openMenu (Fl_Widget *v, void *p) { ((geSampleChannel*)p)->__cb_openMenu(); } +void geSampleChannel::cb_readActions (Fl_Widget *v, void *p) { ((geSampleChannel*)p)->__cb_readActions(); } + + +/* -------------------------------------------------------------------------- */ + + +void geSampleChannel::__cb_button() +{ + if (button->value()) // pushed + glue_keyPress(ch, Fl::event_ctrl(), Fl::event_shift()); + else // released + glue_keyRelease(ch, Fl::event_ctrl(), Fl::event_shift()); +} + + +/* -------------------------------------------------------------------------- */ + + +void geSampleChannel::__cb_openMenu() +{ + /* If you're recording (input or actions) no menu is allowed; you can't do + anything, especially deallocate the channel */ + + if (G_Mixer.recording || G_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 + {"Setup MIDI output..."}, // 4 + {"Edit sample..."}, // 5 + {"Edit actions..."}, // 6 + {"Clear actions", 0, 0, 0, FL_SUBMENU}, // 7 + {"All"}, // 8 + {"Mute"}, // 9 + {"Volume"}, // 10 + {"Start/Stop"}, // 11 + {0}, // 12 + {"Clone channel"}, // 13 + {"Free channel"}, // 14 + {"Delete channel"}, // 15 + {0} + }; + + if (ch->status & (STATUS_EMPTY | STATUS_MISSING)) { + rclick_menu[1].deactivate(); + rclick_menu[5].deactivate(); + rclick_menu[14].deactivate(); + } + + /* no 'clear actions' if there are no actions */ + + if (!ch->hasActions) + rclick_menu[7].deactivate(); + + /* no 'clear start/stop actions' for those channels in loop mode: + * they cannot have start/stop actions. */ + + if (((SampleChannel*)ch)->mode & LOOP_ANY) + rclick_menu[11].deactivate(); + + Fl_Menu_Button *b = new Fl_Menu_Button(0, 0, 100, 50); + b->box(G_CUSTOM_BORDER_BOX); + b->textsize(GUI_FONT_SIZE_BASE); + 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) { + gWindow *w = new gdLoadBrowser(G_Conf.browserX, G_Conf.browserY, + G_Conf.browserW, G_Conf.browserH, "Browse sample", + G_Conf.samplePath.c_str(), glue_loadSample, ch); + gu_openSubWindow(G_MainWin, w, WID_FILE_BROWSER); + 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(G_MainWin, new gdMidiInputChannel(ch), 0); + return; + } + + if (strcmp(m->label(), "Setup MIDI output...") == 0) { + gu_openSubWindow(G_MainWin, new gdMidiOutputSampleCh((SampleChannel*) ch), 0); + return; + } + + if (strcmp(m->label(), "Edit sample...") == 0) { + gu_openSubWindow(G_MainWin, new gdEditor((SampleChannel*) ch), WID_SAMPLE_EDITOR); /// FIXME title it's up to gdEditor + return; + } + + if (strcmp(m->label(), "Export sample to file...") == 0) { + gWindow *w = new gdSaveBrowser(G_Conf.browserX, G_Conf.browserY, + G_Conf.browserW, G_Conf.browserH, "Save sample", \ + G_Conf.samplePath.c_str(), "", glue_saveSample, ch); + gu_openSubWindow(G_MainWin, w, WID_FILE_BROWSER); + 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() */ + + G_MainWin->delSubWindow(WID_FILE_BROWSER); + G_MainWin->delSubWindow(WID_ACTION_EDITOR); + G_MainWin->delSubWindow(WID_SAMPLE_EDITOR); + G_MainWin->delSubWindow(WID_FX_LIST); + + return; + } + + if (strcmp(m->label(), "Clone channel") == 0) { + glue_cloneChannel(ch); + return; + } + + if (strcmp(m->label(), "Mute") == 0) { + if (!gdConfirmWin("Warning", "Clear all mute actions: are you sure?")) + return; + G_Recorder.clearAction(ch->index, ACTION_MUTEON | ACTION_MUTEOFF); + ch->hasActions = G_Recorder.hasActions(ch->index); + + if (!ch->hasActions) + hideActionButton(); + + /* 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; + G_Recorder.clearAction(ch->index, ACTION_KEYPRESS | ACTION_KEYREL | ACTION_KILLCHAN); + ch->hasActions = G_Recorder.hasActions(ch->index); + + if (!ch->hasActions) + hideActionButton(); + 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; + G_Recorder.clearAction(ch->index, ACTION_VOLUME); + ch->hasActions = G_Recorder.hasActions(ch->index); + if (!ch->hasActions) + hideActionButton(); + 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; + G_Recorder.clearChan(ch->index); + ch->hasActions = false; + hideActionButton(); + gu_refreshActionEditor(); // refresh a.editor window, it could be open + return; + } + + if (strcmp(m->label(), "Edit actions...") == 0) { + gu_openSubWindow(G_MainWin, new gdActionEditor(ch), WID_ACTION_EDITOR); + return; + } +} + + +/* -------------------------------------------------------------------------- */ + + +void geSampleChannel::__cb_readActions() +{ + glue_startStopReadingRecs((SampleChannel*) ch); +} + + +/* -------------------------------------------------------------------------- */ + + +void geSampleChannel::refresh() +{ + if (!mainButton->visible()) // mainButton invisible? status too (see below) + return; + + setColorsByStatus(ch->status, ch->recStatus); + + if (((SampleChannel*) ch)->wave != NULL) { + if (G_Mixer.recording && ch->armed) + mainButton->setInputRecordMode(); + if (G_Recorder.active) { + if (G_Recorder.canRec(ch, &G_Mixer)) + mainButton->setActionRecordMode(); + } + status->redraw(); // status invisible? sampleButton too (see below) + } + mainButton->redraw(); +} + + +/* -------------------------------------------------------------------------- */ + + +void geSampleChannel::reset() +{ + hideActionButton(); + mainButton->setDefaultMode("-- no sample --"); + mainButton->redraw(); + status->redraw(); +} + + +/* -------------------------------------------------------------------------- */ + + +void geSampleChannel::update() +{ + /* 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(((SampleChannel*) 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) + showActionButton(); + else + hideActionButton(); + + /* updates modebox */ + + modeBox->value(((SampleChannel*) ch)->mode); + modeBox->redraw(); + + /* update volumes+mute+solo */ + + vol->value(ch->volume); + mute->value(ch->mute); + solo->value(ch->solo); + + mainButton->setKey(ch->key); + +#ifdef WITH_VST + fx->full = ch->plugins.size() > 0; + fx->redraw(); +#endif +} + + +/* -------------------------------------------------------------------------- */ + + +void geSampleChannel::showActionButton() +{ + readActions->value(((SampleChannel*) ch)->readActions); + readActions->show(); + packWidgets(); + redraw(); +} + + +/* -------------------------------------------------------------------------- */ + + +void geSampleChannel::hideActionButton() +{ + readActions->hide(); + packWidgets(); + redraw(); +} + + +/* -------------------------------------------------------------------------- */ + + +void geSampleChannel::resize(int X, int Y, int W, int H) +{ + geChannel::resize(X, Y, W, H); + + arm->hide(); + modeBox->hide(); + readActions->hide(); +#ifdef WITH_VST + fx->hide(); +#endif + + if (w() > BREAK_ARM) + arm->show(); +#ifdef WITH_VST + if (w() > BREAK_FX) + fx->show(); +#endif + if (w() > BREAK_MODE_BOX) + modeBox->show(); + if (w() > BREAK_READ_ACTIONS && ch->hasActions) + readActions->show(); + + packWidgets(); +} + + +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ + + +geSampleChannelButton::geSampleChannelButton(int x, int y, int w, int h, const char *l) + : geChannelButton(x, y, w, h, l) {} + + +/* -------------------------------------------------------------------------- */ + + +int geSampleChannelButton::handle(int e) +{ + int ret = gClick::handle(e); + switch (e) { + case FL_DND_ENTER: + case FL_DND_DRAG: + case FL_DND_RELEASE: { + ret = 1; + break; + } + case FL_PASTE: { + geSampleChannel *gch = (geSampleChannel*) parent(); // parent is geSampleChannel + SampleChannel *ch = (SampleChannel*) gch->ch; + int result = glue_loadChannel(ch, gu_trim(gu_stripFileUrl(Fl::event_text())).c_str()); + if (result != SAMPLE_LOADED_OK) + G_MainWin->keyboard->printChannelMessage(result); + ret = 1; + break; + } + } + return ret; +} diff --git a/src/gui/elems/mainWindow/keyboard/sampleChannel.h b/src/gui/elems/mainWindow/keyboard/sampleChannel.h new file mode 100644 index 0000000..5c52151 --- /dev/null +++ b/src/gui/elems/mainWindow/keyboard/sampleChannel.h @@ -0,0 +1,81 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ge_sampleChannel + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#ifndef GE_SAMPLE_CHANNEL_H +#define GE_SAMPLE_CHANNEL_H + + +#include "channel.h" +#include "channelButton.h" + + +class geSampleChannel : public geChannel +{ +private: + + static void cb_button (Fl_Widget *v, void *p); + static void cb_openMenu (Fl_Widget *v, void *p); + static void cb_readActions (Fl_Widget *v, void *p); + + inline void __cb_button (); + inline void __cb_openMenu (); + inline void __cb_readActions (); + +public: + + geSampleChannel(int x, int y, int w, int h, class SampleChannel *ch); + + void reset (); + void update (); + void refresh (); + void resize (int x, int y, int w, int h); + + /* show/hideActionButton + Adds or removes 'R' button when actions are available. */ + + void showActionButton(); + void hideActionButton(); + + class geChannelMode *modeBox; + class gClick *readActions; +}; + + +/* -------------------------------------------------------------------------- */ + + +class geSampleChannelButton : public geChannelButton +{ +public: + geSampleChannelButton(int x, int y, int w, int h, const char *l=0); + int handle(int e); +}; + + +#endif diff --git a/src/gui/elems/mainWindow/mainIO.cpp b/src/gui/elems/mainWindow/mainIO.cpp new file mode 100644 index 0000000..7e61889 --- /dev/null +++ b/src/gui/elems/mainWindow/mainIO.cpp @@ -0,0 +1,180 @@ +/* ----------------------------------------------------------------------------- +* +* Giada - Your Hardcore Loopmachine +* +* mainIO +* +* ----------------------------------------------------------------------------- +* +* Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual +* +* This file is part of Giada - Your Hardcore Loopmachine. +* +* Giada - Your Hardcore Loopmachine is free software: you can +* redistribute it and/or modify it under the terms of the GNU General +* Public License as published by the Free Software Foundation, either +* version 3 of the License, or (at your option) any later version. +* +* Giada - Your Hardcore Loopmachine is distributed in the hope that it +* will be useful, but WITHOUT ANY WARRANTY; without even the implied +* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +* See the GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with Giada - Your Hardcore Loopmachine. If not, see +* . +* +* -------------------------------------------------------------------------- */ + + +#include "../../../core/const.h" +#include "../../../core/graphics.h" +#include "../../../core/mixer.h" +#include "../../../core/pluginHost.h" +#include "../../../glue/main.h" +#include "../../../utils/gui.h" +#include "../../elems/ge_mixed.h" +#include "../../dialogs/gd_mainWindow.h" +#include "../../dialogs/gd_pluginList.h" +#include "mainIO.h" + + +extern Mixer G_Mixer; +extern gdMainWindow *G_MainWin; + + +geMainIO::geMainIO(int x, int y) + : Fl_Group(x, y, 396, 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+4, 140, 12); + inToOut = new gClick (inMeter->x()+inMeter->w()+4, y+4, 12, 12, "", inputToOutputOff_xpm, inputToOutputOn_xpm); + outMeter = new gSoundMeter(inToOut->x()+inToOut->w()+4, y+4, 140, 12); + 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, 12); + outMeter = new gSoundMeter(inMeter->x()+inMeter->w()+4, y+5, 140, 12); + 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 geMainIO::cb_outVol (Fl_Widget *v, void *p) { ((geMainIO*)p)->__cb_outVol(); } +void geMainIO::cb_inVol (Fl_Widget *v, void *p) { ((geMainIO*)p)->__cb_inVol(); } +#ifdef WITH_VST +void geMainIO::cb_masterFxOut(Fl_Widget *v, void *p) { ((geMainIO*)p)->__cb_masterFxOut(); } +void geMainIO::cb_masterFxIn (Fl_Widget *v, void *p) { ((geMainIO*)p)->__cb_masterFxIn(); } +void geMainIO::cb_inToOut (Fl_Widget *v, void *p) { ((geMainIO*)p)->__cb_inToOut(); } +#endif + + +/* -------------------------------------------------------------------------- */ + + +void geMainIO::__cb_outVol() +{ + glue_setOutVol(outVol->value()); +} + + +/* -------------------------------------------------------------------------- */ + + +void geMainIO::__cb_inVol() +{ + glue_setInVol(inVol->value()); +} + + +/* -------------------------------------------------------------------------- */ + + +#ifdef WITH_VST +void geMainIO::__cb_masterFxOut() +{ + gu_openSubWindow(G_MainWin, new gdPluginList(PluginHost::MASTER_OUT), WID_FX_LIST); +} + +void geMainIO::__cb_masterFxIn() +{ + gu_openSubWindow(G_MainWin, new gdPluginList(PluginHost::MASTER_IN), WID_FX_LIST); +} + +void geMainIO::__cb_inToOut() +{ + G_Mixer.inToOut = inToOut->value(); +} +#endif + + +/* -------------------------------------------------------------------------- */ + + +void geMainIO::setOutVol(float v) +{ + outVol->value(v); +} + + +void geMainIO::setInVol(float v) +{ + inVol->value(v); +} + + +/* -------------------------------------------------------------------------- */ + + +#ifdef WITH_VST + +void geMainIO::setMasterFxOutFull(bool v) +{ + masterFxOut->full = v; + masterFxOut->redraw(); +} + + +void geMainIO::setMasterFxInFull(bool v) +{ + masterFxIn->full = v; + masterFxIn->redraw(); +} + +#endif + + +/* -------------------------------------------------------------------------- */ + + +void geMainIO::refresh() +{ + outMeter->mixerPeak = G_Mixer.peakOut; + inMeter->mixerPeak = G_Mixer.peakIn; + outMeter->redraw(); + inMeter->redraw(); +} diff --git a/src/gui/elems/mainWindow/mainIO.h b/src/gui/elems/mainWindow/mainIO.h new file mode 100644 index 0000000..f275211 --- /dev/null +++ b/src/gui/elems/mainWindow/mainIO.h @@ -0,0 +1,81 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * mainIO + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#ifndef GE_MAIN_IO_H +#define GE_MAIN_IO_H + + +#include + + +class geMainIO : public Fl_Group +{ +private: + + class gSoundMeter *outMeter; + class gSoundMeter *inMeter; + 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: + + geMainIO(int x, int y); + + void refresh(); + + void setOutVol(float v); + void setInVol (float v); +#ifdef WITH_VST + void setMasterFxOutFull(bool v); + void setMasterFxInFull(bool v); +#endif +}; + +#endif diff --git a/src/gui/elems/mainWindow/mainMenu.cpp b/src/gui/elems/mainWindow/mainMenu.cpp new file mode 100644 index 0000000..5c9a0f4 --- /dev/null +++ b/src/gui/elems/mainWindow/mainMenu.cpp @@ -0,0 +1,229 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * mainMenu + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include "../../../core/const.h" +#include "../../../core/mixer.h" +#include "../../../core/conf.h" +#include "../../../core/patch.h" +#include "../../../core/channel.h" +#include "../../../core/sampleChannel.h" +#include "../../../utils/gui.h" +#include "../../../glue/storage.h" +#include "../../../glue/main.h" +#include "../../elems/ge_mixed.h" +#include "../../elems/basics/boxtypes.h" +#include "../../dialogs/gd_mainWindow.h" +#include "../../dialogs/gd_about.h" +#include "../../dialogs/gd_config.h" +#include "../../dialogs/gd_browser.h" +#include "../../dialogs/gd_warnings.h" +#include "../../dialogs/midiIO/midiInputMaster.h" +#include "keyboard/keyboard.h" +#include "mainMenu.h" + + +extern Mixer G_Mixer; +extern Patch G_Patch; +extern Conf G_Conf; +extern gdMainWindow *G_MainWin; + + +geMainMenu::geMainMenu(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 geMainMenu::cb_about (Fl_Widget *v, void *p) { ((geMainMenu*)p)->__cb_about(); } +void geMainMenu::cb_config(Fl_Widget *v, void *p) { ((geMainMenu*)p)->__cb_config(); } +void geMainMenu::cb_file (Fl_Widget *v, void *p) { ((geMainMenu*)p)->__cb_file(); } +void geMainMenu::cb_edit (Fl_Widget *v, void *p) { ((geMainMenu*)p)->__cb_edit(); } + + +/* -------------------------------------------------------------------------- */ + + +void geMainMenu::__cb_about() +{ + gu_openSubWindow(G_MainWin, new gdAbout(), WID_ABOUT); +} + + +/* -------------------------------------------------------------------------- */ + + +void geMainMenu::__cb_config() +{ + gu_openSubWindow(G_MainWin, new gdConfig(380, 370), WID_CONFIG); +} + + +/* -------------------------------------------------------------------------- */ + + +void geMainMenu::__cb_file() +{ + /* An Fl_Menu_Button is made of many Fl_Menu_Item */ + + Fl_Menu_Item menu[] = { + {"Open patch or project..."}, + {"Save patch..."}, + {"Save project..."}, + {"Quit Giada"}, + {0} + }; + + Fl_Menu_Button *b = new Fl_Menu_Button(0, 0, 100, 50); + b->box(G_CUSTOM_BORDER_BOX); + b->textsize(GUI_FONT_SIZE_BASE); + 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 gdLoadBrowser(G_Conf.browserX, G_Conf.browserY, + G_Conf.browserW, G_Conf.browserH, "Load patch or project", + G_Conf.patchPath, glue_loadPatch, NULL); + gu_openSubWindow(G_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 gdSaveBrowser(G_Conf.browserX, G_Conf.browserY, + G_Conf.browserW, G_Conf.browserH, "Save patch", + G_Conf.patchPath, G_Patch.name, glue_savePatch, NULL); + gu_openSubWindow(G_MainWin, childWin, WID_FILE_BROWSER); + return; + } + if (strcmp(m->label(), "Save project...") == 0) { + gWindow *childWin = new gdSaveBrowser(G_Conf.browserX, G_Conf.browserY, + G_Conf.browserW, G_Conf.browserH, "Save project", + G_Conf.patchPath, G_Patch.name, glue_saveProject, NULL); + gu_openSubWindow(G_MainWin, childWin, WID_FILE_BROWSER); + return; + } + if (strcmp(m->label(), "Quit Giada") == 0) { + G_MainWin->do_callback(); + return; + } +} + + +/* -------------------------------------------------------------------------- */ + + +void geMainMenu::__cb_edit() +{ + Fl_Menu_Item menu[] = { + {"Clear all samples"}, + {"Clear all actions"}, + {"Remove empty columns"}, + {"Reset to init state"}, + {"Setup global MIDI input..."}, + {0} + }; + + /* clear all actions disabled if no recs, clear all samples disabled + * if no samples. */ + + menu[1].deactivate(); + + for (unsigned i=0; ihasActions) { + menu[1].activate(); + break; + } + for (unsigned i=0; itype == CHANNEL_SAMPLE) + if (((SampleChannel*)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_CUSTOM_BORDER_BOX); + b->textsize(GUI_FONT_SIZE_BASE); + 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; + G_MainWin->delSubWindow(WID_SAMPLE_EDITOR); + glue_clearAllSamples(); + return; + } + if (strcmp(m->label(), "Clear all actions") == 0) { + if (!gdConfirmWin("Warning", "Clear all actions: are you sure?")) + return; + G_MainWin->delSubWindow(WID_ACTION_EDITOR); + glue_clearAllRecs(); + return; + } + if (strcmp(m->label(), "Reset to init state") == 0) { + if (!gdConfirmWin("Warning", "Reset to init state: are you sure?")) + return; + gu_closeAllSubwindows(); + glue_resetToInitState(); + return; + } + if (strcmp(m->label(), "Remove empty columns") == 0) { + G_MainWin->keyboard->organizeColumns(); + return; + } + if (strcmp(m->label(), "Setup global MIDI input...") == 0) { + gu_openSubWindow(G_MainWin, new gdMidiInputMaster(), 0); + return; + } +} diff --git a/src/gui/elems/mainWindow/mainMenu.h b/src/gui/elems/mainWindow/mainMenu.h new file mode 100644 index 0000000..a8e6f4f --- /dev/null +++ b/src/gui/elems/mainWindow/mainMenu.h @@ -0,0 +1,62 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * mainMenu + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#ifndef GE_MAIN_MENU_H +#define GE_MAIN_MENU_H + + +#include + + +class geMainMenu : 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: + + geMainMenu(int x, int y); +}; + + +#endif diff --git a/src/gui/elems/mainWindow/mainTimer.cpp b/src/gui/elems/mainWindow/mainTimer.cpp new file mode 100644 index 0000000..e1c0d94 --- /dev/null +++ b/src/gui/elems/mainWindow/mainTimer.cpp @@ -0,0 +1,180 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * mainTimer + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include "../../../core/const.h" +#include "../../../core/mixer.h" +#include "../../../core/graphics.h" +#include "../../../glue/main.h" +#include "../../../utils/gui.h" +#include "../../elems/ge_mixed.h" +#include "../../dialogs/gd_mainWindow.h" +#include "../../dialogs/gd_bpmInput.h" +#include "../../dialogs/gd_beatsInput.h" +#include "mainTimer.h" + + +extern Mixer G_Mixer; +extern gdMainWindow *G_MainWin; + + +geMainTimer::geMainTimer(int x, int y) + : Fl_Group(x, y, 180, 20) +{ + begin(); + + quantizer = new gChoice(x, y, 40, 20, "", false); + bpm = new gClick (quantizer->x()+quantizer->w()+4, y, 40, 20); + meter = new gClick (bpm->x()+bpm->w()+8, y, 40, 20, "4/1"); + multiplier = new gClick (meter->x()+meter->w()+4, y, 20, 20, "", multiplyOff_xpm, multiplyOn_xpm); + divider = new gClick (multiplier->x()+multiplier->w()+4, y, 20, 20, "", divideOff_xpm, divideOn_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 geMainTimer::cb_bpm (Fl_Widget *v, void *p) { ((geMainTimer*)p)->__cb_bpm(); } +void geMainTimer::cb_meter (Fl_Widget *v, void *p) { ((geMainTimer*)p)->__cb_meter(); } +void geMainTimer::cb_quantizer (Fl_Widget *v, void *p) { ((geMainTimer*)p)->__cb_quantizer(); } +void geMainTimer::cb_multiplier(Fl_Widget *v, void *p) { ((geMainTimer*)p)->__cb_multiplier(); } +void geMainTimer::cb_divider (Fl_Widget *v, void *p) { ((geMainTimer*)p)->__cb_divider(); } + + +/* -------------------------------------------------------------------------- */ + + +void geMainTimer::__cb_bpm() +{ + gu_openSubWindow(G_MainWin, new gdBpmInput(bpm->label()), WID_BPM); +} + + +/* -------------------------------------------------------------------------- */ + + +void geMainTimer::__cb_meter() +{ + gu_openSubWindow(G_MainWin, new gdBeatsInput(), WID_BEATS); +} + + +/* -------------------------------------------------------------------------- */ + + +void geMainTimer::__cb_quantizer() +{ + glue_quantize(quantizer->value()); +} + + +/* -------------------------------------------------------------------------- */ + + +void geMainTimer::__cb_multiplier() +{ + glue_beatsMultiply(); +} + + +/* -------------------------------------------------------------------------- */ + + +void geMainTimer::__cb_divider() +{ + glue_beatsDivide(); +} + + +/* -------------------------------------------------------------------------- */ + + +void geMainTimer::setBpm(const char *v) +{ + bpm->copy_label(v); +} + + +void geMainTimer::setBpm(float v) +{ + char buf[6]; + sprintf(buf, "%.01f", v); // only 1 decimal place (e.g. 120.0) + bpm->copy_label(buf); +} + + +/* -------------------------------------------------------------------------- */ + + +void geMainTimer::setLock(bool v) +{ + if (v) { + bpm->deactivate(); + meter->deactivate(); + multiplier->deactivate(); + divider->deactivate(); + } + else { + bpm->activate(); + meter->activate(); + multiplier->activate(); + divider->activate(); + } +} + + +/* -------------------------------------------------------------------------- */ + + +void geMainTimer::setMeter(int beats, int bars) +{ + char buf[8]; + sprintf(buf, "%d/%d", beats, bars); + meter->copy_label(buf); +} diff --git a/src/gui/elems/mainWindow/mainTimer.h b/src/gui/elems/mainWindow/mainTimer.h new file mode 100644 index 0000000..8be1d82 --- /dev/null +++ b/src/gui/elems/mainWindow/mainTimer.h @@ -0,0 +1,74 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * mainTimer + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#ifndef GE_MAIN_TIMER_H +#define GE_MAIN_TIMER_H + + +#include + + +class geMainTimer : 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: + + geMainTimer(int x, int y); + + void setBpm(const char *v); + void setBpm(float v); + void setMeter(int beats, int bars); + + /* setLock + Locks bpm, beter and multipliers. Used during audio recordings. */ + + void setLock(bool v); +}; + + +#endif diff --git a/src/gui/elems/mainWindow/mainTransport.cpp b/src/gui/elems/mainWindow/mainTransport.cpp new file mode 100644 index 0000000..9b54c22 --- /dev/null +++ b/src/gui/elems/mainWindow/mainTransport.cpp @@ -0,0 +1,160 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * mainTransport + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include "../../../core/graphics.h" +#include "../../../glue/main.h" +#include "../../../glue/io.h" +#include "../ge_mixed.h" +#include "mainTransport.h" + + +geMainTransport::geMainTransport(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 geMainTransport::cb_rewind (Fl_Widget *v, void *p) { ((geMainTransport*)p)->__cb_rewind(); } +void geMainTransport::cb_play (Fl_Widget *v, void *p) { ((geMainTransport*)p)->__cb_play(); } +void geMainTransport::cb_recAction(Fl_Widget *v, void *p) { ((geMainTransport*)p)->__cb_recAction(); } +void geMainTransport::cb_recInput (Fl_Widget *v, void *p) { ((geMainTransport*)p)->__cb_recInput(); } +void geMainTransport::cb_metronome(Fl_Widget *v, void *p) { ((geMainTransport*)p)->__cb_metronome(); } + + +/* -------------------------------------------------------------------------- */ + + +void geMainTransport::__cb_rewind() +{ + glue_rewindSeq(); +} + + +/* -------------------------------------------------------------------------- */ + + +void geMainTransport::__cb_play() +{ + glue_startStopSeq(true); +} + + +/* -------------------------------------------------------------------------- */ + + +void geMainTransport::__cb_recAction() +{ + glue_startStopActionRec(true); +} + + +/* -------------------------------------------------------------------------- */ + + +void geMainTransport::__cb_recInput() +{ + glue_startStopInputRec(true); +} + + +/* -------------------------------------------------------------------------- */ + + +void geMainTransport::__cb_metronome() +{ + glue_startStopMetronome(true); +} + + +/* -------------------------------------------------------------------------- */ + + +void geMainTransport::updatePlay(int v) +{ + play->value(v); + play->redraw(); +} + + +/* -------------------------------------------------------------------------- */ + + +void geMainTransport::updateMetronome(int v) +{ + metronome->value(v); + metronome->redraw(); +} + + +/* -------------------------------------------------------------------------- */ + + +void geMainTransport::updateRecInput(int v) +{ + recInput->value(v); + recInput->redraw(); +} + + +/* -------------------------------------------------------------------------- */ + + +void geMainTransport::updateRecAction(int v) +{ + recAction->value(v); + recAction->redraw(); +} diff --git a/src/gui/elems/mainWindow/mainTransport.h b/src/gui/elems/mainWindow/mainTransport.h new file mode 100644 index 0000000..57b83e3 --- /dev/null +++ b/src/gui/elems/mainWindow/mainTransport.h @@ -0,0 +1,69 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * mainTransport + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#ifndef GE_MAIN_TRANSPORT_H +#define GE_MAIN_TRANSPORT_H + + +#include + + +class geMainTransport : 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: + + geMainTransport(int x, int y); + + void updatePlay (int v); + void updateMetronome(int v); + void updateRecInput (int v); + void updateRecAction(int v); +}; + +#endif diff --git a/src/gui/elems/midiLearner.cpp b/src/gui/elems/midiLearner.cpp new file mode 100644 index 0000000..f63b208 --- /dev/null +++ b/src/gui/elems/midiLearner.cpp @@ -0,0 +1,110 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * midiLearner + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License 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_mixed.h" +#include "basics/boxtypes.h" +#include "midiLearner.h" + + +extern KernelMidi G_KernelMidi; + + +geMidiLearner::geMidiLearner(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); + button = new gButton(value->x()+value->w()+4, y(), 40, 20, "learn"); + end(); + + text->box(G_CUSTOM_BORDER_BOX); + text->align(FL_ALIGN_LEFT | FL_ALIGN_INSIDE); + + value->box(G_CUSTOM_BORDER_BOX); + value->callback(cb_value, (void*)this); + value->when(FL_WHEN_RELEASE); + updateValue(); + + button->type(FL_TOGGLE_BUTTON); + button->callback(cb_button, (void*)this); +} + + +/* -------------------------------------------------------------------------- */ + + +void geMidiLearner::updateValue() +{ + 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 geMidiLearner::cb_button(Fl_Widget *v, void *p) { ((geMidiLearner*)p)->__cb_button(); } +void geMidiLearner::cb_value(Fl_Widget *v, void *p) { ((geMidiLearner*)p)->__cb_value(); } + + +/* -------------------------------------------------------------------------- */ + + +void geMidiLearner::__cb_value() +{ + if (Fl::event_button() == FL_RIGHT_MOUSE) { + *param = 0x0; + updateValue(); + } + /// TODO - elif (LEFT_MOUSE) : insert values by hand +} + + +/* -------------------------------------------------------------------------- */ + + +void geMidiLearner::__cb_button() +{ + if (button->value() == 1) { + cbData.window = (gdMidiInput*) parent(); // parent = gdMidiInput + cbData.learner = this; + G_KernelMidi.startMidiLearn(callback, (void*)&cbData); + } + else + G_KernelMidi.stopMidiLearn(); +} diff --git a/src/gui/elems/midiLearner.h b/src/gui/elems/midiLearner.h new file mode 100644 index 0000000..9d5b0a0 --- /dev/null +++ b/src/gui/elems/midiLearner.h @@ -0,0 +1,84 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * midiLearner + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License 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_LEARNER_H +#define GE_LEARNER_H + + +#include +#include "../../core/kernelMidi.h" + + +extern KernelMidi G_KernelMidi; + + +class geMidiLearner : 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: + + /* cbData_t + * struct we pass to kernelMidi as extra parameter. */ + + struct cbData_t + { + class gdMidiInput *window; + class geMidiLearner *learner; + } cbData; + + /* param + * pointer to ch->midiIn[value] */ + + uint32_t *param; + + geMidiLearner(int x, int y, int w, const char *l, KernelMidi::cb_midiLearn *cb, + uint32_t *param); + + void updateValue(); +}; + + +#endif diff --git a/src/gui/elems/muteEditor.cpp b/src/gui/elems/muteEditor.cpp new file mode 100644 index 0000000..1c9ae3b --- /dev/null +++ b/src/gui/elems/muteEditor.cpp @@ -0,0 +1,415 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include "../../core/recorder.h" +#include "../../core/mixer.h" +#include "../../core/channel.h" +#include "../../glue/main.h" +#include "../../utils/log.h" +#include "../dialogs/gd_actionEditor.h" +#include "../dialogs/gd_mainWindow.h" +#include "mainWindow/keyboard/keyboard.h" +#include "muteEditor.h" + + +extern gdMainWindow *G_MainWin; +extern Mixer G_Mixer; +extern Recorder G_Recorder; + + +geMuteEditor::geMuteEditor(int x, int y, gdActionEditor *pParent) + : geBaseActionEditor(x, y, 200, 80, pParent), + draggedPoint (-1), + selectedPoint (-1) +{ + size(pParent->totalWidth, h()); + extractPoints(); +} + + +/* ------------------------------------------------------------------ */ + + +void geMuteEditor::draw() +{ + baseDraw(); + + /* print label */ + + fl_color(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 geMuteEditor::extractPoints() +{ + points.clear(); + + /* actions are already sorted by G_Recorder.sortActions() */ + + for (unsigned i=0; ichan == pParent->chan->index) { + if (G_Recorder.global.at(i).at(j)->type & (ACTION_MUTEON | ACTION_MUTEOFF)) { + point p; + p.frame = G_Recorder.frames.at(i); + p.type = G_Recorder.global.at(i).at(j)->type; + p.x = p.frame / pParent->zoom; + points.push_back(p); + //gu_log("[geMuteEditor::extractPoints] point found, type=%d, frame=%d\n", p.type, p.frame); + } + } + } + } +} + + +/* ------------------------------------------------------------------ */ + + +void geMuteEditor::updateActions() { + for (unsigned i=0; izoom; +} + + +/* ------------------------------------------------------------------ */ + + +int geMuteEditor::handle(int e) { + + int ret = 0; + int mouseX = Fl::event_x()-x(); + + switch (e) { + + case FL_ENTER: { + ret = 1; + break; + } + + case FL_MOVE: { + selectedPoint = getSelectedPoint(); + redraw(); + ret = 1; + break; + } + + case FL_LEAVE: { + draggedPoint = -1; + selectedPoint = -1; + redraw(); + ret = 1; + break; + } + + case FL_PUSH: { + + /* left click on point: drag + * right click on point: delete + * left click on void: add */ + + if (Fl::event_button1()) { + + if (selectedPoint != -1) { + draggedPoint = selectedPoint; + previousXPoint = points.at(selectedPoint).x; + } + else { + + /* click on the grey area leads to nowhere */ + + if (mouseX > pParent->coverX) { + ret = 1; + break; + } + + /* click in the middle of a long mute_on (between two points): new actions + * must be added in reverse: first mute_off then mute_on. Let's find the + * next point from here. */ + + unsigned nextPoint = points.size(); + for (unsigned i=0; izoom; + int frame_b = frame_a+2048; + + if (pParent->gridTool->isOn()) { + frame_a = pParent->gridTool->getSnapFrame(mouseX); + frame_b = pParent->gridTool->getSnapFrame(mouseX + pParent->gridTool->getCellSize()); + + /* with snap=on a point can fall onto another */ + + if (pointCollides(frame_a) || pointCollides(frame_b)) { + ret = 1; + break; + } + } + + /* ensure frame parity */ + + if (frame_a % 2 != 0) frame_a++; + if (frame_b % 2 != 0) frame_b++; + + /* avoid overflow: frame_b must be within the sequencer range. In that + * case shift the ON-OFF block */ + + if (frame_b >= G_Mixer.totalFrames) { + frame_b = G_Mixer.totalFrames; + frame_a = frame_b-2048; + } + + if (nextPoint % 2 != 0) { + G_Recorder.rec(pParent->chan->index, ACTION_MUTEOFF, frame_a); + G_Recorder.rec(pParent->chan->index, ACTION_MUTEON, frame_b); + } + else { + G_Recorder.rec(pParent->chan->index, ACTION_MUTEON, frame_a); + G_Recorder.rec(pParent->chan->index, ACTION_MUTEOFF, frame_b); + } + pParent->chan->hasActions = true; + G_Recorder.sortActions(); + + G_MainWin->keyboard->setChannelWithActions((geSampleChannel*)pParent->chan->guiChannel); // update mainWindow + extractPoints(); + redraw(); + } + } + else { + + /* delete points pair */ + + if (selectedPoint != -1) { + + unsigned a; + unsigned b; + + if (points.at(selectedPoint).type == ACTION_MUTEOFF) { + a = selectedPoint-1; + b = selectedPoint; + } + else { + a = selectedPoint; + b = selectedPoint+1; + } + + //gu_log("selected: a=%d, b=%d >>> frame_a=%d, frame_b=%d\n", + // a, b, points.at(a).frame, points.at(b).frame); + + G_Recorder.deleteAction(pParent->chan->index, points.at(a).frame, + points.at(a).type, false, &G_Mixer.mutex_recs); // false = don't check vals + G_Recorder.deleteAction(pParent->chan->index, points.at(b).frame, + points.at(b).type, false, &G_Mixer.mutex_recs); // false = don't check vals + pParent->chan->hasActions = G_Recorder.hasActions(pParent->chan->index); + + G_Recorder.sortActions(); + + G_MainWin->keyboard->setChannelWithActions((geSampleChannel*)pParent->chan->guiChannel); // update mainWindow + extractPoints(); + redraw(); + } + } + ret = 1; + break; + } + + case FL_RELEASE: { + + if (draggedPoint != -1) { + + if (points.at(draggedPoint).x == previousXPoint) { + //gu_log("nothing to do\n"); + } + else { + + int newFrame = points.at(draggedPoint).x * pParent->zoom; + + G_Recorder.deleteAction(pParent->chan->index, + points.at(draggedPoint).frame, points.at(draggedPoint).type, false, + &G_Mixer.mutex_recs); // don't check values + pParent->chan->hasActions = G_Recorder.hasActions(pParent->chan->index); + + G_Recorder.rec( + pParent->chan->index, + points.at(draggedPoint).type, + newFrame); + + pParent->chan->hasActions = true; + G_Recorder.sortActions(); + + points.at(draggedPoint).frame = newFrame; + } + } + draggedPoint = -1; + selectedPoint = -1; + + ret = 1; + break; + } + + case FL_DRAG: { + + if (draggedPoint != -1) { + + /* constrain the point between two ends (leftBorder-point, + * point-point, point-rightBorder) */ + + int prevPoint; + int nextPoint; + + if (draggedPoint == 0) { + prevPoint = 0; + nextPoint = points.at(draggedPoint+1).x - 1; + if (pParent->gridTool->isOn()) + nextPoint -= pParent->gridTool->getCellSize(); + } + else + if ((unsigned) draggedPoint == points.size()-1) { + prevPoint = points.at(draggedPoint-1).x + 1; + nextPoint = pParent->coverX-x(); + if (pParent->gridTool->isOn()) + prevPoint += pParent->gridTool->getCellSize(); + } + else { + prevPoint = points.at(draggedPoint-1).x + 1; + nextPoint = points.at(draggedPoint+1).x - 1; + if (pParent->gridTool->isOn()) { + prevPoint += pParent->gridTool->getCellSize(); + nextPoint -= pParent->gridTool->getCellSize(); + } + } + + if (mouseX <= prevPoint) + points.at(draggedPoint).x = prevPoint; + else + if (mouseX >= nextPoint) + points.at(draggedPoint).x = nextPoint; + else + if (pParent->gridTool->isOn()) + points.at(draggedPoint).x = pParent->gridTool->getSnapPoint(mouseX)-1; + else + points.at(draggedPoint).x = mouseX; + + redraw(); + } + ret = 1; + break; + } + } + + + return ret; +} + + +/* ------------------------------------------------------------------ */ + + +bool geMuteEditor::pointCollides(int frame) { + for (unsigned i=0; i= points.at(i).x+x()-3 && + Fl::event_x() <= points.at(i).x+x()+3) + return i; + } + return -1; +} diff --git a/src/gui/elems/muteEditor.h b/src/gui/elems/muteEditor.h new file mode 100644 index 0000000..620f82a --- /dev/null +++ b/src/gui/elems/muteEditor.h @@ -0,0 +1,106 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License 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 +#include "../../utils/fs.h" +#include "baseActionEditor.h" + + +using std::vector; + + +class geMuteEditor : public geBaseActionEditor +{ +private: + + /* point + * a single dot in the graph. */ + + struct point + { + int frame; + char type; + int x; + }; + + /* points + * array of on/off points, in frames */ + + vector points; + + /* draggedPoint + * which point we are dragging? */ + + int draggedPoint; + + /* selectedPoint + * which point we are selecting? */ + + int selectedPoint; + + /* previousXPoint + * x coordinate of point at time t-1. Used to check effective shifts */ + + int previousXPoint; + + /* extractPoints + * va a leggere l'array di azioni di Recorder ed estrae tutti i punti + * interessanti mute_on o mute_off. Li mette poi nel vector points. */ + void extractPoints(); + + /* getSelectedPoint + * ritorna l'indice di points[] in base al punto selezionato (quello + * con il mouse hover). Ritorna -1 se non trova niente. */ + int getSelectedPoint(); + + /* pointCollides + * true if a point collides with another. Used while adding new points + * with snap active.*/ + + bool pointCollides(int frame); + +public: + + geMuteEditor(int x, int y, 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/gui/elems/noteEditor.cpp b/src/gui/elems/noteEditor.cpp new file mode 100644 index 0000000..19940a2 --- /dev/null +++ b/src/gui/elems/noteEditor.cpp @@ -0,0 +1,90 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include +#include "../../core/const.h" +#include "../../core/conf.h" +#include "../../utils/log.h" +#include "../dialogs/gd_actionEditor.h" +#include "pianoItem.h" +#include "pianoRoll.h" +#include "noteEditor.h" + + +extern Conf G_Conf; + + +geNoteEditor::geNoteEditor(int x, int y, gdActionEditor *pParent) + : Fl_Scroll(x, y, 200, 422), + pParent (pParent) +{ + size(pParent->totalWidth, G_Conf.pianoRollH); + pianoRoll = new gePianoRoll(x, y, pParent->totalWidth, pParent); +} + + +/* -------------------------------------------------------------------------- */ + + +geNoteEditor::~geNoteEditor() +{ + clear(); + G_Conf.pianoRollH = h(); + G_Conf.pianoRollY = pianoRoll->y(); +} + + +/* -------------------------------------------------------------------------- */ + + +void geNoteEditor::updateActions() +{ + pianoRoll->updateActions(); +} + + +/* -------------------------------------------------------------------------- */ + + +void geNoteEditor::draw() +{ + pianoRoll->size(this->w(), pianoRoll->h()); /// <--- not optimal + + /* clear background */ + + fl_rectf(x(), y(), w(), h(), 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()); +} diff --git a/src/gui/elems/noteEditor.h b/src/gui/elems/noteEditor.h new file mode 100644 index 0000000..0ca600f --- /dev/null +++ b/src/gui/elems/noteEditor.h @@ -0,0 +1,54 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#ifndef GE_NOTE_EDITOR_H +#define GE_NOTE_EDITOR_H + +#include +#include +#include +#include +#include "../../core/recorder.h" + + +class geNoteEditor : public Fl_Scroll +{ +private: + + class gdActionEditor *pParent; + class gePianoRoll *pianoRoll; + +public: + + geNoteEditor(int x, int y, class gdActionEditor *parent); + ~geNoteEditor(); + void draw(); + void updateActions(); +}; + + +#endif diff --git a/src/gui/elems/pianoItem.cpp b/src/gui/elems/pianoItem.cpp new file mode 100644 index 0000000..c6449cb --- /dev/null +++ b/src/gui/elems/pianoItem.cpp @@ -0,0 +1,351 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include "../../core/kernelMidi.h" +#include "../../core/mixer.h" +#include "../../core/channel.h" +#include "../../core/midiChannel.h" +#include "../dialogs/gd_actionEditor.h" +#include "noteEditor.h" +#include "pianoRoll.h" +#include "pianoItem.h" + + +extern KernelMidi G_KernelMidi; +extern Mixer G_Mixer; +extern Recorder G_Recorder; + + +gePianoItem::gePianoItem(int X, int Y, int rel_x, int rel_y, Recorder::action *_a, + Recorder::action *_b, gdActionEditor *pParent) + : Fl_Box (X, Y, MIN_WIDTH, gePianoRoll::CELL_H), + 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 = G_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 gePianoItem::overlap() +{ + /* when 2 segments overlap? + * start = the highest value between the two starting points + * end = the lowest value between the two ending points + * if start < end then there's an overlap of end-start pixels. */ + + geNoteEditor *pPiano = (geNoteEditor*) parent(); + + for (int i=0; ichildren(); i++) { + + gePianoItem *pItem = (gePianoItem*) 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 gePianoItem::draw() +{ + int _w = w() > MIN_WIDTH ? w() : MIN_WIDTH; + fl_rectf(x(), y()+2, _w, h()-3, (Fl_Color) selected ? COLOR_BD_1 : COLOR_BG_2); +} + + +/* -------------------------------------------------------------------------- */ + + +void gePianoItem::record() +{ + /* avoid frame overflow */ + + int overflow = frame_b - G_Mixer.totalFrames; + if (overflow > 0) { + frame_b -= overflow; + frame_a -= overflow; + } + + event_a |= (MIDI_NOTE_ON); + event_a |= (note << 16); // note value + event_a |= (MIDI_VELOCITY); + event_a |= (0x00); + + event_b |= (MIDI_NOTE_OFF); + event_b |= (note << 16); // note value + event_b |= (MIDI_VELOCITY); + event_b |= (0x00); + + G_Recorder.rec(pParent->chan->index, ACTION_MIDI, frame_a, event_a); + G_Recorder.rec(pParent->chan->index, ACTION_MIDI, frame_b, event_b); + pParent->chan->hasActions = true; +} + + +/* -------------------------------------------------------------------------- */ + + +void gePianoItem::remove() +{ + G_Recorder.deleteAction(pParent->chan->index, frame_a, ACTION_MIDI, true, + &G_Mixer.mutex_recs, event_a, 0.0); + G_Recorder.deleteAction(pParent->chan->index, frame_b, ACTION_MIDI, true, + &G_Mixer.mutex_recs, 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); + + ((gePianoRoll*) parent())->cursorOnItem = false; +} + + +/* -------------------------------------------------------------------------- */ + + +int gePianoItem::handle(int e) +{ + int ret = 0; + + switch (e) { + + case FL_ENTER: { + ((gePianoRoll*) parent())->cursorOnItem = true; + selected = true; + ret = 1; + redraw(); + break; + } + + case FL_LEAVE: { + fl_cursor(FL_CURSOR_DEFAULT, FL_WHITE, FL_BLACK); + ((gePianoRoll*) parent())->cursorOnItem = false; + selected = false; + ret = 1; + redraw(); + break; + } + + case FL_MOVE: { + onLeftEdge = false; + onRightEdge = false; + + if (Fl::event_x() >= x() && Fl::event_x() < x()+HANDLE_WIDTH) { + onLeftEdge = true; + fl_cursor(FL_CURSOR_WE, FL_WHITE, FL_BLACK); + } + else + if (Fl::event_x() >= x()+w()-HANDLE_WIDTH && Fl::event_x() <= x()+w()) { + onRightEdge = true; + fl_cursor(FL_CURSOR_WE, FL_WHITE, FL_BLACK); + } + else + fl_cursor(FL_CURSOR_DEFAULT, FL_WHITE, FL_BLACK); + + ret = 1; + break; + } + + case FL_PUSH: { + + push_x = Fl::event_x() - x(); + old_x = x(); + old_w = w(); + + if (Fl::event_button3()) { + fl_cursor(FL_CURSOR_DEFAULT, FL_WHITE, FL_BLACK); + remove(); + hide(); // for Windows + Fl::delete_widget(this); + ((geNoteEditor*)parent())->redraw(); + } + ret = 1; + break; + } + + case FL_DRAG: { + + changed = true; + + geNoteEditor *pr = (geNoteEditor*) parent(); + int coverX = pParent->coverX + pr->x(); // relative coverX + int nx, ny, nw; + + if (onLeftEdge) { + nx = Fl::event_x(); + ny = y(); + nw = x()-Fl::event_x()+w(); + if (nx < pr->x()) { + nx = pr->x(); + nw = w()+x()-pr->x(); + } + else + if (nx > x()+w()-MIN_WIDTH) { + nx = x()+w()-MIN_WIDTH; + nw = MIN_WIDTH; + } + resize(nx, ny, nw, h()); + } + else + if (onRightEdge) { + nw = Fl::event_x()-x(); + if (Fl::event_x() < x()+MIN_WIDTH) + nw = MIN_WIDTH; + else + if (Fl::event_x() > coverX) + nw = coverX-x(); + size(nw, h()); + } + else { + nx = Fl::event_x() - push_x; + if (nx < pr->x()+1) + nx = pr->x()+1; + else + if (nx+w() > coverX) + nx = coverX-w(); + + /* snapping */ + + if (pParent->gridTool->isOn()) + nx = pParent->gridTool->getSnapPoint(nx-pr->x()) + pr->x() - 1; + + position(nx, y()); + } + + /* update screen */ + + redraw(); + ((geNoteEditor*)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; + } + + ((geNoteEditor*)parent())->redraw(); + + ret = 1; + break; + } + } + return ret; +} + + +/* -------------------------------------------------------------------------- */ + + +int gePianoItem::getNote(int rel_y) +{ + return gePianoRoll::MAX_KEYS - (rel_y / gePianoRoll::CELL_H); +} + + +/* -------------------------------------------------------------------------- */ + + +int gePianoItem::getRelY() +{ + return y() - parent()->y(); +} + + +/* -------------------------------------------------------------------------- */ + + +int gePianoItem::getRelX() +{ + return x() - parent()->x(); +} + + +/* -------------------------------------------------------------------------- */ + + +int gePianoItem::getY(int note) +{ + return (gePianoRoll::MAX_KEYS * gePianoRoll::CELL_H) - (note * gePianoRoll::CELL_H); +} diff --git a/src/gui/elems/pianoItem.h b/src/gui/elems/pianoItem.h new file mode 100644 index 0000000..0aa4b2b --- /dev/null +++ b/src/gui/elems/pianoItem.h @@ -0,0 +1,119 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#ifndef GE_PIANO_ITEM_H +#define GE_PIANO_ITEM_H + + +#include +#include "../../core/recorder.h" + + +class gePianoItem : public Fl_Box +{ +private: + + /* getRelX/Y + * return x/y point of this item, relative to piano roll (and not to + * entire screen) */ + + int getRelY(); + int getRelX(); + + /* getNote + * from a relative_y return the real MIDI note, range 0-127. 15 is + * the hardcoded value for note height in pixels */ + + int getNote(int rel_y); + + /* getY + * from a note, return the y position on piano roll */ + + int getY(int note); + + /* overlap + * check if this item don't overlap with another one. */ + + bool overlap(); + + struct Recorder::action *a; + struct 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: + + static const int MIN_WIDTH = 10; + static const int HANDLE_WIDTH = 5; + + /* pianoItem ctor + * if action *a == NULL, record a new action */ + + gePianoItem(int x, int y, int rel_x, int rel_y, struct Recorder::action *a, + struct Recorder::action *b, class gdActionEditor *pParent); + + void draw(); + int handle(int e); + void record(); + void remove(); + + int getFrame_a() { return frame_a; } + int getFrame_b() { return frame_b; } + int getNote() { return note; } +}; + + +#endif diff --git a/src/gui/elems/pianoRoll.cpp b/src/gui/elems/pianoRoll.cpp new file mode 100644 index 0000000..b8f776b --- /dev/null +++ b/src/gui/elems/pianoRoll.cpp @@ -0,0 +1,360 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include "../../core/conf.h" +#include "../../core/mixer.h" +#include "../../core/channel.h" +#include "../../core/recorder.h" +#include "../../core/kernelMidi.h" +#include "../../utils/log.h" +#include "../dialogs/gd_actionEditor.h" +#include "pianoItem.h" +#include "noteEditor.h" +#include "pianoRoll.h" + + +extern Conf G_Conf; +extern Recorder G_Recorder; +extern Mixer G_Mixer; +extern KernelMidi G_KernelMidi; + + +gePianoRoll::gePianoRoll(int X, int Y, int W, gdActionEditor *pParent) + : geBaseActionEditor(X, Y, W, 40, pParent), + cursorOnItem (false) +{ + resizable(nullptr); // don't resize children (i.e. pianoItem) + size(W, (MAX_KEYS+1) * CELL_H); // 128 MIDI channels * CELL_H 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. */ + + G_Recorder.sortActions(); + + Recorder::action *a2 = nullptr; + Recorder::action *prev = nullptr; + + for (unsigned i=0; i than the grey area */ + /** FIXME - can we move this to the outer cycle? */ + + if (G_Recorder.frames.at(i) > G_Mixer.totalFrames) + continue; + + Recorder::action *a1 = G_Recorder.global.at(i).at(j); + + /* Skip action if: + - does not belong to this channel + - is not a MIDI action (we only want MIDI things here) + - is the previous one (we have already checked it) + - (later on) if it's NOTE_OFF (0x80): we want note on only */ + + if (a1->chan != pParent->chan->index) + continue; + if (a1->type != ACTION_MIDI) + continue; + if (a1 == prev) + continue; + + /* extract MIDI infos from a1: if is note off skip it, we are looking + * for note on only */ + + int a1_type = G_KernelMidi.getB1(a1->iValue); + int a1_note = G_KernelMidi.getB2(a1->iValue); + + if (a1_type == 0x80) // NOTE_OFF + 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, any velocity value (0xFF) because we just don't care about the + velocity of a note_off. */ + + G_Recorder.getNextAction(a1->chan, ACTION_MIDI, a1->frame, &a2, + G_KernelMidi.getIValue(0x80, a1_note, 0xFF)); + + /* next action note_off found: add a new gePianoItem to piano roll */ + + if (a2) { + new gePianoItem(0, 0, x(), y(), a1, a2, pParent); + prev = a2; + a2 = nullptr; + } + else + gu_log("[geNoteEditor] recorder didn't find requested action!\n"); + // TODO - create new gOrphanedPianoItem + } + } + + end(); +} + + +/* -------------------------------------------------------------------------- */ + + +void gePianoRoll::drawSurface1() +{ + surface1 = fl_create_offscreen(CELL_W, h()); + fl_begin_offscreen(surface1); + + /* warning: only w() and h() come from this widget, x and y coordinates + * are absolute, since we are writing in a memory chunk */ + + fl_rectf(0, 0, CELL_W, h(), COLOR_BG_MAIN); + + fl_line_style(FL_DASH, 0, nullptr); + fl_font(FL_HELVETICA, GUI_FONT_SIZE_BASE); + + int octave = MAX_OCTAVES; + + for (int i=1; i<=MAX_KEYS+1; i++) { + + /* print key note label. C C# D D# E F F# G G# A A# B */ + + char note[6]; + switch (i % KEYS) { + case (int) Notes::G: + fl_rectf(0, i*CELL_H, CELL_W, CELL_H, COLOR_BG_RICH); + sprintf(note, "%dG", octave); + break; + case (int) Notes::FS: + sprintf(note, "%dF#", octave); + break; + case (int) Notes::F: + sprintf(note, "%dF", octave); + break; + case (int) Notes::E: + fl_rectf(0, i*CELL_H, CELL_W, CELL_H, COLOR_BG_RICH); + sprintf(note, "%dE", octave); + break; + case (int) Notes::DS: + sprintf(note, "%dD#", octave); + break; + case (int) Notes::D: + fl_rectf(0, i*CELL_H, CELL_W, CELL_H, COLOR_BG_RICH); + sprintf(note, "%dD", octave); + break; + case (int) Notes::CS: + sprintf(note, "%dC#", octave); + break; + case (int) Notes::C: + sprintf(note, "%dC", octave); + break; + case (int) Notes::B: + fl_rectf(0, i*CELL_H, CELL_W, CELL_H, COLOR_BG_RICH); + sprintf(note, "%dB", octave); + break; + case (int) Notes::AS: + sprintf(note, "%dA#", octave); + break; + case (int) Notes::A: + fl_rectf(0, i*CELL_H, CELL_W, CELL_H, COLOR_BG_RICH); + sprintf(note, "%dA", octave); + break; + case (int) Notes::GS: + sprintf(note, "%dG#", octave); + octave--; + break; + } + + /* Print note name */ + + fl_color(COLOR_BG_LINE); + fl_draw(note, 4, ((i-1)*CELL_H)+1, CELL_W, CELL_H, + (Fl_Align) (FL_ALIGN_LEFT | FL_ALIGN_CENTER)); + + /* Print horizontal line */ + + if (i < MAX_KEYS+1) + fl_line(0, i*CELL_H, CELL_W, +i*CELL_H); + } + + fl_line_style(0); + fl_end_offscreen(); +} + + +/* -------------------------------------------------------------------------- */ + + +void gePianoRoll::drawSurface2() +{ + surface2 = fl_create_offscreen(CELL_W, h()); + fl_begin_offscreen(surface2); + fl_rectf(0, 0, CELL_W, h(), COLOR_BG_MAIN); + fl_color(COLOR_BG_LINE); + fl_line_style(FL_DASH, 0, nullptr); + for (int i=1; i<=MAX_KEYS+1; i++) { + switch (i % KEYS) { + case (int) Notes::G: + case (int) Notes::E: + case (int) Notes::D: + case (int) Notes::B: + case (int) Notes::A: + fl_rectf(0, i*CELL_H, CELL_W, CELL_H, COLOR_BG_RICH); + break; + } + if (i < MAX_KEYS+1) { + fl_color(COLOR_BG_LINE); + fl_line(0, i*CELL_H, CELL_W, i*CELL_H); + } + } + fl_line_style(0); + fl_end_offscreen(); +} + + +/* -------------------------------------------------------------------------- */ + + +void gePianoRoll::draw() +{ + fl_copy_offscreen(x(), y(), CELL_W, h(), surface1, 0, 0); + +#if defined(__APPLE__) + for (int i=36; itotalWidth; i+=36) /// TODO: i < pParent->coverX is faster + fl_copy_offscreen(x()+i, y(), CELL_W, h(), surface2, 1, 0); +#else + for (int i=CELL_W; itotalWidth; i+=CELL_W) /// TODO: i < pParent->coverX is faster + fl_copy_offscreen(x()+i, y(), CELL_W, h(), surface2, 0, 0); +#endif + + baseDraw(false); + draw_children(); +} + + +/* -------------------------------------------------------------------------- */ + + +int gePianoRoll::handle(int e) +{ + int ret = Fl_Group::handle(e); + + switch (e) { + case FL_PUSH: { + + /* avoid click on grey area */ + + if (Fl::event_x() >= pParent->coverX) { + ret = 1; + break; + } + + + push_y = Fl::event_y() - y(); + + if (Fl::event_button1()) { + + /* ax is driven by grid, ay by the height in px of each note */ + + int ax = Fl::event_x(); + int ay = Fl::event_y(); + + /* vertical snap */ + + int edge = (ay-y()) % CELL_H; + if (edge != 0) ay -= edge; + + /* if 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 (!cursorOnItem) { + int greyover = ax+20 - pParent->coverX-x(); + if (greyover > 0) + ax -= greyover; + add(new gePianoItem(ax, ay, ax-x(), ay-y(), nullptr, nullptr, pParent)); + redraw(); + } + } + ret = 1; + break; + } + case FL_DRAG: { + + if (Fl::event_button3()) { + + geNoteEditor *prc = (geNoteEditor*) parent(); + position(x(), Fl::event_y() - push_y); + + if (y() > prc->y()) + position(x(), prc->y()); + else + if (y() < prc->y()+prc->h()-h()) + position(x(), prc->y()+prc->h()-h()); + + prc->redraw(); + } + ret = 1; + break; + } + case FL_MOUSEWHEEL: { // nothing to do, just avoid small internal scroll + ret = 1; + break; + } + } + return ret; +} + + +/* -------------------------------------------------------------------------- */ + + +void gePianoRoll::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 */ + + gePianoItem *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(); + + //gu_log("update point %p, frame_a=%d frame_b=%d, x()=%d\n", (void*) i, i->getFrame_a(), i->getFrame_b(), i->x()); + } +} diff --git a/src/gui/elems/pianoRoll.h b/src/gui/elems/pianoRoll.h new file mode 100644 index 0000000..449d4d0 --- /dev/null +++ b/src/gui/elems/pianoRoll.h @@ -0,0 +1,84 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#ifndef GE_PIANO_ROLL_H +#define GE_PIANO_ROLL_H + + +#include +#include "baseActionEditor.h" + + +class gePianoRoll : public geBaseActionEditor +{ +private: + + enum class Notes + { + G = 1, FS = 2, F = 3, E = 4, DS = 5, D = 6, CS = 7, C = 8, B = 9, AS = 10, + A = 11, GS = 0 + }; + + /* drawSurface* + Generates a complex drawing in memory first and copy it to the screen at a + later point in time. Fl_Offscreen surface holds the necessary data. The first + call creates an offscreen surface of CELL_W pixel wide containing note values. + The second call creates another offscreen surface of CELL_W pixels wide + containing the rest of the piano roll. The latter will then be tiled during + the ::draw() call. */ + + void drawSurface1(); + void drawSurface2(); + + int push_y; + + Fl_Offscreen surface1; // notes, no repeat + Fl_Offscreen surface2; // lines, x-repeat + +public: + + static const int MAX_KEYS = 127; + static const int MAX_OCTAVES = 9; + static const int KEYS = 12; + static const int CELL_H = 18; + static const int CELL_W = 40; + + gePianoRoll(int x, int y, int w, class gdActionEditor *pParent); + + void draw(); + int handle(int e); + void updateActions(); + + /* cursorOnItem + Defines wheter the cursor is over a piano item. This value is updated by each + gePianoItem sub-widget. */ + + bool cursorOnItem; +}; + + +#endif diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..a80e63b --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,113 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License 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 "core/init.h" +#include "core/const.h" +#include "core/patch_DEPR_.h" +#include "core/patch.h" +#include "core/conf.h" +#include "core/midiMapConf.h" +#include "core/mixer.h" +#include "core/mixerHandler.h" +#include "core/kernelAudio.h" +#include "core/kernelMidi.h" +#include "core/recorder.h" +#include "utils/gui.h" +#include "gui/dialogs/gd_mainWindow.h" +#include "core/pluginHost.h" + + +/* global variables. Yeah, we are nasty */ + +pthread_t G_videoThread; +KernelAudio G_KernelAudio; +Mixer G_Mixer; +Recorder G_Recorder; +KernelMidi G_KernelMidi; +bool G_quit; +bool G_audio_status; +bool G_midiStatus; +Patch_DEPR_ G_Patch_DEPR_; +Patch G_Patch; +Conf G_Conf; +MidiMapConf G_MidiMap; +gdMainWindow *G_MainWin; + +#ifdef WITH_VST +PluginHost G_PluginHost; +#endif + + +void *videoThreadCb(void *arg); + + +int main(int argc, char **argv) +{ + G_quit = false; + + init_prepareParser(); + init_prepareMidiMap(); + init_prepareKernelAudio(); + init_prepareKernelMIDI(); + init_startGUI(argc, argv); + Fl::lock(); + pthread_create(&G_videoThread, NULL, videoThreadCb, NULL); + init_startKernelAudio(); + +#ifdef WITH_VST + juce::initialiseJuce_GUI(); +#endif + + int ret = Fl::run(); + +#ifdef WITH_VST + juce::shutdownJuce_GUI(); +#endif + + pthread_join(G_videoThread, NULL); + return ret; +} + + +void *videoThreadCb(void *arg) +{ + if (G_audio_status) + while (!G_quit) { + gu_refreshUI(); +#ifdef _WIN32 + Sleep(GUI_SLEEP); +#else + usleep(GUI_SLEEP); +#endif + } + pthread_exit(NULL); + return 0; +} diff --git a/src/utils/cocoa.h b/src/utils/cocoa.h new file mode 100644 index 0000000..7c064d0 --- /dev/null +++ b/src/utils/cocoa.h @@ -0,0 +1,38 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * cocoa + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +/* fl_xid() from FLTK returns a pointer to NSWindow, but plugins on OS X want a + * pointer to NSView. The function does the hard conversion. */ + +void *cocoa_getViewFromWindow(void *p); + +/* A bug on on OS X seems to misalign plugins' UI. The function takes care of + * fixing the positioning. */ + +void cocoa_setWindowSize(void *p, int w, int h); diff --git a/src/utils/cocoa.mm b/src/utils/cocoa.mm new file mode 100644 index 0000000..cdf3e76 --- /dev/null +++ b/src/utils/cocoa.mm @@ -0,0 +1,46 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * cocoa + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + + #import + #import + #import "cocoa.h" + + +void *cocoa_getViewFromWindow(void *p) +{ + NSWindow *win = (NSWindow *) p; + return (void*) [win contentView]; +} + + +void cocoa_setWindowSize(void *p, int w, int h) +{ + NSWindow *win = (NSWindow *) p; + [win setContentSize:NSMakeSize(w, h)]; +} diff --git a/src/utils/fs.cpp b/src/utils/fs.cpp new file mode 100644 index 0000000..23b2384 --- /dev/null +++ b/src/utils/fs.cpp @@ -0,0 +1,267 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * utils + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#if defined(_WIN32) // getcwd (unix) or __getcwd (win) + #include + #include +#else + #include +#endif + +#include +#include // stat (gu_dirExists) +#include +#include +#include +#include +#include +#include +#if defined(__APPLE__) + #include // basename unix + #include // getpwuid +#endif +#include "../core/const.h" +#include "string.h" +#include "log.h" +#include "fs.h" + + +using std::string; +using std::vector; + + +bool gu_fileExists(const string &filename) +{ + FILE *fh = fopen(filename.c_str(), "rb"); + if (!fh) { + return 0; + } + else { + fclose(fh); + return 1; + } +} + + +/* -------------------------------------------------------------------------- */ + + +bool gu_isDir(const string &path) +{ + bool ret; + +#if defined(__linux__) + + struct stat s1; + stat(path.c_str(), &s1); + ret = S_ISDIR(s1.st_mode); + +#elif defined(__APPLE__) + + if (strcmp(path.c_str(), "") == 0) + ret = false; + else { + struct stat s1; + stat(path.c_str(), &s1); + ret = S_ISDIR(s1.st_mode); + + /* check if ret is a bundle, a special OS X folder which must be + * shown as a regular file (VST). + * FIXME - consider native functions CFBundle... */ + + if (ret) { + if (gu_fileExists(path + "/Contents/Info.plist")) + ret = false; + } + } + +#elif defined(__WIN32) + + unsigned dwAttrib = GetFileAttributes(path.c_str()); + ret = (dwAttrib != INVALID_FILE_ATTRIBUTES && + (dwAttrib & FILE_ATTRIBUTE_DIRECTORY)); +#endif + + return ret & !gu_isProject(path); +} + + +/* -------------------------------------------------------------------------- */ + + +bool gu_dirExists(const string &path) +{ + struct stat st; + if (stat(path.c_str(), &st) != 0 && errno == ENOENT) + return false; + return true; +} + + +/* -------------------------------------------------------------------------- */ + + +bool gu_mkdir(const string &path) +{ +#if defined(__linux__) || defined(__APPLE__) + if (mkdir(path.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) == 0) +#else + if (_mkdir(path.c_str()) == 0) +#endif + return true; + return false; +} + + +/* -------------------------------------------------------------------------- */ + + +string gu_basename(const string &s) +{ + string out = s; + out.erase(0, out.find_last_of(G_SLASH_STR) + 1); + return out; +} + + +/* -------------------------------------------------------------------------- */ + + +string gu_dirname(const string &path) +{ + if (path.empty()) + return ""; + string out = path; + out.erase(out.find_last_of(G_SLASH_STR)); + return out; +} + + +/* -------------------------------------------------------------------------- */ + + +string gu_getCurrentPath() +{ + char buf[PATH_MAX]; +#if defined(__WIN32) + if (_getcwd(buf, PATH_MAX) != NULL) +#else + if (getcwd(buf, PATH_MAX) != NULL) +#endif + return buf; + else + return ""; +} + + +/* -------------------------------------------------------------------------- */ + + +string gu_getExt(const string &file) +{ + // TODO - use std functions + int len = strlen(file.c_str()); + int pos = len; + while (pos>0) { + if (file[pos] == '.') + break; + pos--; + } + if (pos==0) + return ""; + string out = file; + return out.substr(pos+1, len); +} + + +/* -------------------------------------------------------------------------- */ + + +string gu_stripExt(const string &s) +{ + return s.substr(0, s.find_last_of(".")); +} + + +/* -------------------------------------------------------------------------- */ + + +bool gu_isProject(const string &path) +{ + /** FIXME - checks too weak */ + + if (gu_getExt(path.c_str()) == "gprj" && gu_dirExists(path)) + return 1; + return 0; +} + + +/* -------------------------------------------------------------------------- */ + + +string gu_stripFileUrl(const string &f) +{ + string out = f; + out = gu_replace(out, "file://", ""); + out = gu_replace(out, "%20", " "); + return out; +} + + +/* -------------------------------------------------------------------------- */ + + +string gu_getHomePath() +{ + char path[PATH_MAX]; + +#if defined(__linux__) + + snprintf(path, PATH_MAX, "%s/.giada", getenv("HOME")); + +#elif defined(_WIN32) + + snprintf(path, PATH_MAX, "."); + +#elif defined(__APPLE__) + + struct passwd *p = getpwuid(getuid()); + if (p == NULL) { + gu_log("[gu_getHomePath] unable to fetch user infos\n"); + return ""; + } + else { + const char *home = p->pw_dir; + snprintf(path, PATH_MAX, "%s/Library/Application Support/Giada", home); + } + +#endif + + return string(path); +} diff --git a/src/utils/fs.h b/src/utils/fs.h new file mode 100644 index 0000000..abb9861 --- /dev/null +++ b/src/utils/fs.h @@ -0,0 +1,67 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * utils + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License 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 + + +using std::string; +using std::vector; + + +bool gu_fileExists(const string &path); + +bool gu_dirExists(const string &path); + +bool gu_isDir(const string &path); + +bool gu_isProject(const string &path); + +bool gu_mkdir(const string &path); + +string gu_getCurrentPath(); + +string gu_getHomePath(); + +string gu_basename(const string &s); + +string gu_dirname(const string &s); + +string gu_getExt(const string &s); + +string gu_stripExt(const string &s); + +string gu_stripFileUrl(const string &s); + + +#endif diff --git a/src/utils/gui.cpp b/src/utils/gui.cpp new file mode 100644 index 0000000..715126d --- /dev/null +++ b/src/utils/gui.cpp @@ -0,0 +1,222 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * gui_utils + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include "../core/mixer.h" +#include "../core/patch_DEPR_.h" +#include "../core/recorder.h" +#include "../core/wave.h" +#include "../core/pluginHost.h" +#include "../core/channel.h" +#include "../core/conf.h" +#include "../core/graphics.h" +#include "../gui/dialogs/gd_warnings.h" +#include "../gui/dialogs/gd_mainWindow.h" +#include "../gui/dialogs/gd_actionEditor.h" +#include "../gui/elems/ge_window.h" +#include "../gui/elems/mainWindow/mainIO.h" +#include "../gui/elems/mainWindow/mainTimer.h" +#include "../gui/elems/mainWindow/mainTransport.h" +#include "../gui/elems/mainWindow/beatMeter.h" +#include "../gui/elems/mainWindow/keyboard/keyboard.h" +#include "../gui/elems/mainWindow/keyboard/channel.h" +#include "log.h" +#include "string.h" +#include "gui.h" + + +extern Mixer G_Mixer; +extern unsigned G_beats; +extern bool G_audio_status; +extern Patch_DEPR_ G_patch; +extern Conf G_conf; +extern uint32_t G_time; +extern gdMainWindow *G_MainWin; +#ifdef WITH_VST +extern PluginHost G_PluginHost; +#endif + + +static int blinker = 0; + + +void gu_refreshUI() +{ + Fl::lock(); + + /* update dynamic elements: in and out meters, beat meter and + * each channel */ + + G_MainWin->mainIO->refresh(); + G_MainWin->beatMeter->redraw(); + G_MainWin->keyboard->refreshColumns(); + + /* compute timer for blinker */ + + blinker++; + if (blinker > 12) + blinker = 0; + + /* redraw GUI */ + + Fl::unlock(); + Fl::awake(); +} + + +/* -------------------------------------------------------------------------- */ + + +int gu_getBlinker() +{ + return blinker; +} + + +/* -------------------------------------------------------------------------- */ + + +void gu_updateControls() +{ + for (unsigned i=0; iguiChannel->update(); + + G_MainWin->mainIO->setOutVol(G_Mixer.outVol); + G_MainWin->mainIO->setInVol(G_Mixer.inVol); +#ifdef WITH_VST + G_MainWin->mainIO->setMasterFxOutFull(G_PluginHost.getStack(PluginHost::MASTER_OUT)->size() > 0); + G_MainWin->mainIO->setMasterFxInFull(G_PluginHost.getStack(PluginHost::MASTER_IN)->size() > 0); +#endif + + G_MainWin->mainTimer->setMeter(G_Mixer.beats, G_Mixer.bars); + G_MainWin->mainTimer->setBpm(G_Mixer.bpm); + + G_MainWin->mainTransport->updatePlay(G_Mixer.running); + G_MainWin->mainTransport->updateMetronome(G_Mixer.metronome); +} + + +/* -------------------------------------------------------------------------- */ + + +void gu_updateMainWinLabel(const string &s) +{ + std::string out = std::string(G_APP_NAME) + " - " + s; + G_MainWin->copy_label(out.c_str()); +} + + +/* -------------------------------------------------------------------------- */ + + +void gu_setFavicon(Fl_Window *w) +{ +#if defined(__linux__) + + fl_open_display(); + Pixmap p, mask; + XpmCreatePixmapFromData(fl_display, DefaultRootWindow(fl_display), + (char **) giada_icon, &p, &mask, 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)) { + gu_log("[GU] parent has subwindow with id=%d, deleting\n", id); + parent->delSubWindow(id); + } + child->setId(id); + parent->addSubWindow(child); +} + + +/* -------------------------------------------------------------------------- */ + + +void gu_refreshActionEditor() +{ + /** TODO - why don't we simply call WID_ACTION_EDITOR->redraw()? */ + + gdActionEditor *aeditor = (gdActionEditor*) G_MainWin->getChild(WID_ACTION_EDITOR); + if (aeditor) { + Channel *chan = aeditor->chan; + G_MainWin->delSubWindow(WID_ACTION_EDITOR); + gu_openSubWindow(G_MainWin, new gdActionEditor(chan), WID_ACTION_EDITOR); + } +} + + +/* -------------------------------------------------------------------------- */ + + +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 */ + + G_MainWin->delSubWindow(WID_ACTION_EDITOR); + G_MainWin->delSubWindow(WID_SAMPLE_EDITOR); + G_MainWin->delSubWindow(WID_FX_LIST); + G_MainWin->delSubWindow(WID_FX); +} + + +/* -------------------------------------------------------------------------- */ + + +string gu_removeFltkChars(const string &s) +{ + string out = gu_replace(s, "/", "-"); + out = gu_replace(out, "|", "-"); + out = gu_replace(out, "&", "-"); + out = gu_replace(out, "_", "-"); + return out; +} diff --git a/src/utils/gui.h b/src/utils/gui.h new file mode 100644 index 0000000..921c9ea --- /dev/null +++ b/src/utils/gui.h @@ -0,0 +1,99 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License 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 */ + +#if defined(_WIN32) + #include "../ext/resource.h" +#elif defined(__linux__) + #include +#endif + + +using std::string; + + +/* refresh + * refresh all GUI elements. */ + +void gu_refreshUI(); + +/* getBlinker +* return blinker value, used to make widgets blink. */ + +int gu_getBlinker(); + +/* updateControls + * update attributes of control elements (sample names, volumes, ...). + * Useful when loading a new patch. */ + +void gu_updateControls(); + +/* update_win_label + * update the name of the main window */ + +void gu_updateMainWinLabel(const string &s); + +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); + +/* removeFltkChars + * Strip special chars used by FLTK to split menus into sub-menus. */ + +string gu_removeFltkChars(const string &s); + +#endif diff --git a/src/utils/log.cpp b/src/utils/log.cpp new file mode 100644 index 0000000..3724f03 --- /dev/null +++ b/src/utils/log.cpp @@ -0,0 +1,90 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * log + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include +#include +#include +#include "../utils/fs.h" +#include "../core/const.h" +#include "log.h" + + +using std::string; + + +static FILE *f; +static int mode; +static bool stat; + + +int gu_logInit(int m) +{ + mode = m; + stat = true; + if (mode == LOG_MODE_FILE) { + string fpath = gu_getHomePath() + G_SLASH + "giada.log"; + f = fopen(fpath.c_str(), "a"); + if (!f) { + stat = false; + return 0; + } + } + return 1; +} + + +/* -------------------------------------------------------------------------- */ + + +void gu_logClose() +{ + if (mode == LOG_MODE_FILE) + fclose(f); +} + + +/* -------------------------------------------------------------------------- */ + + +void gu_log(const char *format, ...) +{ + if (mode == LOG_MODE_MUTE) + return; + va_list args; + va_start(args, format); + if (mode == LOG_MODE_FILE && stat == true) { + vfprintf(f, format, args); +#ifdef _WIN32 + fflush(f); +#endif + } + else + vprintf(format, args); + va_end(args); +} diff --git a/src/utils/log.h b/src/utils/log.h new file mode 100644 index 0000000..14ece29 --- /dev/null +++ b/src/utils/log.h @@ -0,0 +1,45 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * log + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License 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 gu_logInit(int mode); + +void gu_logClose(); + +void gu_log(const char *format, ...); + + +#endif diff --git a/src/utils/string.cpp b/src/utils/string.cpp new file mode 100644 index 0000000..603693e --- /dev/null +++ b/src/utils/string.cpp @@ -0,0 +1,113 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * utils + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License 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 "string.h" + + +using std::string; + + +string gu_getRealPath(const string &path) +{ + string out = ""; + +#if defined(__linux__) || defined(__APPLE__) + + char *buf = realpath(path.c_str(), NULL); + +#else // Windows + + char *buf = _fullpath(NULL, path.c_str(), PATH_MAX); + +#endif + + if (buf) { + out = buf; + free(buf); + } + return out; +} + + +/* -------------------------------------------------------------------------- */ + + +string gu_itoa(int i) +{ + std::stringstream out; + out << i; + return out.str(); +} + + +/* -------------------------------------------------------------------------- */ + + +string gu_trim(const string &s) +{ + std::size_t first = s.find_first_not_of(" \n\t"); + std::size_t last = s.find_last_not_of(" \n\t"); + return s.substr(first, last-first+1); +} + + +/* -------------------------------------------------------------------------- */ + + +string gu_replace(string in, const string &search, const string &replace) +{ + size_t pos = 0; + while ((pos = in.find(search, pos)) != string::npos) { + in.replace(pos, search.length(), replace); + pos += replace.length(); + } + return in; +} + + +/* -------------------------------------------------------------------------- */ + + +void gu_split(string in, string sep, vector *v) +{ + string full = in; + string token = ""; + size_t curr = 0; + size_t next = -1; + do { + curr = next + 1; + next = full.find_first_of(sep, curr); + token = full.substr(curr, next - curr); + if (token != "") + v->push_back(token); + } + while (next != string::npos); +} diff --git a/src/utils/string.h b/src/utils/string.h new file mode 100644 index 0000000..88df1af --- /dev/null +++ b/src/utils/string.h @@ -0,0 +1,53 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * utils + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License 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_STRING_H__ +#define __UTILS_STRING_H__ + + +#include +#include + + +using std::string; +using std::vector; + + +string gu_getRealPath(const string &path); + +string gu_replace(string in, const string &search, const string &replace); + +string gu_trim(const string &s); + +string gu_itoa(int i); + +void gu_split(string in, string sep, vector *v); + + +#endif diff --git a/tests/catch.hpp b/tests/catch.hpp new file mode 100644 index 0000000..de61226 --- /dev/null +++ b/tests/catch.hpp @@ -0,0 +1,9416 @@ +/* + * Catch v1.2.1 + * Generated: 2015-06-30 18:23:27.961086 + * ---------------------------------------------------------- + * This file has been merged from multiple headers. Please don't edit it directly + * Copyright (c) 2012 Two Blue Cubes Ltd. All rights reserved. + * + * Distributed under the Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + */ +#ifndef TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED +#define TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED + +#define TWOBLUECUBES_CATCH_HPP_INCLUDED + +#ifdef __clang__ +# pragma clang system_header +#elif defined __GNUC__ +# pragma GCC system_header +#endif + +// #included from: internal/catch_suppress_warnings.h + +#define TWOBLUECUBES_CATCH_SUPPRESS_WARNINGS_H_INCLUDED + +#ifdef __clang__ +# ifdef __ICC // icpc defines the __clang__ macro +# pragma warning(push) +# pragma warning(disable: 161 1682) +# else // __ICC +# pragma clang diagnostic ignored "-Wglobal-constructors" +# pragma clang diagnostic ignored "-Wvariadic-macros" +# pragma clang diagnostic ignored "-Wc99-extensions" +# pragma clang diagnostic ignored "-Wunused-variable" +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wpadded" +# pragma clang diagnostic ignored "-Wc++98-compat" +# pragma clang diagnostic ignored "-Wc++98-compat-pedantic" +# pragma clang diagnostic ignored "-Wswitch-enum" +# endif +#elif defined __GNUC__ +# pragma GCC diagnostic ignored "-Wvariadic-macros" +# pragma GCC diagnostic ignored "-Wunused-variable" +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wpadded" +#endif + +#if defined(CATCH_CONFIG_MAIN) || defined(CATCH_CONFIG_RUNNER) +# define CATCH_IMPL +#endif + +#ifdef CATCH_IMPL +# ifndef CLARA_CONFIG_MAIN +# define CLARA_CONFIG_MAIN_NOT_DEFINED +# define CLARA_CONFIG_MAIN +# endif +#endif + +// #included from: internal/catch_notimplemented_exception.h +#define TWOBLUECUBES_CATCH_NOTIMPLEMENTED_EXCEPTION_H_INCLUDED + +// #included from: catch_common.h +#define TWOBLUECUBES_CATCH_COMMON_H_INCLUDED + +#define INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) name##line +#define INTERNAL_CATCH_UNIQUE_NAME_LINE( name, line ) INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) +#define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __LINE__ ) + +#define INTERNAL_CATCH_STRINGIFY2( expr ) #expr +#define INTERNAL_CATCH_STRINGIFY( expr ) INTERNAL_CATCH_STRINGIFY2( expr ) + +#include +#include +#include + +// #included from: catch_compiler_capabilities.h +#define TWOBLUECUBES_CATCH_COMPILER_CAPABILITIES_HPP_INCLUDED + +// Detect a number of compiler features - mostly C++11/14 conformance - by compiler +// The following features are defined: +// +// CATCH_CONFIG_CPP11_NULLPTR : is nullptr supported? +// CATCH_CONFIG_CPP11_NOEXCEPT : is noexcept supported? +// CATCH_CONFIG_CPP11_GENERATED_METHODS : The delete and default keywords for compiler generated methods +// CATCH_CONFIG_CPP11_IS_ENUM : std::is_enum is supported? +// CATCH_CONFIG_CPP11_TUPLE : std::tuple is supported + +// CATCH_CONFIG_CPP11_OR_GREATER : Is C++11 supported? + +// CATCH_CONFIG_VARIADIC_MACROS : are variadic macros supported? + +// In general each macro has a _NO_ form +// (e.g. CATCH_CONFIG_CPP11_NO_NULLPTR) which disables the feature. +// Many features, at point of detection, define an _INTERNAL_ macro, so they +// can be combined, en-mass, with the _NO_ forms later. + +// All the C++11 features can be disabled with CATCH_CONFIG_NO_CPP11 + +#ifdef __clang__ + +# if __has_feature(cxx_nullptr) +# define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR +# endif + +# if __has_feature(cxx_noexcept) +# define CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT +# endif + +#endif // __clang__ + +//////////////////////////////////////////////////////////////////////////////// +// Borland +#ifdef __BORLANDC__ + +#endif // __BORLANDC__ + +//////////////////////////////////////////////////////////////////////////////// +// EDG +#ifdef __EDG_VERSION__ + +#endif // __EDG_VERSION__ + +//////////////////////////////////////////////////////////////////////////////// +// Digital Mars +#ifdef __DMC__ + +#endif // __DMC__ + +//////////////////////////////////////////////////////////////////////////////// +// GCC +#ifdef __GNUC__ + +#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6 && defined(__GXX_EXPERIMENTAL_CXX0X__) ) +# define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR +#endif + +#endif // __GNUC__ + +//////////////////////////////////////////////////////////////////////////////// +// Visual C++ +#ifdef _MSC_VER + +#if (_MSC_VER >= 1600) +# define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR +#endif + +#if (_MSC_VER >= 1900 ) // (VC++ 13 (VS2015)) +#define CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT +#define CATCH_INTERNAL_CONFIG_CPP11_GENERATED_METHODS +#endif + +#endif // _MSC_VER + +// Use variadic macros if the compiler supports them +#if ( defined _MSC_VER && _MSC_VER > 1400 && !defined __EDGE__) || \ + ( defined __WAVE__ && __WAVE_HAS_VARIADICS ) || \ + ( defined __GNUC__ && __GNUC__ >= 3 ) || \ + ( !defined __cplusplus && __STDC_VERSION__ >= 199901L || __cplusplus >= 201103L ) + +#define CATCH_INTERNAL_CONFIG_VARIADIC_MACROS + +#endif + +//////////////////////////////////////////////////////////////////////////////// +// C++ language feature support + +// catch all support for C++11 +#if (__cplusplus >= 201103L) + +# define CATCH_CPP11_OR_GREATER + +# if !defined(CATCH_INTERNAL_CONFIG_CPP11_NULLPTR) +# define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR +# endif + +# ifndef CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT +# define CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT +# endif + +# ifndef CATCH_INTERNAL_CONFIG_CPP11_GENERATED_METHODS +# define CATCH_INTERNAL_CONFIG_CPP11_GENERATED_METHODS +# endif + +# ifndef CATCH_INTERNAL_CONFIG_CPP11_IS_ENUM +# define CATCH_INTERNAL_CONFIG_CPP11_IS_ENUM +# endif + +# ifndef CATCH_INTERNAL_CONFIG_CPP11_TUPLE +# define CATCH_INTERNAL_CONFIG_CPP11_TUPLE +# endif + +# ifndef CATCH_INTERNAL_CONFIG_VARIADIC_MACROS +# define CATCH_INTERNAL_CONFIG_VARIADIC_MACROS +# endif + +#endif // __cplusplus >= 201103L + +// Now set the actual defines based on the above + anything the user has configured +#if defined(CATCH_INTERNAL_CONFIG_CPP11_NULLPTR) && !defined(CATCH_CONFIG_CPP11_NO_NULLPTR) && !defined(CATCH_CONFIG_CPP11_NULLPTR) && !defined(CATCH_CONFIG_NO_CPP11) +# define CATCH_CONFIG_CPP11_NULLPTR +#endif +#if defined(CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT) && !defined(CATCH_CONFIG_CPP11_NO_NOEXCEPT) && !defined(CATCH_CONFIG_CPP11_NOEXCEPT) && !defined(CATCH_CONFIG_NO_CPP11) +# define CATCH_CONFIG_CPP11_NOEXCEPT +#endif +#if defined(CATCH_INTERNAL_CONFIG_CPP11_GENERATED_METHODS) && !defined(CATCH_CONFIG_CPP11_NO_GENERATED_METHODS) && !defined(CATCH_CONFIG_CPP11_GENERATED_METHODS) && !defined(CATCH_CONFIG_NO_CPP11) +# define CATCH_CONFIG_CPP11_GENERATED_METHODS +#endif +#if defined(CATCH_INTERNAL_CONFIG_CPP11_IS_ENUM) && !defined(CATCH_CONFIG_CPP11_NO_IS_ENUM) && !defined(CATCH_CONFIG_CPP11_IS_ENUM) && !defined(CATCH_CONFIG_NO_CPP11) +# define CATCH_CONFIG_CPP11_IS_ENUM +#endif +#if defined(CATCH_INTERNAL_CONFIG_CPP11_TUPLE) && !defined(CATCH_CONFIG_CPP11_NO_TUPLE) && !defined(CATCH_CONFIG_CPP11_TUPLE) && !defined(CATCH_CONFIG_NO_CPP11) +# define CATCH_CONFIG_CPP11_TUPLE +#endif +#if defined(CATCH_INTERNAL_CONFIG_VARIADIC_MACROS) && !defined(CATCH_CONFIG_NO_VARIADIC_MACROS) && !defined(CATCH_CONFIG_VARIADIC_MACROS) +#define CATCH_CONFIG_VARIADIC_MACROS +#endif + +// noexcept support: +#if defined(CATCH_CONFIG_CPP11_NOEXCEPT) && !defined(CATCH_NOEXCEPT) +# define CATCH_NOEXCEPT noexcept +# define CATCH_NOEXCEPT_IS(x) noexcept(x) +#else +# define CATCH_NOEXCEPT throw() +# define CATCH_NOEXCEPT_IS(x) +#endif + +namespace Catch { + + class NonCopyable { +#ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS + NonCopyable( NonCopyable const& ) = delete; + NonCopyable( NonCopyable && ) = delete; + NonCopyable& operator = ( NonCopyable const& ) = delete; + NonCopyable& operator = ( NonCopyable && ) = delete; +#else + NonCopyable( NonCopyable const& info ); + NonCopyable& operator = ( NonCopyable const& ); +#endif + + protected: + NonCopyable() {} + virtual ~NonCopyable(); + }; + + class SafeBool { + public: + typedef void (SafeBool::*type)() const; + + static type makeSafe( bool value ) { + return value ? &SafeBool::trueValue : 0; + } + private: + void trueValue() const {} + }; + + template + inline void deleteAll( ContainerT& container ) { + typename ContainerT::const_iterator it = container.begin(); + typename ContainerT::const_iterator itEnd = container.end(); + for(; it != itEnd; ++it ) + delete *it; + } + template + inline void deleteAllValues( AssociativeContainerT& container ) { + typename AssociativeContainerT::const_iterator it = container.begin(); + typename AssociativeContainerT::const_iterator itEnd = container.end(); + for(; it != itEnd; ++it ) + delete it->second; + } + + bool startsWith( std::string const& s, std::string const& prefix ); + bool endsWith( std::string const& s, std::string const& suffix ); + bool contains( std::string const& s, std::string const& infix ); + void toLowerInPlace( std::string& s ); + std::string toLower( std::string const& s ); + std::string trim( std::string const& str ); + bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis ); + + struct pluralise { + pluralise( std::size_t count, std::string const& label ); + + friend std::ostream& operator << ( std::ostream& os, pluralise const& pluraliser ); + + std::size_t m_count; + std::string m_label; + }; + + struct SourceLineInfo { + + SourceLineInfo(); + SourceLineInfo( char const* _file, std::size_t _line ); + SourceLineInfo( SourceLineInfo const& other ); +# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS + SourceLineInfo( SourceLineInfo && ) = default; + SourceLineInfo& operator = ( SourceLineInfo const& ) = default; + SourceLineInfo& operator = ( SourceLineInfo && ) = default; +# endif + bool empty() const; + bool operator == ( SourceLineInfo const& other ) const; + bool operator < ( SourceLineInfo const& other ) const; + + std::string file; + std::size_t line; + }; + + std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ); + + // This is just here to avoid compiler warnings with macro constants and boolean literals + inline bool isTrue( bool value ){ return value; } + inline bool alwaysTrue() { return true; } + inline bool alwaysFalse() { return false; } + + void throwLogicError( std::string const& message, SourceLineInfo const& locationInfo ); + + // Use this in variadic streaming macros to allow + // >> +StreamEndStop + // as well as + // >> stuff +StreamEndStop + struct StreamEndStop { + std::string operator+() { + return std::string(); + } + }; + template + T const& operator + ( T const& value, StreamEndStop ) { + return value; + } +} + +#define CATCH_INTERNAL_LINEINFO ::Catch::SourceLineInfo( __FILE__, static_cast( __LINE__ ) ) +#define CATCH_INTERNAL_ERROR( msg ) ::Catch::throwLogicError( msg, CATCH_INTERNAL_LINEINFO ); + +#include + +namespace Catch { + + class NotImplementedException : public std::exception + { + public: + NotImplementedException( SourceLineInfo const& lineInfo ); + NotImplementedException( NotImplementedException const& ) {} + + virtual ~NotImplementedException() CATCH_NOEXCEPT {} + + virtual const char* what() const CATCH_NOEXCEPT; + + private: + std::string m_what; + SourceLineInfo m_lineInfo; + }; + +} // end namespace Catch + +/////////////////////////////////////////////////////////////////////////////// +#define CATCH_NOT_IMPLEMENTED throw Catch::NotImplementedException( CATCH_INTERNAL_LINEINFO ) + +// #included from: internal/catch_context.h +#define TWOBLUECUBES_CATCH_CONTEXT_H_INCLUDED + +// #included from: catch_interfaces_generators.h +#define TWOBLUECUBES_CATCH_INTERFACES_GENERATORS_H_INCLUDED + +#include + +namespace Catch { + + struct IGeneratorInfo { + virtual ~IGeneratorInfo(); + virtual bool moveNext() = 0; + virtual std::size_t getCurrentIndex() const = 0; + }; + + struct IGeneratorsForTest { + virtual ~IGeneratorsForTest(); + + virtual IGeneratorInfo& getGeneratorInfo( std::string const& fileInfo, std::size_t size ) = 0; + virtual bool moveNext() = 0; + }; + + IGeneratorsForTest* createGeneratorsForTest(); + +} // end namespace Catch + +// #included from: catch_ptr.hpp +#define TWOBLUECUBES_CATCH_PTR_HPP_INCLUDED + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + +namespace Catch { + + // An intrusive reference counting smart pointer. + // T must implement addRef() and release() methods + // typically implementing the IShared interface + template + class Ptr { + public: + Ptr() : m_p( NULL ){} + Ptr( T* p ) : m_p( p ){ + if( m_p ) + m_p->addRef(); + } + Ptr( Ptr const& other ) : m_p( other.m_p ){ + if( m_p ) + m_p->addRef(); + } + ~Ptr(){ + if( m_p ) + m_p->release(); + } + void reset() { + if( m_p ) + m_p->release(); + m_p = NULL; + } + Ptr& operator = ( T* p ){ + Ptr temp( p ); + swap( temp ); + return *this; + } + Ptr& operator = ( Ptr const& other ){ + Ptr temp( other ); + swap( temp ); + return *this; + } + void swap( Ptr& other ) { std::swap( m_p, other.m_p ); } + T* get() { return m_p; } + const T* get() const{ return m_p; } + T& operator*() const { return *m_p; } + T* operator->() const { return m_p; } + bool operator !() const { return m_p == NULL; } + operator SafeBool::type() const { return SafeBool::makeSafe( m_p != NULL ); } + + private: + T* m_p; + }; + + struct IShared : NonCopyable { + virtual ~IShared(); + virtual void addRef() const = 0; + virtual void release() const = 0; + }; + + template + struct SharedImpl : T { + + SharedImpl() : m_rc( 0 ){} + + virtual void addRef() const { + ++m_rc; + } + virtual void release() const { + if( --m_rc == 0 ) + delete this; + } + + mutable unsigned int m_rc; + }; + +} // end namespace Catch + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#include +#include +#include + +namespace Catch { + + class TestCase; + class Stream; + struct IResultCapture; + struct IRunner; + struct IGeneratorsForTest; + struct IConfig; + + struct IContext + { + virtual ~IContext(); + + virtual IResultCapture* getResultCapture() = 0; + virtual IRunner* getRunner() = 0; + virtual size_t getGeneratorIndex( std::string const& fileInfo, size_t totalSize ) = 0; + virtual bool advanceGeneratorsForCurrentTest() = 0; + virtual Ptr getConfig() const = 0; + }; + + struct IMutableContext : IContext + { + virtual ~IMutableContext(); + virtual void setResultCapture( IResultCapture* resultCapture ) = 0; + virtual void setRunner( IRunner* runner ) = 0; + virtual void setConfig( Ptr const& config ) = 0; + }; + + IContext& getCurrentContext(); + IMutableContext& getCurrentMutableContext(); + void cleanUpContext(); + Stream createStream( std::string const& streamName ); + +} + +// #included from: internal/catch_test_registry.hpp +#define TWOBLUECUBES_CATCH_TEST_REGISTRY_HPP_INCLUDED + +// #included from: catch_interfaces_testcase.h +#define TWOBLUECUBES_CATCH_INTERFACES_TESTCASE_H_INCLUDED + +#include + +namespace Catch { + + class TestSpec; + + struct ITestCase : IShared { + virtual void invoke () const = 0; + protected: + virtual ~ITestCase(); + }; + + class TestCase; + struct IConfig; + + struct ITestCaseRegistry { + virtual ~ITestCaseRegistry(); + virtual std::vector const& getAllTests() const = 0; + virtual void getFilteredTests( TestSpec const& testSpec, IConfig const& config, std::vector& matchingTestCases, bool negated = false ) const = 0; + + }; +} + +namespace Catch { + +template +class MethodTestCase : public SharedImpl { + +public: + MethodTestCase( void (C::*method)() ) : m_method( method ) {} + + virtual void invoke() const { + C obj; + (obj.*m_method)(); + } + +private: + virtual ~MethodTestCase() {} + + void (C::*m_method)(); +}; + +typedef void(*TestFunction)(); + +struct NameAndDesc { + NameAndDesc( const char* _name = "", const char* _description= "" ) + : name( _name ), description( _description ) + {} + + const char* name; + const char* description; +}; + +struct AutoReg { + + AutoReg( TestFunction function, + SourceLineInfo const& lineInfo, + NameAndDesc const& nameAndDesc ); + + template + AutoReg( void (C::*method)(), + char const* className, + NameAndDesc const& nameAndDesc, + SourceLineInfo const& lineInfo ) { + registerTestCase( new MethodTestCase( method ), + className, + nameAndDesc, + lineInfo ); + } + + void registerTestCase( ITestCase* testCase, + char const* className, + NameAndDesc const& nameAndDesc, + SourceLineInfo const& lineInfo ); + + ~AutoReg(); + +private: + AutoReg( AutoReg const& ); + void operator= ( AutoReg const& ); +}; + +} // end namespace Catch + +#ifdef CATCH_CONFIG_VARIADIC_MACROS + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_TESTCASE( ... ) \ + static void INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )(); \ + namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( __VA_ARGS__ ) ); }\ + static void INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )() + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_METHOD_AS_TEST_CASE( QualifiedMethod, ... ) \ + namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &QualifiedMethod, "&" #QualifiedMethod, Catch::NameAndDesc( __VA_ARGS__ ), CATCH_INTERNAL_LINEINFO ); } + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, ... )\ + namespace{ \ + struct INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ) : ClassName{ \ + void test(); \ + }; \ + Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar ) ( &INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )::test, #ClassName, Catch::NameAndDesc( __VA_ARGS__ ), CATCH_INTERNAL_LINEINFO ); \ + } \ + void INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )::test() + +#else + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_TESTCASE( Name, Desc ) \ + static void INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )(); \ + namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( Name, Desc ) ); }\ + static void INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )() + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_METHOD_AS_TEST_CASE( QualifiedMethod, Name, Desc ) \ + namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &QualifiedMethod, "&" #QualifiedMethod, Catch::NameAndDesc( Name, Desc ), CATCH_INTERNAL_LINEINFO ); } + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, TestName, Desc )\ + namespace{ \ + struct INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ) : ClassName{ \ + void test(); \ + }; \ + Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar ) ( &INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )::test, #ClassName, Catch::NameAndDesc( TestName, Desc ), CATCH_INTERNAL_LINEINFO ); \ + } \ + void INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )::test() + +#endif + +// #included from: internal/catch_capture.hpp +#define TWOBLUECUBES_CATCH_CAPTURE_HPP_INCLUDED + +// #included from: catch_result_builder.h +#define TWOBLUECUBES_CATCH_RESULT_BUILDER_H_INCLUDED + +// #included from: catch_result_type.h +#define TWOBLUECUBES_CATCH_RESULT_TYPE_H_INCLUDED + +namespace Catch { + + // ResultWas::OfType enum + struct ResultWas { enum OfType { + Unknown = -1, + Ok = 0, + Info = 1, + Warning = 2, + + FailureBit = 0x10, + + ExpressionFailed = FailureBit | 1, + ExplicitFailure = FailureBit | 2, + + Exception = 0x100 | FailureBit, + + ThrewException = Exception | 1, + DidntThrowException = Exception | 2, + + FatalErrorCondition = 0x200 | FailureBit + + }; }; + + inline bool isOk( ResultWas::OfType resultType ) { + return ( resultType & ResultWas::FailureBit ) == 0; + } + inline bool isJustInfo( int flags ) { + return flags == ResultWas::Info; + } + + // ResultDisposition::Flags enum + struct ResultDisposition { enum Flags { + Normal = 0x01, + + ContinueOnFailure = 0x02, // Failures fail test, but execution continues + FalseTest = 0x04, // Prefix expression with ! + SuppressFail = 0x08 // Failures are reported but do not fail the test + }; }; + + inline ResultDisposition::Flags operator | ( ResultDisposition::Flags lhs, ResultDisposition::Flags rhs ) { + return static_cast( static_cast( lhs ) | static_cast( rhs ) ); + } + + inline bool shouldContinueOnFailure( int flags ) { return ( flags & ResultDisposition::ContinueOnFailure ) != 0; } + inline bool isFalseTest( int flags ) { return ( flags & ResultDisposition::FalseTest ) != 0; } + inline bool shouldSuppressFailure( int flags ) { return ( flags & ResultDisposition::SuppressFail ) != 0; } + +} // end namespace Catch + +// #included from: catch_assertionresult.h +#define TWOBLUECUBES_CATCH_ASSERTIONRESULT_H_INCLUDED + +#include + +namespace Catch { + + struct AssertionInfo + { + AssertionInfo() {} + AssertionInfo( std::string const& _macroName, + SourceLineInfo const& _lineInfo, + std::string const& _capturedExpression, + ResultDisposition::Flags _resultDisposition ); + + std::string macroName; + SourceLineInfo lineInfo; + std::string capturedExpression; + ResultDisposition::Flags resultDisposition; + }; + + struct AssertionResultData + { + AssertionResultData() : resultType( ResultWas::Unknown ) {} + + std::string reconstructedExpression; + std::string message; + ResultWas::OfType resultType; + }; + + class AssertionResult { + public: + AssertionResult(); + AssertionResult( AssertionInfo const& info, AssertionResultData const& data ); + ~AssertionResult(); +# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS + AssertionResult( AssertionResult const& ) = default; + AssertionResult( AssertionResult && ) = default; + AssertionResult& operator = ( AssertionResult const& ) = default; + AssertionResult& operator = ( AssertionResult && ) = default; +# endif + + bool isOk() const; + bool succeeded() const; + ResultWas::OfType getResultType() const; + bool hasExpression() const; + bool hasMessage() const; + std::string getExpression() const; + std::string getExpressionInMacro() const; + bool hasExpandedExpression() const; + std::string getExpandedExpression() const; + std::string getMessage() const; + SourceLineInfo getSourceInfo() const; + std::string getTestMacroName() const; + + protected: + AssertionInfo m_info; + AssertionResultData m_resultData; + }; + +} // end namespace Catch + +namespace Catch { + + struct TestFailureException{}; + + template class ExpressionLhs; + + struct STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison; + + struct CopyableStream { + CopyableStream() {} + CopyableStream( CopyableStream const& other ) { + oss << other.oss.str(); + } + CopyableStream& operator=( CopyableStream const& other ) { + oss.str(""); + oss << other.oss.str(); + return *this; + } + std::ostringstream oss; + }; + + class ResultBuilder { + public: + ResultBuilder( char const* macroName, + SourceLineInfo const& lineInfo, + char const* capturedExpression, + ResultDisposition::Flags resultDisposition ); + + template + ExpressionLhs operator <= ( T const& operand ); + ExpressionLhs operator <= ( bool value ); + + template + ResultBuilder& operator << ( T const& value ) { + m_stream.oss << value; + return *this; + } + + template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator && ( RhsT const& ); + template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator || ( RhsT const& ); + + ResultBuilder& setResultType( ResultWas::OfType result ); + ResultBuilder& setResultType( bool result ); + ResultBuilder& setLhs( std::string const& lhs ); + ResultBuilder& setRhs( std::string const& rhs ); + ResultBuilder& setOp( std::string const& op ); + + void endExpression(); + + std::string reconstructExpression() const; + AssertionResult build() const; + + void useActiveException( ResultDisposition::Flags resultDisposition = ResultDisposition::Normal ); + void captureResult( ResultWas::OfType resultType ); + void captureExpression(); + void react(); + bool shouldDebugBreak() const; + bool allowThrows() const; + + private: + AssertionInfo m_assertionInfo; + AssertionResultData m_data; + struct ExprComponents { + ExprComponents() : testFalse( false ) {} + bool testFalse; + std::string lhs, rhs, op; + } m_exprComponents; + CopyableStream m_stream; + + bool m_shouldDebugBreak; + bool m_shouldThrow; + }; + +} // namespace Catch + +// Include after due to circular dependency: +// #included from: catch_expression_lhs.hpp +#define TWOBLUECUBES_CATCH_EXPRESSION_LHS_HPP_INCLUDED + +// #included from: catch_evaluate.hpp +#define TWOBLUECUBES_CATCH_EVALUATE_HPP_INCLUDED + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4389) // '==' : signed/unsigned mismatch +#endif + +#include + +namespace Catch { +namespace Internal { + + enum Operator { + IsEqualTo, + IsNotEqualTo, + IsLessThan, + IsGreaterThan, + IsLessThanOrEqualTo, + IsGreaterThanOrEqualTo + }; + + template struct OperatorTraits { static const char* getName(){ return "*error*"; } }; + template<> struct OperatorTraits { static const char* getName(){ return "=="; } }; + template<> struct OperatorTraits { static const char* getName(){ return "!="; } }; + template<> struct OperatorTraits { static const char* getName(){ return "<"; } }; + template<> struct OperatorTraits { static const char* getName(){ return ">"; } }; + template<> struct OperatorTraits { static const char* getName(){ return "<="; } }; + template<> struct OperatorTraits{ static const char* getName(){ return ">="; } }; + + template + inline T& opCast(T const& t) { return const_cast(t); } + +// nullptr_t support based on pull request #154 from Konstantin Baumann +#ifdef CATCH_CONFIG_CPP11_NULLPTR + inline std::nullptr_t opCast(std::nullptr_t) { return nullptr; } +#endif // CATCH_CONFIG_CPP11_NULLPTR + + // So the compare overloads can be operator agnostic we convey the operator as a template + // enum, which is used to specialise an Evaluator for doing the comparison. + template + class Evaluator{}; + + template + struct Evaluator { + static bool evaluate( T1 const& lhs, T2 const& rhs) { + return opCast( lhs ) == opCast( rhs ); + } + }; + template + struct Evaluator { + static bool evaluate( T1 const& lhs, T2 const& rhs ) { + return opCast( lhs ) != opCast( rhs ); + } + }; + template + struct Evaluator { + static bool evaluate( T1 const& lhs, T2 const& rhs ) { + return opCast( lhs ) < opCast( rhs ); + } + }; + template + struct Evaluator { + static bool evaluate( T1 const& lhs, T2 const& rhs ) { + return opCast( lhs ) > opCast( rhs ); + } + }; + template + struct Evaluator { + static bool evaluate( T1 const& lhs, T2 const& rhs ) { + return opCast( lhs ) >= opCast( rhs ); + } + }; + template + struct Evaluator { + static bool evaluate( T1 const& lhs, T2 const& rhs ) { + return opCast( lhs ) <= opCast( rhs ); + } + }; + + template + bool applyEvaluator( T1 const& lhs, T2 const& rhs ) { + return Evaluator::evaluate( lhs, rhs ); + } + + // This level of indirection allows us to specialise for integer types + // to avoid signed/ unsigned warnings + + // "base" overload + template + bool compare( T1 const& lhs, T2 const& rhs ) { + return Evaluator::evaluate( lhs, rhs ); + } + + // unsigned X to int + template bool compare( unsigned int lhs, int rhs ) { + return applyEvaluator( lhs, static_cast( rhs ) ); + } + template bool compare( unsigned long lhs, int rhs ) { + return applyEvaluator( lhs, static_cast( rhs ) ); + } + template bool compare( unsigned char lhs, int rhs ) { + return applyEvaluator( lhs, static_cast( rhs ) ); + } + + // unsigned X to long + template bool compare( unsigned int lhs, long rhs ) { + return applyEvaluator( lhs, static_cast( rhs ) ); + } + template bool compare( unsigned long lhs, long rhs ) { + return applyEvaluator( lhs, static_cast( rhs ) ); + } + template bool compare( unsigned char lhs, long rhs ) { + return applyEvaluator( lhs, static_cast( rhs ) ); + } + + // int to unsigned X + template bool compare( int lhs, unsigned int rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + template bool compare( int lhs, unsigned long rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + template bool compare( int lhs, unsigned char rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + + // long to unsigned X + template bool compare( long lhs, unsigned int rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + template bool compare( long lhs, unsigned long rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + template bool compare( long lhs, unsigned char rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + + // pointer to long (when comparing against NULL) + template bool compare( long lhs, T* rhs ) { + return Evaluator::evaluate( reinterpret_cast( lhs ), rhs ); + } + template bool compare( T* lhs, long rhs ) { + return Evaluator::evaluate( lhs, reinterpret_cast( rhs ) ); + } + + // pointer to int (when comparing against NULL) + template bool compare( int lhs, T* rhs ) { + return Evaluator::evaluate( reinterpret_cast( lhs ), rhs ); + } + template bool compare( T* lhs, int rhs ) { + return Evaluator::evaluate( lhs, reinterpret_cast( rhs ) ); + } + +#ifdef CATCH_CONFIG_CPP11_NULLPTR + // pointer to nullptr_t (when comparing against nullptr) + template bool compare( std::nullptr_t, T* rhs ) { + return Evaluator::evaluate( NULL, rhs ); + } + template bool compare( T* lhs, std::nullptr_t ) { + return Evaluator::evaluate( lhs, NULL ); + } +#endif // CATCH_CONFIG_CPP11_NULLPTR + +} // end of namespace Internal +} // end of namespace Catch + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +// #included from: catch_tostring.h +#define TWOBLUECUBES_CATCH_TOSTRING_H_INCLUDED + +#include +#include +#include +#include +#include + +#ifdef __OBJC__ +// #included from: catch_objc_arc.hpp +#define TWOBLUECUBES_CATCH_OBJC_ARC_HPP_INCLUDED + +#import + +#ifdef __has_feature +#define CATCH_ARC_ENABLED __has_feature(objc_arc) +#else +#define CATCH_ARC_ENABLED 0 +#endif + +void arcSafeRelease( NSObject* obj ); +id performOptionalSelector( id obj, SEL sel ); + +#if !CATCH_ARC_ENABLED +inline void arcSafeRelease( NSObject* obj ) { + [obj release]; +} +inline id performOptionalSelector( id obj, SEL sel ) { + if( [obj respondsToSelector: sel] ) + return [obj performSelector: sel]; + return nil; +} +#define CATCH_UNSAFE_UNRETAINED +#define CATCH_ARC_STRONG +#else +inline void arcSafeRelease( NSObject* ){} +inline id performOptionalSelector( id obj, SEL sel ) { +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Warc-performSelector-leaks" +#endif + if( [obj respondsToSelector: sel] ) + return [obj performSelector: sel]; +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + return nil; +} +#define CATCH_UNSAFE_UNRETAINED __unsafe_unretained +#define CATCH_ARC_STRONG __strong +#endif + +#endif + +#ifdef CATCH_CONFIG_CPP11_TUPLE +#include +#endif + +#ifdef CATCH_CONFIG_CPP11_IS_ENUM +#include +#endif + +namespace Catch { + +// Why we're here. +template +std::string toString( T const& value ); + +// Built in overloads + +std::string toString( std::string const& value ); +std::string toString( std::wstring const& value ); +std::string toString( const char* const value ); +std::string toString( char* const value ); +std::string toString( const wchar_t* const value ); +std::string toString( wchar_t* const value ); +std::string toString( int value ); +std::string toString( unsigned long value ); +std::string toString( unsigned int value ); +std::string toString( const double value ); +std::string toString( const float value ); +std::string toString( bool value ); +std::string toString( char value ); +std::string toString( signed char value ); +std::string toString( unsigned char value ); + +#ifdef CATCH_CONFIG_CPP11_NULLPTR +std::string toString( std::nullptr_t ); +#endif + +#ifdef __OBJC__ + std::string toString( NSString const * const& nsstring ); + std::string toString( NSString * CATCH_ARC_STRONG const& nsstring ); + std::string toString( NSObject* const& nsObject ); +#endif + +namespace Detail { + + extern std::string unprintableString; + + struct BorgType { + template BorgType( T const& ); + }; + + struct TrueType { char sizer[1]; }; + struct FalseType { char sizer[2]; }; + + TrueType& testStreamable( std::ostream& ); + FalseType testStreamable( FalseType ); + + FalseType operator<<( std::ostream const&, BorgType const& ); + + template + struct IsStreamInsertable { + static std::ostream &s; + static T const&t; + enum { value = sizeof( testStreamable(s << t) ) == sizeof( TrueType ) }; + }; + +#if defined(CATCH_CONFIG_CPP11_IS_ENUM) + template::value + > + struct EnumStringMaker + { + static std::string convert( T const& ) { return unprintableString; } + }; + + template + struct EnumStringMaker + { + static std::string convert( T const& v ) + { + return ::Catch::toString( + static_cast::type>(v) + ); + } + }; +#endif + template + struct StringMakerBase { +#if defined(CATCH_CONFIG_CPP11_IS_ENUM) + template + static std::string convert( T const& v ) + { + return EnumStringMaker::convert( v ); + } +#else + template + static std::string convert( T const& ) { return unprintableString; } +#endif + }; + + template<> + struct StringMakerBase { + template + static std::string convert( T const& _value ) { + std::ostringstream oss; + oss << _value; + return oss.str(); + } + }; + + std::string rawMemoryToString( const void *object, std::size_t size ); + + template + inline std::string rawMemoryToString( const T& object ) { + return rawMemoryToString( &object, sizeof(object) ); + } + +} // end namespace Detail + +template +struct StringMaker : + Detail::StringMakerBase::value> {}; + +template +struct StringMaker { + template + static std::string convert( U* p ) { + if( !p ) + return INTERNAL_CATCH_STRINGIFY( NULL ); + else + return Detail::rawMemoryToString( p ); + } +}; + +template +struct StringMaker { + static std::string convert( R C::* p ) { + if( !p ) + return INTERNAL_CATCH_STRINGIFY( NULL ); + else + return Detail::rawMemoryToString( p ); + } +}; + +namespace Detail { + template + std::string rangeToString( InputIterator first, InputIterator last ); +} + +//template +//struct StringMaker > { +// static std::string convert( std::vector const& v ) { +// return Detail::rangeToString( v.begin(), v.end() ); +// } +//}; + +template +std::string toString( std::vector const& v ) { + return Detail::rangeToString( v.begin(), v.end() ); +} + +#ifdef CATCH_CONFIG_CPP11_TUPLE + +// toString for tuples +namespace TupleDetail { + template< + typename Tuple, + std::size_t N = 0, + bool = (N < std::tuple_size::value) + > + struct ElementPrinter { + static void print( const Tuple& tuple, std::ostream& os ) + { + os << ( N ? ", " : " " ) + << Catch::toString(std::get(tuple)); + ElementPrinter::print(tuple,os); + } + }; + + template< + typename Tuple, + std::size_t N + > + struct ElementPrinter { + static void print( const Tuple&, std::ostream& ) {} + }; + +} + +template +struct StringMaker> { + + static std::string convert( const std::tuple& tuple ) + { + std::ostringstream os; + os << '{'; + TupleDetail::ElementPrinter>::print( tuple, os ); + os << " }"; + return os.str(); + } +}; +#endif // CATCH_CONFIG_CPP11_TUPLE + +namespace Detail { + template + std::string makeString( T const& value ) { + return StringMaker::convert( value ); + } +} // end namespace Detail + +/// \brief converts any type to a string +/// +/// The default template forwards on to ostringstream - except when an +/// ostringstream overload does not exist - in which case it attempts to detect +/// that and writes {?}. +/// Overload (not specialise) this template for custom typs that you don't want +/// to provide an ostream overload for. +template +std::string toString( T const& value ) { + return StringMaker::convert( value ); +} + + namespace Detail { + template + std::string rangeToString( InputIterator first, InputIterator last ) { + std::ostringstream oss; + oss << "{ "; + if( first != last ) { + oss << Catch::toString( *first ); + for( ++first ; first != last ; ++first ) + oss << ", " << Catch::toString( *first ); + } + oss << " }"; + return oss.str(); + } +} + +} // end namespace Catch + +namespace Catch { + +// Wraps the LHS of an expression and captures the operator and RHS (if any) - +// wrapping them all in a ResultBuilder object +template +class ExpressionLhs { + ExpressionLhs& operator = ( ExpressionLhs const& ); +# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS + ExpressionLhs& operator = ( ExpressionLhs && ) = delete; +# endif + +public: + ExpressionLhs( ResultBuilder& rb, T lhs ) : m_rb( rb ), m_lhs( lhs ) {} +# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS + ExpressionLhs( ExpressionLhs const& ) = default; + ExpressionLhs( ExpressionLhs && ) = default; +# endif + + template + ResultBuilder& operator == ( RhsT const& rhs ) { + return captureExpression( rhs ); + } + + template + ResultBuilder& operator != ( RhsT const& rhs ) { + return captureExpression( rhs ); + } + + template + ResultBuilder& operator < ( RhsT const& rhs ) { + return captureExpression( rhs ); + } + + template + ResultBuilder& operator > ( RhsT const& rhs ) { + return captureExpression( rhs ); + } + + template + ResultBuilder& operator <= ( RhsT const& rhs ) { + return captureExpression( rhs ); + } + + template + ResultBuilder& operator >= ( RhsT const& rhs ) { + return captureExpression( rhs ); + } + + ResultBuilder& operator == ( bool rhs ) { + return captureExpression( rhs ); + } + + ResultBuilder& operator != ( bool rhs ) { + return captureExpression( rhs ); + } + + void endExpression() { + bool value = m_lhs ? true : false; + m_rb + .setLhs( Catch::toString( value ) ) + .setResultType( value ) + .endExpression(); + } + + // Only simple binary expressions are allowed on the LHS. + // If more complex compositions are required then place the sub expression in parentheses + template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator + ( RhsT const& ); + template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator - ( RhsT const& ); + template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator / ( RhsT const& ); + template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator * ( RhsT const& ); + template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator && ( RhsT const& ); + template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator || ( RhsT const& ); + +private: + template + ResultBuilder& captureExpression( RhsT const& rhs ) { + return m_rb + .setResultType( Internal::compare( m_lhs, rhs ) ) + .setLhs( Catch::toString( m_lhs ) ) + .setRhs( Catch::toString( rhs ) ) + .setOp( Internal::OperatorTraits::getName() ); + } + +private: + ResultBuilder& m_rb; + T m_lhs; +}; + +} // end namespace Catch + + +namespace Catch { + + template + inline ExpressionLhs ResultBuilder::operator <= ( T const& operand ) { + return ExpressionLhs( *this, operand ); + } + + inline ExpressionLhs ResultBuilder::operator <= ( bool value ) { + return ExpressionLhs( *this, value ); + } + +} // namespace Catch + +// #included from: catch_message.h +#define TWOBLUECUBES_CATCH_MESSAGE_H_INCLUDED + +#include + +namespace Catch { + + struct MessageInfo { + MessageInfo( std::string const& _macroName, + SourceLineInfo const& _lineInfo, + ResultWas::OfType _type ); + + std::string macroName; + SourceLineInfo lineInfo; + ResultWas::OfType type; + std::string message; + unsigned int sequence; + + bool operator == ( MessageInfo const& other ) const { + return sequence == other.sequence; + } + bool operator < ( MessageInfo const& other ) const { + return sequence < other.sequence; + } + private: + static unsigned int globalCount; + }; + + struct MessageBuilder { + MessageBuilder( std::string const& macroName, + SourceLineInfo const& lineInfo, + ResultWas::OfType type ) + : m_info( macroName, lineInfo, type ) + {} + + template + MessageBuilder& operator << ( T const& value ) { + m_stream << value; + return *this; + } + + MessageInfo m_info; + std::ostringstream m_stream; + }; + + class ScopedMessage { + public: + ScopedMessage( MessageBuilder const& builder ); + ScopedMessage( ScopedMessage const& other ); + ~ScopedMessage(); + + MessageInfo m_info; + }; + +} // end namespace Catch + +// #included from: catch_interfaces_capture.h +#define TWOBLUECUBES_CATCH_INTERFACES_CAPTURE_H_INCLUDED + +#include + +namespace Catch { + + class TestCase; + class AssertionResult; + struct AssertionInfo; + struct SectionInfo; + struct MessageInfo; + class ScopedMessageBuilder; + struct Counts; + + struct IResultCapture { + + virtual ~IResultCapture(); + + virtual void assertionEnded( AssertionResult const& result ) = 0; + virtual bool sectionStarted( SectionInfo const& sectionInfo, + Counts& assertions ) = 0; + virtual void sectionEnded( SectionInfo const& name, Counts const& assertions, double _durationInSeconds ) = 0; + virtual void pushScopedMessage( MessageInfo const& message ) = 0; + virtual void popScopedMessage( MessageInfo const& message ) = 0; + + virtual std::string getCurrentTestName() const = 0; + virtual const AssertionResult* getLastResult() const = 0; + + virtual void handleFatalErrorCondition( std::string const& message ) = 0; + }; + + IResultCapture& getResultCapture(); +} + +// #included from: catch_debugger.h +#define TWOBLUECUBES_CATCH_DEBUGGER_H_INCLUDED + +// #included from: catch_platform.h +#define TWOBLUECUBES_CATCH_PLATFORM_H_INCLUDED + +#if defined(__MAC_OS_X_VERSION_MIN_REQUIRED) +#define CATCH_PLATFORM_MAC +#elif defined(__IPHONE_OS_VERSION_MIN_REQUIRED) +#define CATCH_PLATFORM_IPHONE +#elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) +#define CATCH_PLATFORM_WINDOWS +#endif + +#include + +namespace Catch{ + + bool isDebuggerActive(); + void writeToDebugConsole( std::string const& text ); +} + +#ifdef CATCH_PLATFORM_MAC + + // The following code snippet based on: + // http://cocoawithlove.com/2008/03/break-into-debugger.html + #ifdef DEBUG + #if defined(__ppc64__) || defined(__ppc__) + #define CATCH_BREAK_INTO_DEBUGGER() \ + if( Catch::isDebuggerActive() ) { \ + __asm__("li r0, 20\nsc\nnop\nli r0, 37\nli r4, 2\nsc\nnop\n" \ + : : : "memory","r0","r3","r4" ); \ + } + #else + #define CATCH_BREAK_INTO_DEBUGGER() if( Catch::isDebuggerActive() ) {__asm__("int $3\n" : : );} + #endif + #endif + +#elif defined(_MSC_VER) + #define CATCH_BREAK_INTO_DEBUGGER() if( Catch::isDebuggerActive() ) { __debugbreak(); } +#elif defined(__MINGW32__) + extern "C" __declspec(dllimport) void __stdcall DebugBreak(); + #define CATCH_BREAK_INTO_DEBUGGER() if( Catch::isDebuggerActive() ) { DebugBreak(); } +#endif + +#ifndef CATCH_BREAK_INTO_DEBUGGER +#define CATCH_BREAK_INTO_DEBUGGER() Catch::alwaysTrue(); +#endif + +// #included from: catch_interfaces_runner.h +#define TWOBLUECUBES_CATCH_INTERFACES_RUNNER_H_INCLUDED + +namespace Catch { + class TestCase; + + struct IRunner { + virtual ~IRunner(); + virtual bool aborting() const = 0; + }; +} + +/////////////////////////////////////////////////////////////////////////////// +// In the event of a failure works out if the debugger needs to be invoked +// and/or an exception thrown and takes appropriate action. +// This needs to be done as a macro so the debugger will stop in the user +// source code rather than in Catch library code +#define INTERNAL_CATCH_REACT( resultBuilder ) \ + if( resultBuilder.shouldDebugBreak() ) CATCH_BREAK_INTO_DEBUGGER(); \ + resultBuilder.react(); + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_TEST( expr, resultDisposition, macroName ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \ + try { \ + ( __catchResult <= expr ).endExpression(); \ + } \ + catch( ... ) { \ + __catchResult.useActiveException( Catch::ResultDisposition::Normal ); \ + } \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::isTrue( false && (expr) ) ) // expr here is never evaluated at runtime but it forces the compiler to give it a look + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_IF( expr, resultDisposition, macroName ) \ + INTERNAL_CATCH_TEST( expr, resultDisposition, macroName ); \ + if( Catch::getResultCapture().getLastResult()->succeeded() ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_ELSE( expr, resultDisposition, macroName ) \ + INTERNAL_CATCH_TEST( expr, resultDisposition, macroName ); \ + if( !Catch::getResultCapture().getLastResult()->succeeded() ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_NO_THROW( expr, resultDisposition, macroName ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \ + try { \ + expr; \ + __catchResult.captureResult( Catch::ResultWas::Ok ); \ + } \ + catch( ... ) { \ + __catchResult.useActiveException( resultDisposition ); \ + } \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::alwaysFalse() ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_THROWS( expr, resultDisposition, macroName ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \ + if( __catchResult.allowThrows() ) \ + try { \ + expr; \ + __catchResult.captureResult( Catch::ResultWas::DidntThrowException ); \ + } \ + catch( ... ) { \ + __catchResult.captureResult( Catch::ResultWas::Ok ); \ + } \ + else \ + __catchResult.captureResult( Catch::ResultWas::Ok ); \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::alwaysFalse() ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_THROWS_AS( expr, exceptionType, resultDisposition, macroName ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \ + if( __catchResult.allowThrows() ) \ + try { \ + expr; \ + __catchResult.captureResult( Catch::ResultWas::DidntThrowException ); \ + } \ + catch( exceptionType ) { \ + __catchResult.captureResult( Catch::ResultWas::Ok ); \ + } \ + catch( ... ) { \ + __catchResult.useActiveException( resultDisposition ); \ + } \ + else \ + __catchResult.captureResult( Catch::ResultWas::Ok ); \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::alwaysFalse() ) + +/////////////////////////////////////////////////////////////////////////////// +#ifdef CATCH_CONFIG_VARIADIC_MACROS + #define INTERNAL_CATCH_MSG( messageType, resultDisposition, macroName, ... ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, "", resultDisposition ); \ + __catchResult << __VA_ARGS__ + ::Catch::StreamEndStop(); \ + __catchResult.captureResult( messageType ); \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::alwaysFalse() ) +#else + #define INTERNAL_CATCH_MSG( messageType, resultDisposition, macroName, log ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, "", resultDisposition ); \ + __catchResult << log + ::Catch::StreamEndStop(); \ + __catchResult.captureResult( messageType ); \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::alwaysFalse() ) +#endif + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_INFO( log, macroName ) \ + Catch::ScopedMessage INTERNAL_CATCH_UNIQUE_NAME( scopedMessage ) = Catch::MessageBuilder( macroName, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info ) << log; + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CHECK_THAT( arg, matcher, resultDisposition, macroName ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #arg " " #matcher, resultDisposition ); \ + try { \ + std::string matcherAsString = ::Catch::Matchers::matcher.toString(); \ + __catchResult \ + .setLhs( Catch::toString( arg ) ) \ + .setRhs( matcherAsString == Catch::Detail::unprintableString ? #matcher : matcherAsString ) \ + .setOp( "matches" ) \ + .setResultType( ::Catch::Matchers::matcher.match( arg ) ); \ + __catchResult.captureExpression(); \ + } catch( ... ) { \ + __catchResult.useActiveException( resultDisposition | Catch::ResultDisposition::ContinueOnFailure ); \ + } \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::alwaysFalse() ) + +// #included from: internal/catch_section.h +#define TWOBLUECUBES_CATCH_SECTION_H_INCLUDED + +// #included from: catch_section_info.h +#define TWOBLUECUBES_CATCH_SECTION_INFO_H_INCLUDED + +namespace Catch { + + struct SectionInfo { + SectionInfo + ( SourceLineInfo const& _lineInfo, + std::string const& _name, + std::string const& _description = std::string() ); + + std::string name; + std::string description; + SourceLineInfo lineInfo; + }; + +} // end namespace Catch + +// #included from: catch_totals.hpp +#define TWOBLUECUBES_CATCH_TOTALS_HPP_INCLUDED + +#include + +namespace Catch { + + struct Counts { + Counts() : passed( 0 ), failed( 0 ), failedButOk( 0 ) {} + + Counts operator - ( Counts const& other ) const { + Counts diff; + diff.passed = passed - other.passed; + diff.failed = failed - other.failed; + diff.failedButOk = failedButOk - other.failedButOk; + return diff; + } + Counts& operator += ( Counts const& other ) { + passed += other.passed; + failed += other.failed; + failedButOk += other.failedButOk; + return *this; + } + + std::size_t total() const { + return passed + failed + failedButOk; + } + bool allPassed() const { + return failed == 0 && failedButOk == 0; + } + bool allOk() const { + return failed == 0; + } + + std::size_t passed; + std::size_t failed; + std::size_t failedButOk; + }; + + struct Totals { + + Totals operator - ( Totals const& other ) const { + Totals diff; + diff.assertions = assertions - other.assertions; + diff.testCases = testCases - other.testCases; + return diff; + } + + Totals delta( Totals const& prevTotals ) const { + Totals diff = *this - prevTotals; + if( diff.assertions.failed > 0 ) + ++diff.testCases.failed; + else if( diff.assertions.failedButOk > 0 ) + ++diff.testCases.failedButOk; + else + ++diff.testCases.passed; + return diff; + } + + Totals& operator += ( Totals const& other ) { + assertions += other.assertions; + testCases += other.testCases; + return *this; + } + + Counts assertions; + Counts testCases; + }; +} + +// #included from: catch_timer.h +#define TWOBLUECUBES_CATCH_TIMER_H_INCLUDED + +#ifdef CATCH_PLATFORM_WINDOWS +typedef unsigned long long uint64_t; +#else +#include +#endif + +namespace Catch { + + class Timer { + public: + Timer() : m_ticks( 0 ) {} + void start(); + unsigned int getElapsedMicroseconds() const; + unsigned int getElapsedMilliseconds() const; + double getElapsedSeconds() const; + + private: + uint64_t m_ticks; + }; + +} // namespace Catch + +#include + +namespace Catch { + + class Section : NonCopyable { + public: + Section( SectionInfo const& info ); + ~Section(); + + // This indicates whether the section should be executed or not + operator bool() const; + + private: + SectionInfo m_info; + + std::string m_name; + Counts m_assertions; + bool m_sectionIncluded; + Timer m_timer; + }; + +} // end namespace Catch + +#ifdef CATCH_CONFIG_VARIADIC_MACROS + #define INTERNAL_CATCH_SECTION( ... ) \ + if( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( catch_internal_Section ) = Catch::SectionInfo( CATCH_INTERNAL_LINEINFO, __VA_ARGS__ ) ) +#else + #define INTERNAL_CATCH_SECTION( name, desc ) \ + if( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( catch_internal_Section ) = Catch::SectionInfo( CATCH_INTERNAL_LINEINFO, name, desc ) ) +#endif + +// #included from: internal/catch_generators.hpp +#define TWOBLUECUBES_CATCH_GENERATORS_HPP_INCLUDED + +#include +#include +#include +#include + +namespace Catch { + +template +struct IGenerator { + virtual ~IGenerator() {} + virtual T getValue( std::size_t index ) const = 0; + virtual std::size_t size () const = 0; +}; + +template +class BetweenGenerator : public IGenerator { +public: + BetweenGenerator( T from, T to ) : m_from( from ), m_to( to ){} + + virtual T getValue( std::size_t index ) const { + return m_from+static_cast( index ); + } + + virtual std::size_t size() const { + return static_cast( 1+m_to-m_from ); + } + +private: + + T m_from; + T m_to; +}; + +template +class ValuesGenerator : public IGenerator { +public: + ValuesGenerator(){} + + void add( T value ) { + m_values.push_back( value ); + } + + virtual T getValue( std::size_t index ) const { + return m_values[index]; + } + + virtual std::size_t size() const { + return m_values.size(); + } + +private: + std::vector m_values; +}; + +template +class CompositeGenerator { +public: + CompositeGenerator() : m_totalSize( 0 ) {} + + // *** Move semantics, similar to auto_ptr *** + CompositeGenerator( CompositeGenerator& other ) + : m_fileInfo( other.m_fileInfo ), + m_totalSize( 0 ) + { + move( other ); + } + + CompositeGenerator& setFileInfo( const char* fileInfo ) { + m_fileInfo = fileInfo; + return *this; + } + + ~CompositeGenerator() { + deleteAll( m_composed ); + } + + operator T () const { + size_t overallIndex = getCurrentContext().getGeneratorIndex( m_fileInfo, m_totalSize ); + + typename std::vector*>::const_iterator it = m_composed.begin(); + typename std::vector*>::const_iterator itEnd = m_composed.end(); + for( size_t index = 0; it != itEnd; ++it ) + { + const IGenerator* generator = *it; + if( overallIndex >= index && overallIndex < index + generator->size() ) + { + return generator->getValue( overallIndex-index ); + } + index += generator->size(); + } + CATCH_INTERNAL_ERROR( "Indexed past end of generated range" ); + return T(); // Suppress spurious "not all control paths return a value" warning in Visual Studio - if you know how to fix this please do so + } + + void add( const IGenerator* generator ) { + m_totalSize += generator->size(); + m_composed.push_back( generator ); + } + + CompositeGenerator& then( CompositeGenerator& other ) { + move( other ); + return *this; + } + + CompositeGenerator& then( T value ) { + ValuesGenerator* valuesGen = new ValuesGenerator(); + valuesGen->add( value ); + add( valuesGen ); + return *this; + } + +private: + + void move( CompositeGenerator& other ) { + std::copy( other.m_composed.begin(), other.m_composed.end(), std::back_inserter( m_composed ) ); + m_totalSize += other.m_totalSize; + other.m_composed.clear(); + } + + std::vector*> m_composed; + std::string m_fileInfo; + size_t m_totalSize; +}; + +namespace Generators +{ + template + CompositeGenerator between( T from, T to ) { + CompositeGenerator generators; + generators.add( new BetweenGenerator( from, to ) ); + return generators; + } + + template + CompositeGenerator values( T val1, T val2 ) { + CompositeGenerator generators; + ValuesGenerator* valuesGen = new ValuesGenerator(); + valuesGen->add( val1 ); + valuesGen->add( val2 ); + generators.add( valuesGen ); + return generators; + } + + template + CompositeGenerator values( T val1, T val2, T val3 ){ + CompositeGenerator generators; + ValuesGenerator* valuesGen = new ValuesGenerator(); + valuesGen->add( val1 ); + valuesGen->add( val2 ); + valuesGen->add( val3 ); + generators.add( valuesGen ); + return generators; + } + + template + CompositeGenerator values( T val1, T val2, T val3, T val4 ) { + CompositeGenerator generators; + ValuesGenerator* valuesGen = new ValuesGenerator(); + valuesGen->add( val1 ); + valuesGen->add( val2 ); + valuesGen->add( val3 ); + valuesGen->add( val4 ); + generators.add( valuesGen ); + return generators; + } + +} // end namespace Generators + +using namespace Generators; + +} // end namespace Catch + +#define INTERNAL_CATCH_LINESTR2( line ) #line +#define INTERNAL_CATCH_LINESTR( line ) INTERNAL_CATCH_LINESTR2( line ) + +#define INTERNAL_CATCH_GENERATE( expr ) expr.setFileInfo( __FILE__ "(" INTERNAL_CATCH_LINESTR( __LINE__ ) ")" ) + +// #included from: internal/catch_interfaces_exception.h +#define TWOBLUECUBES_CATCH_INTERFACES_EXCEPTION_H_INCLUDED + +#include +// #included from: catch_interfaces_registry_hub.h +#define TWOBLUECUBES_CATCH_INTERFACES_REGISTRY_HUB_H_INCLUDED + +#include + +namespace Catch { + + class TestCase; + struct ITestCaseRegistry; + struct IExceptionTranslatorRegistry; + struct IExceptionTranslator; + struct IReporterRegistry; + struct IReporterFactory; + + struct IRegistryHub { + virtual ~IRegistryHub(); + + virtual IReporterRegistry const& getReporterRegistry() const = 0; + virtual ITestCaseRegistry const& getTestCaseRegistry() const = 0; + virtual IExceptionTranslatorRegistry& getExceptionTranslatorRegistry() = 0; + }; + + struct IMutableRegistryHub { + virtual ~IMutableRegistryHub(); + virtual void registerReporter( std::string const& name, IReporterFactory* factory ) = 0; + virtual void registerTest( TestCase const& testInfo ) = 0; + virtual void registerTranslator( const IExceptionTranslator* translator ) = 0; + }; + + IRegistryHub& getRegistryHub(); + IMutableRegistryHub& getMutableRegistryHub(); + void cleanUp(); + std::string translateActiveException(); + +} + + +namespace Catch { + + typedef std::string(*exceptionTranslateFunction)(); + + struct IExceptionTranslator { + virtual ~IExceptionTranslator(); + virtual std::string translate() const = 0; + }; + + struct IExceptionTranslatorRegistry { + virtual ~IExceptionTranslatorRegistry(); + + virtual std::string translateActiveException() const = 0; + }; + + class ExceptionTranslatorRegistrar { + template + class ExceptionTranslator : public IExceptionTranslator { + public: + + ExceptionTranslator( std::string(*translateFunction)( T& ) ) + : m_translateFunction( translateFunction ) + {} + + virtual std::string translate() const { + try { + throw; + } + catch( T& ex ) { + return m_translateFunction( ex ); + } + } + + protected: + std::string(*m_translateFunction)( T& ); + }; + + public: + template + ExceptionTranslatorRegistrar( std::string(*translateFunction)( T& ) ) { + getMutableRegistryHub().registerTranslator + ( new ExceptionTranslator( translateFunction ) ); + } + }; +} + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature ) \ + static std::string INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator )( signature ); \ + namespace{ Catch::ExceptionTranslatorRegistrar INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionRegistrar )( &INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator ) ); }\ + static std::string INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator )( signature ) + +// #included from: internal/catch_approx.hpp +#define TWOBLUECUBES_CATCH_APPROX_HPP_INCLUDED + +#include +#include + +namespace Catch { +namespace Detail { + + class Approx { + public: + explicit Approx ( double value ) + : m_epsilon( std::numeric_limits::epsilon()*100 ), + m_scale( 1.0 ), + m_value( value ) + {} + + Approx( Approx const& other ) + : m_epsilon( other.m_epsilon ), + m_scale( other.m_scale ), + m_value( other.m_value ) + {} + + static Approx custom() { + return Approx( 0 ); + } + + Approx operator()( double value ) { + Approx approx( value ); + approx.epsilon( m_epsilon ); + approx.scale( m_scale ); + return approx; + } + + friend bool operator == ( double lhs, Approx const& rhs ) { + // Thanks to Richard Harris for his help refining this formula + return fabs( lhs - rhs.m_value ) < rhs.m_epsilon * (rhs.m_scale + (std::max)( fabs(lhs), fabs(rhs.m_value) ) ); + } + + friend bool operator == ( Approx const& lhs, double rhs ) { + return operator==( rhs, lhs ); + } + + friend bool operator != ( double lhs, Approx const& rhs ) { + return !operator==( lhs, rhs ); + } + + friend bool operator != ( Approx const& lhs, double rhs ) { + return !operator==( rhs, lhs ); + } + + Approx& epsilon( double newEpsilon ) { + m_epsilon = newEpsilon; + return *this; + } + + Approx& scale( double newScale ) { + m_scale = newScale; + return *this; + } + + std::string toString() const { + std::ostringstream oss; + oss << "Approx( " << Catch::toString( m_value ) << " )"; + return oss.str(); + } + + private: + double m_epsilon; + double m_scale; + double m_value; + }; +} + +template<> +inline std::string toString( Detail::Approx const& value ) { + return value.toString(); +} + +} // end namespace Catch + +// #included from: internal/catch_matchers.hpp +#define TWOBLUECUBES_CATCH_MATCHERS_HPP_INCLUDED + +namespace Catch { +namespace Matchers { + namespace Impl { + + template + struct Matcher : SharedImpl + { + typedef ExpressionT ExpressionType; + + virtual ~Matcher() {} + virtual Ptr clone() const = 0; + virtual bool match( ExpressionT const& expr ) const = 0; + virtual std::string toString() const = 0; + }; + + template + struct MatcherImpl : Matcher { + + virtual Ptr > clone() const { + return Ptr >( new DerivedT( static_cast( *this ) ) ); + } + }; + + namespace Generic { + + template + class AllOf : public MatcherImpl, ExpressionT> { + public: + + AllOf() {} + AllOf( AllOf const& other ) : m_matchers( other.m_matchers ) {} + + AllOf& add( Matcher const& matcher ) { + m_matchers.push_back( matcher.clone() ); + return *this; + } + virtual bool match( ExpressionT const& expr ) const + { + for( std::size_t i = 0; i < m_matchers.size(); ++i ) + if( !m_matchers[i]->match( expr ) ) + return false; + return true; + } + virtual std::string toString() const { + std::ostringstream oss; + oss << "( "; + for( std::size_t i = 0; i < m_matchers.size(); ++i ) { + if( i != 0 ) + oss << " and "; + oss << m_matchers[i]->toString(); + } + oss << " )"; + return oss.str(); + } + + private: + std::vector > > m_matchers; + }; + + template + class AnyOf : public MatcherImpl, ExpressionT> { + public: + + AnyOf() {} + AnyOf( AnyOf const& other ) : m_matchers( other.m_matchers ) {} + + AnyOf& add( Matcher const& matcher ) { + m_matchers.push_back( matcher.clone() ); + return *this; + } + virtual bool match( ExpressionT const& expr ) const + { + for( std::size_t i = 0; i < m_matchers.size(); ++i ) + if( m_matchers[i]->match( expr ) ) + return true; + return false; + } + virtual std::string toString() const { + std::ostringstream oss; + oss << "( "; + for( std::size_t i = 0; i < m_matchers.size(); ++i ) { + if( i != 0 ) + oss << " or "; + oss << m_matchers[i]->toString(); + } + oss << " )"; + return oss.str(); + } + + private: + std::vector > > m_matchers; + }; + + } + + namespace StdString { + + inline std::string makeString( std::string const& str ) { return str; } + inline std::string makeString( const char* str ) { return str ? std::string( str ) : std::string(); } + + struct Equals : MatcherImpl { + Equals( std::string const& str ) : m_str( str ){} + Equals( Equals const& other ) : m_str( other.m_str ){} + + virtual ~Equals(); + + virtual bool match( std::string const& expr ) const { + return m_str == expr; + } + virtual std::string toString() const { + return "equals: \"" + m_str + "\""; + } + + std::string m_str; + }; + + struct Contains : MatcherImpl { + Contains( std::string const& substr ) : m_substr( substr ){} + Contains( Contains const& other ) : m_substr( other.m_substr ){} + + virtual ~Contains(); + + virtual bool match( std::string const& expr ) const { + return expr.find( m_substr ) != std::string::npos; + } + virtual std::string toString() const { + return "contains: \"" + m_substr + "\""; + } + + std::string m_substr; + }; + + struct StartsWith : MatcherImpl { + StartsWith( std::string const& substr ) : m_substr( substr ){} + StartsWith( StartsWith const& other ) : m_substr( other.m_substr ){} + + virtual ~StartsWith(); + + virtual bool match( std::string const& expr ) const { + return expr.find( m_substr ) == 0; + } + virtual std::string toString() const { + return "starts with: \"" + m_substr + "\""; + } + + std::string m_substr; + }; + + struct EndsWith : MatcherImpl { + EndsWith( std::string const& substr ) : m_substr( substr ){} + EndsWith( EndsWith const& other ) : m_substr( other.m_substr ){} + + virtual ~EndsWith(); + + virtual bool match( std::string const& expr ) const { + return expr.find( m_substr ) == expr.size() - m_substr.size(); + } + virtual std::string toString() const { + return "ends with: \"" + m_substr + "\""; + } + + std::string m_substr; + }; + } // namespace StdString + } // namespace Impl + + // The following functions create the actual matcher objects. + // This allows the types to be inferred + template + inline Impl::Generic::AllOf AllOf( Impl::Matcher const& m1, + Impl::Matcher const& m2 ) { + return Impl::Generic::AllOf().add( m1 ).add( m2 ); + } + template + inline Impl::Generic::AllOf AllOf( Impl::Matcher const& m1, + Impl::Matcher const& m2, + Impl::Matcher const& m3 ) { + return Impl::Generic::AllOf().add( m1 ).add( m2 ).add( m3 ); + } + template + inline Impl::Generic::AnyOf AnyOf( Impl::Matcher const& m1, + Impl::Matcher const& m2 ) { + return Impl::Generic::AnyOf().add( m1 ).add( m2 ); + } + template + inline Impl::Generic::AnyOf AnyOf( Impl::Matcher const& m1, + Impl::Matcher const& m2, + Impl::Matcher const& m3 ) { + return Impl::Generic::AnyOf().add( m1 ).add( m2 ).add( m3 ); + } + + inline Impl::StdString::Equals Equals( std::string const& str ) { + return Impl::StdString::Equals( str ); + } + inline Impl::StdString::Equals Equals( const char* str ) { + return Impl::StdString::Equals( Impl::StdString::makeString( str ) ); + } + inline Impl::StdString::Contains Contains( std::string const& substr ) { + return Impl::StdString::Contains( substr ); + } + inline Impl::StdString::Contains Contains( const char* substr ) { + return Impl::StdString::Contains( Impl::StdString::makeString( substr ) ); + } + inline Impl::StdString::StartsWith StartsWith( std::string const& substr ) { + return Impl::StdString::StartsWith( substr ); + } + inline Impl::StdString::StartsWith StartsWith( const char* substr ) { + return Impl::StdString::StartsWith( Impl::StdString::makeString( substr ) ); + } + inline Impl::StdString::EndsWith EndsWith( std::string const& substr ) { + return Impl::StdString::EndsWith( substr ); + } + inline Impl::StdString::EndsWith EndsWith( const char* substr ) { + return Impl::StdString::EndsWith( Impl::StdString::makeString( substr ) ); + } + +} // namespace Matchers + +using namespace Matchers; + +} // namespace Catch + +// #included from: internal/catch_interfaces_tag_alias_registry.h +#define TWOBLUECUBES_CATCH_INTERFACES_TAG_ALIAS_REGISTRY_H_INCLUDED + +// #included from: catch_tag_alias.h +#define TWOBLUECUBES_CATCH_TAG_ALIAS_H_INCLUDED + +#include + +namespace Catch { + + struct TagAlias { + TagAlias( std::string _tag, SourceLineInfo _lineInfo ) : tag( _tag ), lineInfo( _lineInfo ) {} + + std::string tag; + SourceLineInfo lineInfo; + }; + + struct RegistrarForTagAliases { + RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo ); + }; + +} // end namespace Catch + +#define CATCH_REGISTER_TAG_ALIAS( alias, spec ) namespace{ Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); } +// #included from: catch_option.hpp +#define TWOBLUECUBES_CATCH_OPTION_HPP_INCLUDED + +namespace Catch { + + // An optional type + template + class Option { + public: + Option() : nullableValue( NULL ) {} + Option( T const& _value ) + : nullableValue( new( storage ) T( _value ) ) + {} + Option( Option const& _other ) + : nullableValue( _other ? new( storage ) T( *_other ) : NULL ) + {} + + ~Option() { + reset(); + } + + Option& operator= ( Option const& _other ) { + if( &_other != this ) { + reset(); + if( _other ) + nullableValue = new( storage ) T( *_other ); + } + return *this; + } + Option& operator = ( T const& _value ) { + reset(); + nullableValue = new( storage ) T( _value ); + return *this; + } + + void reset() { + if( nullableValue ) + nullableValue->~T(); + nullableValue = NULL; + } + + T& operator*() { return *nullableValue; } + T const& operator*() const { return *nullableValue; } + T* operator->() { return nullableValue; } + const T* operator->() const { return nullableValue; } + + T valueOr( T const& defaultValue ) const { + return nullableValue ? *nullableValue : defaultValue; + } + + bool some() const { return nullableValue != NULL; } + bool none() const { return nullableValue == NULL; } + + bool operator !() const { return nullableValue == NULL; } + operator SafeBool::type() const { + return SafeBool::makeSafe( some() ); + } + + private: + T* nullableValue; + char storage[sizeof(T)]; + }; + +} // end namespace Catch + +namespace Catch { + + struct ITagAliasRegistry { + virtual ~ITagAliasRegistry(); + virtual Option find( std::string const& alias ) const = 0; + virtual std::string expandAliases( std::string const& unexpandedTestSpec ) const = 0; + + static ITagAliasRegistry const& get(); + }; + +} // end namespace Catch + +// These files are included here so the single_include script doesn't put them +// in the conditionally compiled sections +// #included from: internal/catch_test_case_info.h +#define TWOBLUECUBES_CATCH_TEST_CASE_INFO_H_INCLUDED + +#include +#include + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + +namespace Catch { + + struct ITestCase; + + struct TestCaseInfo { + enum SpecialProperties{ + None = 0, + IsHidden = 1 << 1, + ShouldFail = 1 << 2, + MayFail = 1 << 3, + Throws = 1 << 4 + }; + + TestCaseInfo( std::string const& _name, + std::string const& _className, + std::string const& _description, + std::set const& _tags, + SourceLineInfo const& _lineInfo ); + + TestCaseInfo( TestCaseInfo const& other ); + + bool isHidden() const; + bool throws() const; + bool okToFail() const; + bool expectedToFail() const; + + std::string name; + std::string className; + std::string description; + std::set tags; + std::set lcaseTags; + std::string tagsAsString; + SourceLineInfo lineInfo; + SpecialProperties properties; + }; + + class TestCase : public TestCaseInfo { + public: + + TestCase( ITestCase* testCase, TestCaseInfo const& info ); + TestCase( TestCase const& other ); + + TestCase withName( std::string const& _newName ) const; + + void invoke() const; + + TestCaseInfo const& getTestCaseInfo() const; + + void swap( TestCase& other ); + bool operator == ( TestCase const& other ) const; + bool operator < ( TestCase const& other ) const; + TestCase& operator = ( TestCase const& other ); + + private: + Ptr test; + }; + + TestCase makeTestCase( ITestCase* testCase, + std::string const& className, + std::string const& name, + std::string const& description, + SourceLineInfo const& lineInfo ); +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + + +#ifdef __OBJC__ +// #included from: internal/catch_objc.hpp +#define TWOBLUECUBES_CATCH_OBJC_HPP_INCLUDED + +#import + +#include + +// NB. Any general catch headers included here must be included +// in catch.hpp first to make sure they are included by the single +// header for non obj-usage + +/////////////////////////////////////////////////////////////////////////////// +// This protocol is really only here for (self) documenting purposes, since +// all its methods are optional. +@protocol OcFixture + +@optional + +-(void) setUp; +-(void) tearDown; + +@end + +namespace Catch { + + class OcMethod : public SharedImpl { + + public: + OcMethod( Class cls, SEL sel ) : m_cls( cls ), m_sel( sel ) {} + + virtual void invoke() const { + id obj = [[m_cls alloc] init]; + + performOptionalSelector( obj, @selector(setUp) ); + performOptionalSelector( obj, m_sel ); + performOptionalSelector( obj, @selector(tearDown) ); + + arcSafeRelease( obj ); + } + private: + virtual ~OcMethod() {} + + Class m_cls; + SEL m_sel; + }; + + namespace Detail{ + + inline std::string getAnnotation( Class cls, + std::string const& annotationName, + std::string const& testCaseName ) { + NSString* selStr = [[NSString alloc] initWithFormat:@"Catch_%s_%s", annotationName.c_str(), testCaseName.c_str()]; + SEL sel = NSSelectorFromString( selStr ); + arcSafeRelease( selStr ); + id value = performOptionalSelector( cls, sel ); + if( value ) + return [(NSString*)value UTF8String]; + return ""; + } + } + + inline size_t registerTestMethods() { + size_t noTestMethods = 0; + int noClasses = objc_getClassList( NULL, 0 ); + + Class* classes = (CATCH_UNSAFE_UNRETAINED Class *)malloc( sizeof(Class) * noClasses); + objc_getClassList( classes, noClasses ); + + for( int c = 0; c < noClasses; c++ ) { + Class cls = classes[c]; + { + u_int count; + Method* methods = class_copyMethodList( cls, &count ); + for( u_int m = 0; m < count ; m++ ) { + SEL selector = method_getName(methods[m]); + std::string methodName = sel_getName(selector); + if( startsWith( methodName, "Catch_TestCase_" ) ) { + std::string testCaseName = methodName.substr( 15 ); + std::string name = Detail::getAnnotation( cls, "Name", testCaseName ); + std::string desc = Detail::getAnnotation( cls, "Description", testCaseName ); + const char* className = class_getName( cls ); + + getMutableRegistryHub().registerTest( makeTestCase( new OcMethod( cls, selector ), className, name.c_str(), desc.c_str(), SourceLineInfo() ) ); + noTestMethods++; + } + } + free(methods); + } + } + return noTestMethods; + } + + namespace Matchers { + namespace Impl { + namespace NSStringMatchers { + + template + struct StringHolder : MatcherImpl{ + StringHolder( NSString* substr ) : m_substr( [substr copy] ){} + StringHolder( StringHolder const& other ) : m_substr( [other.m_substr copy] ){} + StringHolder() { + arcSafeRelease( m_substr ); + } + + NSString* m_substr; + }; + + struct Equals : StringHolder { + Equals( NSString* substr ) : StringHolder( substr ){} + + virtual bool match( ExpressionType const& str ) const { + return (str != nil || m_substr == nil ) && + [str isEqualToString:m_substr]; + } + + virtual std::string toString() const { + return "equals string: " + Catch::toString( m_substr ); + } + }; + + struct Contains : StringHolder { + Contains( NSString* substr ) : StringHolder( substr ){} + + virtual bool match( ExpressionType const& str ) const { + return (str != nil || m_substr == nil ) && + [str rangeOfString:m_substr].location != NSNotFound; + } + + virtual std::string toString() const { + return "contains string: " + Catch::toString( m_substr ); + } + }; + + struct StartsWith : StringHolder { + StartsWith( NSString* substr ) : StringHolder( substr ){} + + virtual bool match( ExpressionType const& str ) const { + return (str != nil || m_substr == nil ) && + [str rangeOfString:m_substr].location == 0; + } + + virtual std::string toString() const { + return "starts with: " + Catch::toString( m_substr ); + } + }; + struct EndsWith : StringHolder { + EndsWith( NSString* substr ) : StringHolder( substr ){} + + virtual bool match( ExpressionType const& str ) const { + return (str != nil || m_substr == nil ) && + [str rangeOfString:m_substr].location == [str length] - [m_substr length]; + } + + virtual std::string toString() const { + return "ends with: " + Catch::toString( m_substr ); + } + }; + + } // namespace NSStringMatchers + } // namespace Impl + + inline Impl::NSStringMatchers::Equals + Equals( NSString* substr ){ return Impl::NSStringMatchers::Equals( substr ); } + + inline Impl::NSStringMatchers::Contains + Contains( NSString* substr ){ return Impl::NSStringMatchers::Contains( substr ); } + + inline Impl::NSStringMatchers::StartsWith + StartsWith( NSString* substr ){ return Impl::NSStringMatchers::StartsWith( substr ); } + + inline Impl::NSStringMatchers::EndsWith + EndsWith( NSString* substr ){ return Impl::NSStringMatchers::EndsWith( substr ); } + + } // namespace Matchers + + using namespace Matchers; + +} // namespace Catch + +/////////////////////////////////////////////////////////////////////////////// +#define OC_TEST_CASE( name, desc )\ ++(NSString*) INTERNAL_CATCH_UNIQUE_NAME( Catch_Name_test ) \ +{\ +return @ name; \ +}\ ++(NSString*) INTERNAL_CATCH_UNIQUE_NAME( Catch_Description_test ) \ +{ \ +return @ desc; \ +} \ +-(void) INTERNAL_CATCH_UNIQUE_NAME( Catch_TestCase_test ) + +#endif + +#ifdef CATCH_IMPL +// #included from: internal/catch_impl.hpp +#define TWOBLUECUBES_CATCH_IMPL_HPP_INCLUDED + +// Collect all the implementation files together here +// These are the equivalent of what would usually be cpp files + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wweak-vtables" +#endif + +// #included from: ../catch_runner.hpp +#define TWOBLUECUBES_CATCH_RUNNER_HPP_INCLUDED + +// #included from: internal/catch_commandline.hpp +#define TWOBLUECUBES_CATCH_COMMANDLINE_HPP_INCLUDED + +// #included from: catch_config.hpp +#define TWOBLUECUBES_CATCH_CONFIG_HPP_INCLUDED + +// #included from: catch_test_spec_parser.hpp +#define TWOBLUECUBES_CATCH_TEST_SPEC_PARSER_HPP_INCLUDED + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + +// #included from: catch_test_spec.hpp +#define TWOBLUECUBES_CATCH_TEST_SPEC_HPP_INCLUDED + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + +#include +#include + +namespace Catch { + + class TestSpec { + struct Pattern : SharedImpl<> { + virtual ~Pattern(); + virtual bool matches( TestCaseInfo const& testCase ) const = 0; + }; + class NamePattern : public Pattern { + enum WildcardPosition { + NoWildcard = 0, + WildcardAtStart = 1, + WildcardAtEnd = 2, + WildcardAtBothEnds = WildcardAtStart | WildcardAtEnd + }; + + public: + NamePattern( std::string const& name ) : m_name( toLower( name ) ), m_wildcard( NoWildcard ) { + if( startsWith( m_name, "*" ) ) { + m_name = m_name.substr( 1 ); + m_wildcard = WildcardAtStart; + } + if( endsWith( m_name, "*" ) ) { + m_name = m_name.substr( 0, m_name.size()-1 ); + m_wildcard = static_cast( m_wildcard | WildcardAtEnd ); + } + } + virtual ~NamePattern(); + virtual bool matches( TestCaseInfo const& testCase ) const { + switch( m_wildcard ) { + case NoWildcard: + return m_name == toLower( testCase.name ); + case WildcardAtStart: + return endsWith( toLower( testCase.name ), m_name ); + case WildcardAtEnd: + return startsWith( toLower( testCase.name ), m_name ); + case WildcardAtBothEnds: + return contains( toLower( testCase.name ), m_name ); + } + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunreachable-code" +#endif + throw std::logic_error( "Unknown enum" ); +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + } + private: + std::string m_name; + WildcardPosition m_wildcard; + }; + class TagPattern : public Pattern { + public: + TagPattern( std::string const& tag ) : m_tag( toLower( tag ) ) {} + virtual ~TagPattern(); + virtual bool matches( TestCaseInfo const& testCase ) const { + return testCase.lcaseTags.find( m_tag ) != testCase.lcaseTags.end(); + } + private: + std::string m_tag; + }; + class ExcludedPattern : public Pattern { + public: + ExcludedPattern( Ptr const& underlyingPattern ) : m_underlyingPattern( underlyingPattern ) {} + virtual ~ExcludedPattern(); + virtual bool matches( TestCaseInfo const& testCase ) const { return !m_underlyingPattern->matches( testCase ); } + private: + Ptr m_underlyingPattern; + }; + + struct Filter { + std::vector > m_patterns; + + bool matches( TestCaseInfo const& testCase ) const { + // All patterns in a filter must match for the filter to be a match + for( std::vector >::const_iterator it = m_patterns.begin(), itEnd = m_patterns.end(); it != itEnd; ++it ) + if( !(*it)->matches( testCase ) ) + return false; + return true; + } + }; + + public: + bool hasFilters() const { + return !m_filters.empty(); + } + bool matches( TestCaseInfo const& testCase ) const { + // A TestSpec matches if any filter matches + for( std::vector::const_iterator it = m_filters.begin(), itEnd = m_filters.end(); it != itEnd; ++it ) + if( it->matches( testCase ) ) + return true; + return false; + } + + private: + std::vector m_filters; + + friend class TestSpecParser; + }; +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +namespace Catch { + + class TestSpecParser { + enum Mode{ None, Name, QuotedName, Tag }; + Mode m_mode; + bool m_exclusion; + std::size_t m_start, m_pos; + std::string m_arg; + TestSpec::Filter m_currentFilter; + TestSpec m_testSpec; + ITagAliasRegistry const* m_tagAliases; + + public: + TestSpecParser( ITagAliasRegistry const& tagAliases ) : m_tagAliases( &tagAliases ) {} + + TestSpecParser& parse( std::string const& arg ) { + m_mode = None; + m_exclusion = false; + m_start = std::string::npos; + m_arg = m_tagAliases->expandAliases( arg ); + for( m_pos = 0; m_pos < m_arg.size(); ++m_pos ) + visitChar( m_arg[m_pos] ); + if( m_mode == Name ) + addPattern(); + return *this; + } + TestSpec testSpec() { + addFilter(); + return m_testSpec; + } + private: + void visitChar( char c ) { + if( m_mode == None ) { + switch( c ) { + case ' ': return; + case '~': m_exclusion = true; return; + case '[': return startNewMode( Tag, ++m_pos ); + case '"': return startNewMode( QuotedName, ++m_pos ); + default: startNewMode( Name, m_pos ); break; + } + } + if( m_mode == Name ) { + if( c == ',' ) { + addPattern(); + addFilter(); + } + else if( c == '[' ) { + if( subString() == "exclude:" ) + m_exclusion = true; + else + addPattern(); + startNewMode( Tag, ++m_pos ); + } + } + else if( m_mode == QuotedName && c == '"' ) + addPattern(); + else if( m_mode == Tag && c == ']' ) + addPattern(); + } + void startNewMode( Mode mode, std::size_t start ) { + m_mode = mode; + m_start = start; + } + std::string subString() const { return m_arg.substr( m_start, m_pos - m_start ); } + template + void addPattern() { + std::string token = subString(); + if( startsWith( token, "exclude:" ) ) { + m_exclusion = true; + token = token.substr( 8 ); + } + if( !token.empty() ) { + Ptr pattern = new T( token ); + if( m_exclusion ) + pattern = new TestSpec::ExcludedPattern( pattern ); + m_currentFilter.m_patterns.push_back( pattern ); + } + m_exclusion = false; + m_mode = None; + } + void addFilter() { + if( !m_currentFilter.m_patterns.empty() ) { + m_testSpec.m_filters.push_back( m_currentFilter ); + m_currentFilter = TestSpec::Filter(); + } + } + }; + inline TestSpec parseTestSpec( std::string const& arg ) { + return TestSpecParser( ITagAliasRegistry::get() ).parse( arg ).testSpec(); + } + +} // namespace Catch + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +// #included from: catch_interfaces_config.h +#define TWOBLUECUBES_CATCH_INTERFACES_CONFIG_H_INCLUDED + +#include +#include +#include + +namespace Catch { + + struct Verbosity { enum Level { + NoOutput = 0, + Quiet, + Normal + }; }; + + struct WarnAbout { enum What { + Nothing = 0x00, + NoAssertions = 0x01 + }; }; + + struct ShowDurations { enum OrNot { + DefaultForReporter, + Always, + Never + }; }; + struct RunTests { enum InWhatOrder { + InDeclarationOrder, + InLexicographicalOrder, + InRandomOrder + }; }; + + class TestSpec; + + struct IConfig : IShared { + + virtual ~IConfig(); + + virtual bool allowThrows() const = 0; + virtual std::ostream& stream() const = 0; + virtual std::string name() const = 0; + virtual bool includeSuccessfulResults() const = 0; + virtual bool shouldDebugBreak() const = 0; + virtual bool warnAboutMissingAssertions() const = 0; + virtual int abortAfter() const = 0; + virtual bool showInvisibles() const = 0; + virtual ShowDurations::OrNot showDurations() const = 0; + virtual TestSpec const& testSpec() const = 0; + virtual RunTests::InWhatOrder runOrder() const = 0; + virtual unsigned int rngSeed() const = 0; + virtual bool forceColour() const = 0; + }; +} + +// #included from: catch_stream.h +#define TWOBLUECUBES_CATCH_STREAM_H_INCLUDED + +#include + +#ifdef __clang__ +#pragma clang diagnostic ignored "-Wpadded" +#endif + +namespace Catch { + + class Stream { + public: + Stream(); + Stream( std::streambuf* _streamBuf, bool _isOwned ); + void release(); + + std::streambuf* streamBuf; + + private: + bool isOwned; + }; + + std::ostream& cout(); + std::ostream& cerr(); +} + +#include +#include +#include +#include +#include + +#ifndef CATCH_CONFIG_CONSOLE_WIDTH +#define CATCH_CONFIG_CONSOLE_WIDTH 80 +#endif + +namespace Catch { + + struct ConfigData { + + ConfigData() + : listTests( false ), + listTags( false ), + listReporters( false ), + listTestNamesOnly( false ), + showSuccessfulTests( false ), + shouldDebugBreak( false ), + noThrow( false ), + showHelp( false ), + showInvisibles( false ), + forceColour( false ), + abortAfter( -1 ), + rngSeed( 0 ), + verbosity( Verbosity::Normal ), + warnings( WarnAbout::Nothing ), + showDurations( ShowDurations::DefaultForReporter ), + runOrder( RunTests::InDeclarationOrder ) + {} + + bool listTests; + bool listTags; + bool listReporters; + bool listTestNamesOnly; + + bool showSuccessfulTests; + bool shouldDebugBreak; + bool noThrow; + bool showHelp; + bool showInvisibles; + bool forceColour; + + int abortAfter; + unsigned int rngSeed; + + Verbosity::Level verbosity; + WarnAbout::What warnings; + ShowDurations::OrNot showDurations; + RunTests::InWhatOrder runOrder; + + std::string reporterName; + std::string outputFilename; + std::string name; + std::string processName; + + std::vector testsOrTags; + }; + + class Config : public SharedImpl { + private: + Config( Config const& other ); + Config& operator = ( Config const& other ); + virtual void dummy(); + public: + + Config() + : m_os( Catch::cout().rdbuf() ) + {} + + Config( ConfigData const& data ) + : m_data( data ), + m_os( Catch::cout().rdbuf() ) + { + if( !data.testsOrTags.empty() ) { + TestSpecParser parser( ITagAliasRegistry::get() ); + for( std::size_t i = 0; i < data.testsOrTags.size(); ++i ) + parser.parse( data.testsOrTags[i] ); + m_testSpec = parser.testSpec(); + } + } + + virtual ~Config() { + m_os.rdbuf( Catch::cout().rdbuf() ); + m_stream.release(); + } + + void setFilename( std::string const& filename ) { + m_data.outputFilename = filename; + } + + std::string const& getFilename() const { + return m_data.outputFilename ; + } + + bool listTests() const { return m_data.listTests; } + bool listTestNamesOnly() const { return m_data.listTestNamesOnly; } + bool listTags() const { return m_data.listTags; } + bool listReporters() const { return m_data.listReporters; } + + std::string getProcessName() const { return m_data.processName; } + + bool shouldDebugBreak() const { return m_data.shouldDebugBreak; } + + void setStreamBuf( std::streambuf* buf ) { + m_os.rdbuf( buf ? buf : Catch::cout().rdbuf() ); + } + + void useStream( std::string const& streamName ) { + Stream stream = createStream( streamName ); + setStreamBuf( stream.streamBuf ); + m_stream.release(); + m_stream = stream; + } + + std::string getReporterName() const { return m_data.reporterName; } + + int abortAfter() const { return m_data.abortAfter; } + + TestSpec const& testSpec() const { return m_testSpec; } + + bool showHelp() const { return m_data.showHelp; } + bool showInvisibles() const { return m_data.showInvisibles; } + + // IConfig interface + virtual bool allowThrows() const { return !m_data.noThrow; } + virtual std::ostream& stream() const { return m_os; } + virtual std::string name() const { return m_data.name.empty() ? m_data.processName : m_data.name; } + virtual bool includeSuccessfulResults() const { return m_data.showSuccessfulTests; } + virtual bool warnAboutMissingAssertions() const { return m_data.warnings & WarnAbout::NoAssertions; } + virtual ShowDurations::OrNot showDurations() const { return m_data.showDurations; } + virtual RunTests::InWhatOrder runOrder() const { return m_data.runOrder; } + virtual unsigned int rngSeed() const { return m_data.rngSeed; } + virtual bool forceColour() const { return m_data.forceColour; } + + private: + ConfigData m_data; + + Stream m_stream; + mutable std::ostream m_os; + TestSpec m_testSpec; + }; + +} // end namespace Catch + +// #included from: catch_clara.h +#define TWOBLUECUBES_CATCH_CLARA_H_INCLUDED + +// Use Catch's value for console width (store Clara's off to the side, if present) +#ifdef CLARA_CONFIG_CONSOLE_WIDTH +#define CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH CLARA_CONFIG_CONSOLE_WIDTH +#undef CLARA_CONFIG_CONSOLE_WIDTH +#endif +#define CLARA_CONFIG_CONSOLE_WIDTH CATCH_CONFIG_CONSOLE_WIDTH + +// Declare Clara inside the Catch namespace +#define STITCH_CLARA_OPEN_NAMESPACE namespace Catch { +// #included from: ../external/clara.h + +// Only use header guard if we are not using an outer namespace +#if !defined(TWOBLUECUBES_CLARA_H_INCLUDED) || defined(STITCH_CLARA_OPEN_NAMESPACE) + +#ifndef STITCH_CLARA_OPEN_NAMESPACE +#define TWOBLUECUBES_CLARA_H_INCLUDED +#define STITCH_CLARA_OPEN_NAMESPACE +#define STITCH_CLARA_CLOSE_NAMESPACE +#else +#define STITCH_CLARA_CLOSE_NAMESPACE } +#endif + +#define STITCH_TBC_TEXT_FORMAT_OPEN_NAMESPACE STITCH_CLARA_OPEN_NAMESPACE + +// ----------- #included from tbc_text_format.h ----------- + +// Only use header guard if we are not using an outer namespace +#if !defined(TBC_TEXT_FORMAT_H_INCLUDED) || defined(STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE) +#ifndef STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE +#define TBC_TEXT_FORMAT_H_INCLUDED +#endif + +#include +#include +#include + +// Use optional outer namespace +#ifdef STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE +namespace STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE { +#endif + +namespace Tbc { + +#ifdef TBC_TEXT_FORMAT_CONSOLE_WIDTH + const unsigned int consoleWidth = TBC_TEXT_FORMAT_CONSOLE_WIDTH; +#else + const unsigned int consoleWidth = 80; +#endif + + struct TextAttributes { + TextAttributes() + : initialIndent( std::string::npos ), + indent( 0 ), + width( consoleWidth-1 ), + tabChar( '\t' ) + {} + + TextAttributes& setInitialIndent( std::size_t _value ) { initialIndent = _value; return *this; } + TextAttributes& setIndent( std::size_t _value ) { indent = _value; return *this; } + TextAttributes& setWidth( std::size_t _value ) { width = _value; return *this; } + TextAttributes& setTabChar( char _value ) { tabChar = _value; return *this; } + + std::size_t initialIndent; // indent of first line, or npos + std::size_t indent; // indent of subsequent lines, or all if initialIndent is npos + std::size_t width; // maximum width of text, including indent. Longer text will wrap + char tabChar; // If this char is seen the indent is changed to current pos + }; + + class Text { + public: + Text( std::string const& _str, TextAttributes const& _attr = TextAttributes() ) + : attr( _attr ) + { + std::string wrappableChars = " [({.,/|\\-"; + std::size_t indent = _attr.initialIndent != std::string::npos + ? _attr.initialIndent + : _attr.indent; + std::string remainder = _str; + + while( !remainder.empty() ) { + if( lines.size() >= 1000 ) { + lines.push_back( "... message truncated due to excessive size" ); + return; + } + std::size_t tabPos = std::string::npos; + std::size_t width = (std::min)( remainder.size(), _attr.width - indent ); + std::size_t pos = remainder.find_first_of( '\n' ); + if( pos <= width ) { + width = pos; + } + pos = remainder.find_last_of( _attr.tabChar, width ); + if( pos != std::string::npos ) { + tabPos = pos; + if( remainder[width] == '\n' ) + width--; + remainder = remainder.substr( 0, tabPos ) + remainder.substr( tabPos+1 ); + } + + if( width == remainder.size() ) { + spliceLine( indent, remainder, width ); + } + else if( remainder[width] == '\n' ) { + spliceLine( indent, remainder, width ); + if( width <= 1 || remainder.size() != 1 ) + remainder = remainder.substr( 1 ); + indent = _attr.indent; + } + else { + pos = remainder.find_last_of( wrappableChars, width ); + if( pos != std::string::npos && pos > 0 ) { + spliceLine( indent, remainder, pos ); + if( remainder[0] == ' ' ) + remainder = remainder.substr( 1 ); + } + else { + spliceLine( indent, remainder, width-1 ); + lines.back() += "-"; + } + if( lines.size() == 1 ) + indent = _attr.indent; + if( tabPos != std::string::npos ) + indent += tabPos; + } + } + } + + void spliceLine( std::size_t _indent, std::string& _remainder, std::size_t _pos ) { + lines.push_back( std::string( _indent, ' ' ) + _remainder.substr( 0, _pos ) ); + _remainder = _remainder.substr( _pos ); + } + + typedef std::vector::const_iterator const_iterator; + + const_iterator begin() const { return lines.begin(); } + const_iterator end() const { return lines.end(); } + std::string const& last() const { return lines.back(); } + std::size_t size() const { return lines.size(); } + std::string const& operator[]( std::size_t _index ) const { return lines[_index]; } + std::string toString() const { + std::ostringstream oss; + oss << *this; + return oss.str(); + } + + inline friend std::ostream& operator << ( std::ostream& _stream, Text const& _text ) { + for( Text::const_iterator it = _text.begin(), itEnd = _text.end(); + it != itEnd; ++it ) { + if( it != _text.begin() ) + _stream << "\n"; + _stream << *it; + } + return _stream; + } + + private: + std::string str; + TextAttributes attr; + std::vector lines; + }; + +} // end namespace Tbc + +#ifdef STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE +} // end outer namespace +#endif + +#endif // TBC_TEXT_FORMAT_H_INCLUDED + +// ----------- end of #include from tbc_text_format.h ----------- +// ........... back in /Users/philnash/Dev/OSS/Clara/srcs/clara.h + +#undef STITCH_TBC_TEXT_FORMAT_OPEN_NAMESPACE + +#include +#include +#include +#include + +// Use optional outer namespace +#ifdef STITCH_CLARA_OPEN_NAMESPACE +STITCH_CLARA_OPEN_NAMESPACE +#endif + +namespace Clara { + + struct UnpositionalTag {}; + + extern UnpositionalTag _; + +#ifdef CLARA_CONFIG_MAIN + UnpositionalTag _; +#endif + + namespace Detail { + +#ifdef CLARA_CONSOLE_WIDTH + const unsigned int consoleWidth = CLARA_CONFIG_CONSOLE_WIDTH; +#else + const unsigned int consoleWidth = 80; +#endif + + using namespace Tbc; + + inline bool startsWith( std::string const& str, std::string const& prefix ) { + return str.size() >= prefix.size() && str.substr( 0, prefix.size() ) == prefix; + } + + template struct RemoveConstRef{ typedef T type; }; + template struct RemoveConstRef{ typedef T type; }; + template struct RemoveConstRef{ typedef T type; }; + template struct RemoveConstRef{ typedef T type; }; + + template struct IsBool { static const bool value = false; }; + template<> struct IsBool { static const bool value = true; }; + + template + void convertInto( std::string const& _source, T& _dest ) { + std::stringstream ss; + ss << _source; + ss >> _dest; + if( ss.fail() ) + throw std::runtime_error( "Unable to convert " + _source + " to destination type" ); + } + inline void convertInto( std::string const& _source, std::string& _dest ) { + _dest = _source; + } + inline void convertInto( std::string const& _source, bool& _dest ) { + std::string sourceLC = _source; + std::transform( sourceLC.begin(), sourceLC.end(), sourceLC.begin(), ::tolower ); + if( sourceLC == "y" || sourceLC == "1" || sourceLC == "true" || sourceLC == "yes" || sourceLC == "on" ) + _dest = true; + else if( sourceLC == "n" || sourceLC == "0" || sourceLC == "false" || sourceLC == "no" || sourceLC == "off" ) + _dest = false; + else + throw std::runtime_error( "Expected a boolean value but did not recognise:\n '" + _source + "'" ); + } + inline void convertInto( bool _source, bool& _dest ) { + _dest = _source; + } + template + inline void convertInto( bool, T& ) { + throw std::runtime_error( "Invalid conversion" ); + } + + template + struct IArgFunction { + virtual ~IArgFunction() {} +# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS + IArgFunction() = default; + IArgFunction( IArgFunction const& ) = default; +# endif + virtual void set( ConfigT& config, std::string const& value ) const = 0; + virtual void setFlag( ConfigT& config ) const = 0; + virtual bool takesArg() const = 0; + virtual IArgFunction* clone() const = 0; + }; + + template + class BoundArgFunction { + public: + BoundArgFunction() : functionObj( NULL ) {} + BoundArgFunction( IArgFunction* _functionObj ) : functionObj( _functionObj ) {} + BoundArgFunction( BoundArgFunction const& other ) : functionObj( other.functionObj ? other.functionObj->clone() : NULL ) {} + BoundArgFunction& operator = ( BoundArgFunction const& other ) { + IArgFunction* newFunctionObj = other.functionObj ? other.functionObj->clone() : NULL; + delete functionObj; + functionObj = newFunctionObj; + return *this; + } + ~BoundArgFunction() { delete functionObj; } + + void set( ConfigT& config, std::string const& value ) const { + functionObj->set( config, value ); + } + void setFlag( ConfigT& config ) const { + functionObj->setFlag( config ); + } + bool takesArg() const { return functionObj->takesArg(); } + + bool isSet() const { + return functionObj != NULL; + } + private: + IArgFunction* functionObj; + }; + + template + struct NullBinder : IArgFunction{ + virtual void set( C&, std::string const& ) const {} + virtual void setFlag( C& ) const {} + virtual bool takesArg() const { return true; } + virtual IArgFunction* clone() const { return new NullBinder( *this ); } + }; + + template + struct BoundDataMember : IArgFunction{ + BoundDataMember( M C::* _member ) : member( _member ) {} + virtual void set( C& p, std::string const& stringValue ) const { + convertInto( stringValue, p.*member ); + } + virtual void setFlag( C& p ) const { + convertInto( true, p.*member ); + } + virtual bool takesArg() const { return !IsBool::value; } + virtual IArgFunction* clone() const { return new BoundDataMember( *this ); } + M C::* member; + }; + template + struct BoundUnaryMethod : IArgFunction{ + BoundUnaryMethod( void (C::*_member)( M ) ) : member( _member ) {} + virtual void set( C& p, std::string const& stringValue ) const { + typename RemoveConstRef::type value; + convertInto( stringValue, value ); + (p.*member)( value ); + } + virtual void setFlag( C& p ) const { + typename RemoveConstRef::type value; + convertInto( true, value ); + (p.*member)( value ); + } + virtual bool takesArg() const { return !IsBool::value; } + virtual IArgFunction* clone() const { return new BoundUnaryMethod( *this ); } + void (C::*member)( M ); + }; + template + struct BoundNullaryMethod : IArgFunction{ + BoundNullaryMethod( void (C::*_member)() ) : member( _member ) {} + virtual void set( C& p, std::string const& stringValue ) const { + bool value; + convertInto( stringValue, value ); + if( value ) + (p.*member)(); + } + virtual void setFlag( C& p ) const { + (p.*member)(); + } + virtual bool takesArg() const { return false; } + virtual IArgFunction* clone() const { return new BoundNullaryMethod( *this ); } + void (C::*member)(); + }; + + template + struct BoundUnaryFunction : IArgFunction{ + BoundUnaryFunction( void (*_function)( C& ) ) : function( _function ) {} + virtual void set( C& obj, std::string const& stringValue ) const { + bool value; + convertInto( stringValue, value ); + if( value ) + function( obj ); + } + virtual void setFlag( C& p ) const { + function( p ); + } + virtual bool takesArg() const { return false; } + virtual IArgFunction* clone() const { return new BoundUnaryFunction( *this ); } + void (*function)( C& ); + }; + + template + struct BoundBinaryFunction : IArgFunction{ + BoundBinaryFunction( void (*_function)( C&, T ) ) : function( _function ) {} + virtual void set( C& obj, std::string const& stringValue ) const { + typename RemoveConstRef::type value; + convertInto( stringValue, value ); + function( obj, value ); + } + virtual void setFlag( C& obj ) const { + typename RemoveConstRef::type value; + convertInto( true, value ); + function( obj, value ); + } + virtual bool takesArg() const { return !IsBool::value; } + virtual IArgFunction* clone() const { return new BoundBinaryFunction( *this ); } + void (*function)( C&, T ); + }; + + } // namespace Detail + + struct Parser { + Parser() : separators( " \t=:" ) {} + + struct Token { + enum Type { Positional, ShortOpt, LongOpt }; + Token( Type _type, std::string const& _data ) : type( _type ), data( _data ) {} + Type type; + std::string data; + }; + + void parseIntoTokens( int argc, char const * const * argv, std::vector& tokens ) const { + const std::string doubleDash = "--"; + for( int i = 1; i < argc && argv[i] != doubleDash; ++i ) + parseIntoTokens( argv[i] , tokens); + } + void parseIntoTokens( std::string arg, std::vector& tokens ) const { + while( !arg.empty() ) { + Parser::Token token( Parser::Token::Positional, arg ); + arg = ""; + if( token.data[0] == '-' ) { + if( token.data.size() > 1 && token.data[1] == '-' ) { + token = Parser::Token( Parser::Token::LongOpt, token.data.substr( 2 ) ); + } + else { + token = Parser::Token( Parser::Token::ShortOpt, token.data.substr( 1 ) ); + if( token.data.size() > 1 && separators.find( token.data[1] ) == std::string::npos ) { + arg = "-" + token.data.substr( 1 ); + token.data = token.data.substr( 0, 1 ); + } + } + } + if( token.type != Parser::Token::Positional ) { + std::size_t pos = token.data.find_first_of( separators ); + if( pos != std::string::npos ) { + arg = token.data.substr( pos+1 ); + token.data = token.data.substr( 0, pos ); + } + } + tokens.push_back( token ); + } + } + std::string separators; + }; + + template + struct CommonArgProperties { + CommonArgProperties() {} + CommonArgProperties( Detail::BoundArgFunction const& _boundField ) : boundField( _boundField ) {} + + Detail::BoundArgFunction boundField; + std::string description; + std::string detail; + std::string placeholder; // Only value if boundField takes an arg + + bool takesArg() const { + return !placeholder.empty(); + } + void validate() const { + if( !boundField.isSet() ) + throw std::logic_error( "option not bound" ); + } + }; + struct OptionArgProperties { + std::vector shortNames; + std::string longName; + + bool hasShortName( std::string const& shortName ) const { + return std::find( shortNames.begin(), shortNames.end(), shortName ) != shortNames.end(); + } + bool hasLongName( std::string const& _longName ) const { + return _longName == longName; + } + }; + struct PositionalArgProperties { + PositionalArgProperties() : position( -1 ) {} + int position; // -1 means non-positional (floating) + + bool isFixedPositional() const { + return position != -1; + } + }; + + template + class CommandLine { + + struct Arg : CommonArgProperties, OptionArgProperties, PositionalArgProperties { + Arg() {} + Arg( Detail::BoundArgFunction const& _boundField ) : CommonArgProperties( _boundField ) {} + + using CommonArgProperties::placeholder; // !TBD + + std::string dbgName() const { + if( !longName.empty() ) + return "--" + longName; + if( !shortNames.empty() ) + return "-" + shortNames[0]; + return "positional args"; + } + std::string commands() const { + std::ostringstream oss; + bool first = true; + std::vector::const_iterator it = shortNames.begin(), itEnd = shortNames.end(); + for(; it != itEnd; ++it ) { + if( first ) + first = false; + else + oss << ", "; + oss << "-" << *it; + } + if( !longName.empty() ) { + if( !first ) + oss << ", "; + oss << "--" << longName; + } + if( !placeholder.empty() ) + oss << " <" << placeholder << ">"; + return oss.str(); + } + }; + + // NOTE: std::auto_ptr is deprecated in c++11/c++0x +#if defined(__cplusplus) && __cplusplus > 199711L + typedef std::unique_ptr ArgAutoPtr; +#else + typedef std::auto_ptr ArgAutoPtr; +#endif + + friend void addOptName( Arg& arg, std::string const& optName ) + { + if( optName.empty() ) + return; + if( Detail::startsWith( optName, "--" ) ) { + if( !arg.longName.empty() ) + throw std::logic_error( "Only one long opt may be specified. '" + + arg.longName + + "' already specified, now attempting to add '" + + optName + "'" ); + arg.longName = optName.substr( 2 ); + } + else if( Detail::startsWith( optName, "-" ) ) + arg.shortNames.push_back( optName.substr( 1 ) ); + else + throw std::logic_error( "option must begin with - or --. Option was: '" + optName + "'" ); + } + friend void setPositionalArg( Arg& arg, int position ) + { + arg.position = position; + } + + class ArgBuilder { + public: + ArgBuilder( Arg* arg ) : m_arg( arg ) {} + + // Bind a non-boolean data member (requires placeholder string) + template + void bind( M C::* field, std::string const& placeholder ) { + m_arg->boundField = new Detail::BoundDataMember( field ); + m_arg->placeholder = placeholder; + } + // Bind a boolean data member (no placeholder required) + template + void bind( bool C::* field ) { + m_arg->boundField = new Detail::BoundDataMember( field ); + } + + // Bind a method taking a single, non-boolean argument (requires a placeholder string) + template + void bind( void (C::* unaryMethod)( M ), std::string const& placeholder ) { + m_arg->boundField = new Detail::BoundUnaryMethod( unaryMethod ); + m_arg->placeholder = placeholder; + } + + // Bind a method taking a single, boolean argument (no placeholder string required) + template + void bind( void (C::* unaryMethod)( bool ) ) { + m_arg->boundField = new Detail::BoundUnaryMethod( unaryMethod ); + } + + // Bind a method that takes no arguments (will be called if opt is present) + template + void bind( void (C::* nullaryMethod)() ) { + m_arg->boundField = new Detail::BoundNullaryMethod( nullaryMethod ); + } + + // Bind a free function taking a single argument - the object to operate on (no placeholder string required) + template + void bind( void (* unaryFunction)( C& ) ) { + m_arg->boundField = new Detail::BoundUnaryFunction( unaryFunction ); + } + + // Bind a free function taking a single argument - the object to operate on (requires a placeholder string) + template + void bind( void (* binaryFunction)( C&, T ), std::string const& placeholder ) { + m_arg->boundField = new Detail::BoundBinaryFunction( binaryFunction ); + m_arg->placeholder = placeholder; + } + + ArgBuilder& describe( std::string const& description ) { + m_arg->description = description; + return *this; + } + ArgBuilder& detail( std::string const& detail ) { + m_arg->detail = detail; + return *this; + } + + protected: + Arg* m_arg; + }; + + class OptBuilder : public ArgBuilder { + public: + OptBuilder( Arg* arg ) : ArgBuilder( arg ) {} + OptBuilder( OptBuilder& other ) : ArgBuilder( other ) {} + + OptBuilder& operator[]( std::string const& optName ) { + addOptName( *ArgBuilder::m_arg, optName ); + return *this; + } + }; + + public: + + CommandLine() + : m_boundProcessName( new Detail::NullBinder() ), + m_highestSpecifiedArgPosition( 0 ), + m_throwOnUnrecognisedTokens( false ) + {} + CommandLine( CommandLine const& other ) + : m_boundProcessName( other.m_boundProcessName ), + m_options ( other.m_options ), + m_positionalArgs( other.m_positionalArgs ), + m_highestSpecifiedArgPosition( other.m_highestSpecifiedArgPosition ), + m_throwOnUnrecognisedTokens( other.m_throwOnUnrecognisedTokens ) + { + if( other.m_floatingArg.get() ) + m_floatingArg.reset( new Arg( *other.m_floatingArg ) ); + } + + CommandLine& setThrowOnUnrecognisedTokens( bool shouldThrow = true ) { + m_throwOnUnrecognisedTokens = shouldThrow; + return *this; + } + + OptBuilder operator[]( std::string const& optName ) { + m_options.push_back( Arg() ); + addOptName( m_options.back(), optName ); + OptBuilder builder( &m_options.back() ); + return builder; + } + + ArgBuilder operator[]( int position ) { + m_positionalArgs.insert( std::make_pair( position, Arg() ) ); + if( position > m_highestSpecifiedArgPosition ) + m_highestSpecifiedArgPosition = position; + setPositionalArg( m_positionalArgs[position], position ); + ArgBuilder builder( &m_positionalArgs[position] ); + return builder; + } + + // Invoke this with the _ instance + ArgBuilder operator[]( UnpositionalTag ) { + if( m_floatingArg.get() ) + throw std::logic_error( "Only one unpositional argument can be added" ); + m_floatingArg.reset( new Arg() ); + ArgBuilder builder( m_floatingArg.get() ); + return builder; + } + + template + void bindProcessName( M C::* field ) { + m_boundProcessName = new Detail::BoundDataMember( field ); + } + template + void bindProcessName( void (C::*_unaryMethod)( M ) ) { + m_boundProcessName = new Detail::BoundUnaryMethod( _unaryMethod ); + } + + void optUsage( std::ostream& os, std::size_t indent = 0, std::size_t width = Detail::consoleWidth ) const { + typename std::vector::const_iterator itBegin = m_options.begin(), itEnd = m_options.end(), it; + std::size_t maxWidth = 0; + for( it = itBegin; it != itEnd; ++it ) + maxWidth = (std::max)( maxWidth, it->commands().size() ); + + for( it = itBegin; it != itEnd; ++it ) { + Detail::Text usage( it->commands(), Detail::TextAttributes() + .setWidth( maxWidth+indent ) + .setIndent( indent ) ); + Detail::Text desc( it->description, Detail::TextAttributes() + .setWidth( width - maxWidth - 3 ) ); + + for( std::size_t i = 0; i < (std::max)( usage.size(), desc.size() ); ++i ) { + std::string usageCol = i < usage.size() ? usage[i] : ""; + os << usageCol; + + if( i < desc.size() && !desc[i].empty() ) + os << std::string( indent + 2 + maxWidth - usageCol.size(), ' ' ) + << desc[i]; + os << "\n"; + } + } + } + std::string optUsage() const { + std::ostringstream oss; + optUsage( oss ); + return oss.str(); + } + + void argSynopsis( std::ostream& os ) const { + for( int i = 1; i <= m_highestSpecifiedArgPosition; ++i ) { + if( i > 1 ) + os << " "; + typename std::map::const_iterator it = m_positionalArgs.find( i ); + if( it != m_positionalArgs.end() ) + os << "<" << it->second.placeholder << ">"; + else if( m_floatingArg.get() ) + os << "<" << m_floatingArg->placeholder << ">"; + else + throw std::logic_error( "non consecutive positional arguments with no floating args" ); + } + // !TBD No indication of mandatory args + if( m_floatingArg.get() ) { + if( m_highestSpecifiedArgPosition > 1 ) + os << " "; + os << "[<" << m_floatingArg->placeholder << "> ...]"; + } + } + std::string argSynopsis() const { + std::ostringstream oss; + argSynopsis( oss ); + return oss.str(); + } + + void usage( std::ostream& os, std::string const& procName ) const { + validate(); + os << "usage:\n " << procName << " "; + argSynopsis( os ); + if( !m_options.empty() ) { + os << " [options]\n\nwhere options are: \n"; + optUsage( os, 2 ); + } + os << "\n"; + } + std::string usage( std::string const& procName ) const { + std::ostringstream oss; + usage( oss, procName ); + return oss.str(); + } + + ConfigT parse( int argc, char const * const * argv ) const { + ConfigT config; + parseInto( argc, argv, config ); + return config; + } + + std::vector parseInto( int argc, char const * const * argv, ConfigT& config ) const { + std::string processName = argv[0]; + std::size_t lastSlash = processName.find_last_of( "/\\" ); + if( lastSlash != std::string::npos ) + processName = processName.substr( lastSlash+1 ); + m_boundProcessName.set( config, processName ); + std::vector tokens; + Parser parser; + parser.parseIntoTokens( argc, argv, tokens ); + return populate( tokens, config ); + } + + std::vector populate( std::vector const& tokens, ConfigT& config ) const { + validate(); + std::vector unusedTokens = populateOptions( tokens, config ); + unusedTokens = populateFixedArgs( unusedTokens, config ); + unusedTokens = populateFloatingArgs( unusedTokens, config ); + return unusedTokens; + } + + std::vector populateOptions( std::vector const& tokens, ConfigT& config ) const { + std::vector unusedTokens; + std::vector errors; + for( std::size_t i = 0; i < tokens.size(); ++i ) { + Parser::Token const& token = tokens[i]; + typename std::vector::const_iterator it = m_options.begin(), itEnd = m_options.end(); + for(; it != itEnd; ++it ) { + Arg const& arg = *it; + + try { + if( ( token.type == Parser::Token::ShortOpt && arg.hasShortName( token.data ) ) || + ( token.type == Parser::Token::LongOpt && arg.hasLongName( token.data ) ) ) { + if( arg.takesArg() ) { + if( i == tokens.size()-1 || tokens[i+1].type != Parser::Token::Positional ) + errors.push_back( "Expected argument to option: " + token.data ); + else + arg.boundField.set( config, tokens[++i].data ); + } + else { + arg.boundField.setFlag( config ); + } + break; + } + } + catch( std::exception& ex ) { + errors.push_back( std::string( ex.what() ) + "\n- while parsing: (" + arg.commands() + ")" ); + } + } + if( it == itEnd ) { + if( token.type == Parser::Token::Positional || !m_throwOnUnrecognisedTokens ) + unusedTokens.push_back( token ); + else if( errors.empty() && m_throwOnUnrecognisedTokens ) + errors.push_back( "unrecognised option: " + token.data ); + } + } + if( !errors.empty() ) { + std::ostringstream oss; + for( std::vector::const_iterator it = errors.begin(), itEnd = errors.end(); + it != itEnd; + ++it ) { + if( it != errors.begin() ) + oss << "\n"; + oss << *it; + } + throw std::runtime_error( oss.str() ); + } + return unusedTokens; + } + std::vector populateFixedArgs( std::vector const& tokens, ConfigT& config ) const { + std::vector unusedTokens; + int position = 1; + for( std::size_t i = 0; i < tokens.size(); ++i ) { + Parser::Token const& token = tokens[i]; + typename std::map::const_iterator it = m_positionalArgs.find( position ); + if( it != m_positionalArgs.end() ) + it->second.boundField.set( config, token.data ); + else + unusedTokens.push_back( token ); + if( token.type == Parser::Token::Positional ) + position++; + } + return unusedTokens; + } + std::vector populateFloatingArgs( std::vector const& tokens, ConfigT& config ) const { + if( !m_floatingArg.get() ) + return tokens; + std::vector unusedTokens; + for( std::size_t i = 0; i < tokens.size(); ++i ) { + Parser::Token const& token = tokens[i]; + if( token.type == Parser::Token::Positional ) + m_floatingArg->boundField.set( config, token.data ); + else + unusedTokens.push_back( token ); + } + return unusedTokens; + } + + void validate() const + { + if( m_options.empty() && m_positionalArgs.empty() && !m_floatingArg.get() ) + throw std::logic_error( "No options or arguments specified" ); + + for( typename std::vector::const_iterator it = m_options.begin(), + itEnd = m_options.end(); + it != itEnd; ++it ) + it->validate(); + } + + private: + Detail::BoundArgFunction m_boundProcessName; + std::vector m_options; + std::map m_positionalArgs; + ArgAutoPtr m_floatingArg; + int m_highestSpecifiedArgPosition; + bool m_throwOnUnrecognisedTokens; + }; + +} // end namespace Clara + +STITCH_CLARA_CLOSE_NAMESPACE +#undef STITCH_CLARA_OPEN_NAMESPACE +#undef STITCH_CLARA_CLOSE_NAMESPACE + +#endif // TWOBLUECUBES_CLARA_H_INCLUDED +#undef STITCH_CLARA_OPEN_NAMESPACE + +// Restore Clara's value for console width, if present +#ifdef CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH +#define CLARA_CONFIG_CONSOLE_WIDTH CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH +#undef CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH +#endif + +#include + +namespace Catch { + + inline void abortAfterFirst( ConfigData& config ) { config.abortAfter = 1; } + inline void abortAfterX( ConfigData& config, int x ) { + if( x < 1 ) + throw std::runtime_error( "Value after -x or --abortAfter must be greater than zero" ); + config.abortAfter = x; + } + inline void addTestOrTags( ConfigData& config, std::string const& _testSpec ) { config.testsOrTags.push_back( _testSpec ); } + + inline void addWarning( ConfigData& config, std::string const& _warning ) { + if( _warning == "NoAssertions" ) + config.warnings = static_cast( config.warnings | WarnAbout::NoAssertions ); + else + throw std::runtime_error( "Unrecognised warning: '" + _warning + "'" ); + } + inline void setOrder( ConfigData& config, std::string const& order ) { + if( startsWith( "declared", order ) ) + config.runOrder = RunTests::InDeclarationOrder; + else if( startsWith( "lexical", order ) ) + config.runOrder = RunTests::InLexicographicalOrder; + else if( startsWith( "random", order ) ) + config.runOrder = RunTests::InRandomOrder; + else + throw std::runtime_error( "Unrecognised ordering: '" + order + "'" ); + } + inline void setRngSeed( ConfigData& config, std::string const& seed ) { + if( seed == "time" ) { + config.rngSeed = static_cast( std::time(0) ); + } + else { + std::stringstream ss; + ss << seed; + ss >> config.rngSeed; + if( ss.fail() ) + throw std::runtime_error( "Argment to --rng-seed should be the word 'time' or a number" ); + } + } + inline void setVerbosity( ConfigData& config, int level ) { + // !TBD: accept strings? + config.verbosity = static_cast( level ); + } + inline void setShowDurations( ConfigData& config, bool _showDurations ) { + config.showDurations = _showDurations + ? ShowDurations::Always + : ShowDurations::Never; + } + inline void loadTestNamesFromFile( ConfigData& config, std::string const& _filename ) { + std::ifstream f( _filename.c_str() ); + if( !f.is_open() ) + throw std::domain_error( "Unable to load input file: " + _filename ); + + std::string line; + while( std::getline( f, line ) ) { + line = trim(line); + if( !line.empty() && !startsWith( line, "#" ) ) + addTestOrTags( config, "\"" + line + "\"," ); + } + } + + inline Clara::CommandLine makeCommandLineParser() { + + using namespace Clara; + CommandLine cli; + + cli.bindProcessName( &ConfigData::processName ); + + cli["-?"]["-h"]["--help"] + .describe( "display usage information" ) + .bind( &ConfigData::showHelp ); + + cli["-l"]["--list-tests"] + .describe( "list all/matching test cases" ) + .bind( &ConfigData::listTests ); + + cli["-t"]["--list-tags"] + .describe( "list all/matching tags" ) + .bind( &ConfigData::listTags ); + + cli["-s"]["--success"] + .describe( "include successful tests in output" ) + .bind( &ConfigData::showSuccessfulTests ); + + cli["-b"]["--break"] + .describe( "break into debugger on failure" ) + .bind( &ConfigData::shouldDebugBreak ); + + cli["-e"]["--nothrow"] + .describe( "skip exception tests" ) + .bind( &ConfigData::noThrow ); + + cli["-i"]["--invisibles"] + .describe( "show invisibles (tabs, newlines)" ) + .bind( &ConfigData::showInvisibles ); + + cli["-o"]["--out"] + .describe( "output filename" ) + .bind( &ConfigData::outputFilename, "filename" ); + + cli["-r"]["--reporter"] +// .placeholder( "name[:filename]" ) + .describe( "reporter to use (defaults to console)" ) + .bind( &ConfigData::reporterName, "name" ); + + cli["-n"]["--name"] + .describe( "suite name" ) + .bind( &ConfigData::name, "name" ); + + cli["-a"]["--abort"] + .describe( "abort at first failure" ) + .bind( &abortAfterFirst ); + + cli["-x"]["--abortx"] + .describe( "abort after x failures" ) + .bind( &abortAfterX, "no. failures" ); + + cli["-w"]["--warn"] + .describe( "enable warnings" ) + .bind( &addWarning, "warning name" ); + +// - needs updating if reinstated +// cli.into( &setVerbosity ) +// .describe( "level of verbosity (0=no output)" ) +// .shortOpt( "v") +// .longOpt( "verbosity" ) +// .placeholder( "level" ); + + cli[_] + .describe( "which test or tests to use" ) + .bind( &addTestOrTags, "test name, pattern or tags" ); + + cli["-d"]["--durations"] + .describe( "show test durations" ) + .bind( &setShowDurations, "yes/no" ); + + cli["-f"]["--input-file"] + .describe( "load test names to run from a file" ) + .bind( &loadTestNamesFromFile, "filename" ); + + // Less common commands which don't have a short form + cli["--list-test-names-only"] + .describe( "list all/matching test cases names only" ) + .bind( &ConfigData::listTestNamesOnly ); + + cli["--list-reporters"] + .describe( "list all reporters" ) + .bind( &ConfigData::listReporters ); + + cli["--order"] + .describe( "test case order (defaults to decl)" ) + .bind( &setOrder, "decl|lex|rand" ); + + cli["--rng-seed"] + .describe( "set a specific seed for random numbers" ) + .bind( &setRngSeed, "'time'|number" ); + + cli["--force-colour"] + .describe( "force colourised output" ) + .bind( &ConfigData::forceColour ); + + return cli; + } + +} // end namespace Catch + +// #included from: internal/catch_list.hpp +#define TWOBLUECUBES_CATCH_LIST_HPP_INCLUDED + +// #included from: catch_text.h +#define TWOBLUECUBES_CATCH_TEXT_H_INCLUDED + +#define TBC_TEXT_FORMAT_CONSOLE_WIDTH CATCH_CONFIG_CONSOLE_WIDTH + +#define CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE Catch +// #included from: ../external/tbc_text_format.h +// Only use header guard if we are not using an outer namespace +#ifndef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE +# ifdef TWOBLUECUBES_TEXT_FORMAT_H_INCLUDED +# ifndef TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED +# define TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED +# endif +# else +# define TWOBLUECUBES_TEXT_FORMAT_H_INCLUDED +# endif +#endif +#ifndef TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED +#include +#include +#include + +// Use optional outer namespace +#ifdef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE +namespace CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE { +#endif + +namespace Tbc { + +#ifdef TBC_TEXT_FORMAT_CONSOLE_WIDTH + const unsigned int consoleWidth = TBC_TEXT_FORMAT_CONSOLE_WIDTH; +#else + const unsigned int consoleWidth = 80; +#endif + + struct TextAttributes { + TextAttributes() + : initialIndent( std::string::npos ), + indent( 0 ), + width( consoleWidth-1 ), + tabChar( '\t' ) + {} + + TextAttributes& setInitialIndent( std::size_t _value ) { initialIndent = _value; return *this; } + TextAttributes& setIndent( std::size_t _value ) { indent = _value; return *this; } + TextAttributes& setWidth( std::size_t _value ) { width = _value; return *this; } + TextAttributes& setTabChar( char _value ) { tabChar = _value; return *this; } + + std::size_t initialIndent; // indent of first line, or npos + std::size_t indent; // indent of subsequent lines, or all if initialIndent is npos + std::size_t width; // maximum width of text, including indent. Longer text will wrap + char tabChar; // If this char is seen the indent is changed to current pos + }; + + class Text { + public: + Text( std::string const& _str, TextAttributes const& _attr = TextAttributes() ) + : attr( _attr ) + { + std::string wrappableChars = " [({.,/|\\-"; + std::size_t indent = _attr.initialIndent != std::string::npos + ? _attr.initialIndent + : _attr.indent; + std::string remainder = _str; + + while( !remainder.empty() ) { + if( lines.size() >= 1000 ) { + lines.push_back( "... message truncated due to excessive size" ); + return; + } + std::size_t tabPos = std::string::npos; + std::size_t width = (std::min)( remainder.size(), _attr.width - indent ); + std::size_t pos = remainder.find_first_of( '\n' ); + if( pos <= width ) { + width = pos; + } + pos = remainder.find_last_of( _attr.tabChar, width ); + if( pos != std::string::npos ) { + tabPos = pos; + if( remainder[width] == '\n' ) + width--; + remainder = remainder.substr( 0, tabPos ) + remainder.substr( tabPos+1 ); + } + + if( width == remainder.size() ) { + spliceLine( indent, remainder, width ); + } + else if( remainder[width] == '\n' ) { + spliceLine( indent, remainder, width ); + if( width <= 1 || remainder.size() != 1 ) + remainder = remainder.substr( 1 ); + indent = _attr.indent; + } + else { + pos = remainder.find_last_of( wrappableChars, width ); + if( pos != std::string::npos && pos > 0 ) { + spliceLine( indent, remainder, pos ); + if( remainder[0] == ' ' ) + remainder = remainder.substr( 1 ); + } + else { + spliceLine( indent, remainder, width-1 ); + lines.back() += "-"; + } + if( lines.size() == 1 ) + indent = _attr.indent; + if( tabPos != std::string::npos ) + indent += tabPos; + } + } + } + + void spliceLine( std::size_t _indent, std::string& _remainder, std::size_t _pos ) { + lines.push_back( std::string( _indent, ' ' ) + _remainder.substr( 0, _pos ) ); + _remainder = _remainder.substr( _pos ); + } + + typedef std::vector::const_iterator const_iterator; + + const_iterator begin() const { return lines.begin(); } + const_iterator end() const { return lines.end(); } + std::string const& last() const { return lines.back(); } + std::size_t size() const { return lines.size(); } + std::string const& operator[]( std::size_t _index ) const { return lines[_index]; } + std::string toString() const { + std::ostringstream oss; + oss << *this; + return oss.str(); + } + + inline friend std::ostream& operator << ( std::ostream& _stream, Text const& _text ) { + for( Text::const_iterator it = _text.begin(), itEnd = _text.end(); + it != itEnd; ++it ) { + if( it != _text.begin() ) + _stream << "\n"; + _stream << *it; + } + return _stream; + } + + private: + std::string str; + TextAttributes attr; + std::vector lines; + }; + +} // end namespace Tbc + +#ifdef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE +} // end outer namespace +#endif + +#endif // TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED +#undef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE + +namespace Catch { + using Tbc::Text; + using Tbc::TextAttributes; +} + +// #included from: catch_console_colour.hpp +#define TWOBLUECUBES_CATCH_CONSOLE_COLOUR_HPP_INCLUDED + +namespace Catch { + + struct Colour { + enum Code { + None = 0, + + White, + Red, + Green, + Blue, + Cyan, + Yellow, + Grey, + + Bright = 0x10, + + BrightRed = Bright | Red, + BrightGreen = Bright | Green, + LightGrey = Bright | Grey, + BrightWhite = Bright | White, + + // By intention + FileName = LightGrey, + Warning = Yellow, + ResultError = BrightRed, + ResultSuccess = BrightGreen, + ResultExpectedFailure = Warning, + + Error = BrightRed, + Success = Green, + + OriginalExpression = Cyan, + ReconstructedExpression = Yellow, + + SecondaryText = LightGrey, + Headers = White + }; + + // Use constructed object for RAII guard + Colour( Code _colourCode ); + Colour( Colour const& other ); + ~Colour(); + + // Use static method for one-shot changes + static void use( Code _colourCode ); + + private: + bool m_moved; + }; + + inline std::ostream& operator << ( std::ostream& os, Colour const& ) { return os; } + +} // end namespace Catch + +// #included from: catch_interfaces_reporter.h +#define TWOBLUECUBES_CATCH_INTERFACES_REPORTER_H_INCLUDED + +#include +#include +#include +#include + +namespace Catch +{ + struct ReporterConfig { + explicit ReporterConfig( Ptr const& _fullConfig ) + : m_stream( &_fullConfig->stream() ), m_fullConfig( _fullConfig ) {} + + ReporterConfig( Ptr const& _fullConfig, std::ostream& _stream ) + : m_stream( &_stream ), m_fullConfig( _fullConfig ) {} + + std::ostream& stream() const { return *m_stream; } + Ptr fullConfig() const { return m_fullConfig; } + + private: + std::ostream* m_stream; + Ptr m_fullConfig; + }; + + struct ReporterPreferences { + ReporterPreferences() + : shouldRedirectStdOut( false ) + {} + + bool shouldRedirectStdOut; + }; + + template + struct LazyStat : Option { + LazyStat() : used( false ) {} + LazyStat& operator=( T const& _value ) { + Option::operator=( _value ); + used = false; + return *this; + } + void reset() { + Option::reset(); + used = false; + } + bool used; + }; + + struct TestRunInfo { + TestRunInfo( std::string const& _name ) : name( _name ) {} + std::string name; + }; + struct GroupInfo { + GroupInfo( std::string const& _name, + std::size_t _groupIndex, + std::size_t _groupsCount ) + : name( _name ), + groupIndex( _groupIndex ), + groupsCounts( _groupsCount ) + {} + + std::string name; + std::size_t groupIndex; + std::size_t groupsCounts; + }; + + struct AssertionStats { + AssertionStats( AssertionResult const& _assertionResult, + std::vector const& _infoMessages, + Totals const& _totals ) + : assertionResult( _assertionResult ), + infoMessages( _infoMessages ), + totals( _totals ) + { + if( assertionResult.hasMessage() ) { + // Copy message into messages list. + // !TBD This should have been done earlier, somewhere + MessageBuilder builder( assertionResult.getTestMacroName(), assertionResult.getSourceInfo(), assertionResult.getResultType() ); + builder << assertionResult.getMessage(); + builder.m_info.message = builder.m_stream.str(); + + infoMessages.push_back( builder.m_info ); + } + } + virtual ~AssertionStats(); + +# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS + AssertionStats( AssertionStats const& ) = default; + AssertionStats( AssertionStats && ) = default; + AssertionStats& operator = ( AssertionStats const& ) = default; + AssertionStats& operator = ( AssertionStats && ) = default; +# endif + + AssertionResult assertionResult; + std::vector infoMessages; + Totals totals; + }; + + struct SectionStats { + SectionStats( SectionInfo const& _sectionInfo, + Counts const& _assertions, + double _durationInSeconds, + bool _missingAssertions ) + : sectionInfo( _sectionInfo ), + assertions( _assertions ), + durationInSeconds( _durationInSeconds ), + missingAssertions( _missingAssertions ) + {} + virtual ~SectionStats(); +# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS + SectionStats( SectionStats const& ) = default; + SectionStats( SectionStats && ) = default; + SectionStats& operator = ( SectionStats const& ) = default; + SectionStats& operator = ( SectionStats && ) = default; +# endif + + SectionInfo sectionInfo; + Counts assertions; + double durationInSeconds; + bool missingAssertions; + }; + + struct TestCaseStats { + TestCaseStats( TestCaseInfo const& _testInfo, + Totals const& _totals, + std::string const& _stdOut, + std::string const& _stdErr, + bool _aborting ) + : testInfo( _testInfo ), + totals( _totals ), + stdOut( _stdOut ), + stdErr( _stdErr ), + aborting( _aborting ) + {} + virtual ~TestCaseStats(); + +# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS + TestCaseStats( TestCaseStats const& ) = default; + TestCaseStats( TestCaseStats && ) = default; + TestCaseStats& operator = ( TestCaseStats const& ) = default; + TestCaseStats& operator = ( TestCaseStats && ) = default; +# endif + + TestCaseInfo testInfo; + Totals totals; + std::string stdOut; + std::string stdErr; + bool aborting; + }; + + struct TestGroupStats { + TestGroupStats( GroupInfo const& _groupInfo, + Totals const& _totals, + bool _aborting ) + : groupInfo( _groupInfo ), + totals( _totals ), + aborting( _aborting ) + {} + TestGroupStats( GroupInfo const& _groupInfo ) + : groupInfo( _groupInfo ), + aborting( false ) + {} + virtual ~TestGroupStats(); + +# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS + TestGroupStats( TestGroupStats const& ) = default; + TestGroupStats( TestGroupStats && ) = default; + TestGroupStats& operator = ( TestGroupStats const& ) = default; + TestGroupStats& operator = ( TestGroupStats && ) = default; +# endif + + GroupInfo groupInfo; + Totals totals; + bool aborting; + }; + + struct TestRunStats { + TestRunStats( TestRunInfo const& _runInfo, + Totals const& _totals, + bool _aborting ) + : runInfo( _runInfo ), + totals( _totals ), + aborting( _aborting ) + {} + virtual ~TestRunStats(); + +# ifndef CATCH_CONFIG_CPP11_GENERATED_METHODS + TestRunStats( TestRunStats const& _other ) + : runInfo( _other.runInfo ), + totals( _other.totals ), + aborting( _other.aborting ) + {} +# else + TestRunStats( TestRunStats const& ) = default; + TestRunStats( TestRunStats && ) = default; + TestRunStats& operator = ( TestRunStats const& ) = default; + TestRunStats& operator = ( TestRunStats && ) = default; +# endif + + TestRunInfo runInfo; + Totals totals; + bool aborting; + }; + + struct IStreamingReporter : IShared { + virtual ~IStreamingReporter(); + + // Implementing class must also provide the following static method: + // static std::string getDescription(); + + virtual ReporterPreferences getPreferences() const = 0; + + virtual void noMatchingTestCases( std::string const& spec ) = 0; + + virtual void testRunStarting( TestRunInfo const& testRunInfo ) = 0; + virtual void testGroupStarting( GroupInfo const& groupInfo ) = 0; + + virtual void testCaseStarting( TestCaseInfo const& testInfo ) = 0; + virtual void sectionStarting( SectionInfo const& sectionInfo ) = 0; + + virtual void assertionStarting( AssertionInfo const& assertionInfo ) = 0; + + // The return value indicates if the messages buffer should be cleared: + virtual bool assertionEnded( AssertionStats const& assertionStats ) = 0; + virtual void sectionEnded( SectionStats const& sectionStats ) = 0; + virtual void testCaseEnded( TestCaseStats const& testCaseStats ) = 0; + virtual void testGroupEnded( TestGroupStats const& testGroupStats ) = 0; + virtual void testRunEnded( TestRunStats const& testRunStats ) = 0; + + virtual void skipTest( TestCaseInfo const& testInfo ) = 0; + }; + + struct IReporterFactory { + virtual ~IReporterFactory(); + virtual IStreamingReporter* create( ReporterConfig const& config ) const = 0; + virtual std::string getDescription() const = 0; + }; + + struct IReporterRegistry { + typedef std::map FactoryMap; + + virtual ~IReporterRegistry(); + virtual IStreamingReporter* create( std::string const& name, Ptr const& config ) const = 0; + virtual FactoryMap const& getFactories() const = 0; + }; + +} + +#include +#include + +namespace Catch { + + inline std::size_t listTests( Config const& config ) { + + TestSpec testSpec = config.testSpec(); + if( config.testSpec().hasFilters() ) + Catch::cout() << "Matching test cases:\n"; + else { + Catch::cout() << "All available test cases:\n"; + testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec(); + } + + std::size_t matchedTests = 0; + TextAttributes nameAttr, tagsAttr; + nameAttr.setInitialIndent( 2 ).setIndent( 4 ); + tagsAttr.setIndent( 6 ); + + std::vector matchedTestCases; + getRegistryHub().getTestCaseRegistry().getFilteredTests( testSpec, config, matchedTestCases ); + for( std::vector::const_iterator it = matchedTestCases.begin(), itEnd = matchedTestCases.end(); + it != itEnd; + ++it ) { + matchedTests++; + TestCaseInfo const& testCaseInfo = it->getTestCaseInfo(); + Colour::Code colour = testCaseInfo.isHidden() + ? Colour::SecondaryText + : Colour::None; + Colour colourGuard( colour ); + + Catch::cout() << Text( testCaseInfo.name, nameAttr ) << std::endl; + if( !testCaseInfo.tags.empty() ) + Catch::cout() << Text( testCaseInfo.tagsAsString, tagsAttr ) << std::endl; + } + + if( !config.testSpec().hasFilters() ) + Catch::cout() << pluralise( matchedTests, "test case" ) << "\n" << std::endl; + else + Catch::cout() << pluralise( matchedTests, "matching test case" ) << "\n" << std::endl; + return matchedTests; + } + + inline std::size_t listTestsNamesOnly( Config const& config ) { + TestSpec testSpec = config.testSpec(); + if( !config.testSpec().hasFilters() ) + testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec(); + std::size_t matchedTests = 0; + std::vector matchedTestCases; + getRegistryHub().getTestCaseRegistry().getFilteredTests( testSpec, config, matchedTestCases ); + for( std::vector::const_iterator it = matchedTestCases.begin(), itEnd = matchedTestCases.end(); + it != itEnd; + ++it ) { + matchedTests++; + TestCaseInfo const& testCaseInfo = it->getTestCaseInfo(); + Catch::cout() << testCaseInfo.name << std::endl; + } + return matchedTests; + } + + struct TagInfo { + TagInfo() : count ( 0 ) {} + void add( std::string const& spelling ) { + ++count; + spellings.insert( spelling ); + } + std::string all() const { + std::string out; + for( std::set::const_iterator it = spellings.begin(), itEnd = spellings.end(); + it != itEnd; + ++it ) + out += "[" + *it + "]"; + return out; + } + std::set spellings; + std::size_t count; + }; + + inline std::size_t listTags( Config const& config ) { + TestSpec testSpec = config.testSpec(); + if( config.testSpec().hasFilters() ) + Catch::cout() << "Tags for matching test cases:\n"; + else { + Catch::cout() << "All available tags:\n"; + testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec(); + } + + std::map tagCounts; + + std::vector matchedTestCases; + getRegistryHub().getTestCaseRegistry().getFilteredTests( testSpec, config, matchedTestCases ); + for( std::vector::const_iterator it = matchedTestCases.begin(), itEnd = matchedTestCases.end(); + it != itEnd; + ++it ) { + for( std::set::const_iterator tagIt = it->getTestCaseInfo().tags.begin(), + tagItEnd = it->getTestCaseInfo().tags.end(); + tagIt != tagItEnd; + ++tagIt ) { + std::string tagName = *tagIt; + std::string lcaseTagName = toLower( tagName ); + std::map::iterator countIt = tagCounts.find( lcaseTagName ); + if( countIt == tagCounts.end() ) + countIt = tagCounts.insert( std::make_pair( lcaseTagName, TagInfo() ) ).first; + countIt->second.add( tagName ); + } + } + + for( std::map::const_iterator countIt = tagCounts.begin(), + countItEnd = tagCounts.end(); + countIt != countItEnd; + ++countIt ) { + std::ostringstream oss; + oss << " " << std::setw(2) << countIt->second.count << " "; + Text wrapper( countIt->second.all(), TextAttributes() + .setInitialIndent( 0 ) + .setIndent( oss.str().size() ) + .setWidth( CATCH_CONFIG_CONSOLE_WIDTH-10 ) ); + Catch::cout() << oss.str() << wrapper << "\n"; + } + Catch::cout() << pluralise( tagCounts.size(), "tag" ) << "\n" << std::endl; + return tagCounts.size(); + } + + inline std::size_t listReporters( Config const& /*config*/ ) { + Catch::cout() << "Available reporters:\n"; + IReporterRegistry::FactoryMap const& factories = getRegistryHub().getReporterRegistry().getFactories(); + IReporterRegistry::FactoryMap::const_iterator itBegin = factories.begin(), itEnd = factories.end(), it; + std::size_t maxNameLen = 0; + for(it = itBegin; it != itEnd; ++it ) + maxNameLen = (std::max)( maxNameLen, it->first.size() ); + + for(it = itBegin; it != itEnd; ++it ) { + Text wrapper( it->second->getDescription(), TextAttributes() + .setInitialIndent( 0 ) + .setIndent( 7+maxNameLen ) + .setWidth( CATCH_CONFIG_CONSOLE_WIDTH - maxNameLen-8 ) ); + Catch::cout() << " " + << it->first + << ":" + << std::string( maxNameLen - it->first.size() + 2, ' ' ) + << wrapper << "\n"; + } + Catch::cout() << std::endl; + return factories.size(); + } + + inline Option list( Config const& config ) { + Option listedCount; + if( config.listTests() ) + listedCount = listedCount.valueOr(0) + listTests( config ); + if( config.listTestNamesOnly() ) + listedCount = listedCount.valueOr(0) + listTestsNamesOnly( config ); + if( config.listTags() ) + listedCount = listedCount.valueOr(0) + listTags( config ); + if( config.listReporters() ) + listedCount = listedCount.valueOr(0) + listReporters( config ); + return listedCount; + } + +} // end namespace Catch + +// #included from: internal/catch_runner_impl.hpp +#define TWOBLUECUBES_CATCH_RUNNER_IMPL_HPP_INCLUDED + +// #included from: catch_test_case_tracker.hpp +#define TWOBLUECUBES_CATCH_TEST_CASE_TRACKER_HPP_INCLUDED + +#include +#include +#include + +namespace Catch { +namespace SectionTracking { + + class TrackedSection { + + typedef std::map TrackedSections; + + public: + enum RunState { + NotStarted, + Executing, + ExecutingChildren, + Completed + }; + + TrackedSection( std::string const& name, TrackedSection* parent ) + : m_name( name ), m_runState( NotStarted ), m_parent( parent ) + {} + + RunState runState() const { return m_runState; } + + TrackedSection* findChild( std::string const& childName ); + TrackedSection* acquireChild( std::string const& childName ); + + void enter() { + if( m_runState == NotStarted ) + m_runState = Executing; + } + void leave(); + + TrackedSection* getParent() { + return m_parent; + } + bool hasChildren() const { + return !m_children.empty(); + } + + private: + std::string m_name; + RunState m_runState; + TrackedSections m_children; + TrackedSection* m_parent; + }; + + inline TrackedSection* TrackedSection::findChild( std::string const& childName ) { + TrackedSections::iterator it = m_children.find( childName ); + return it != m_children.end() + ? &it->second + : NULL; + } + inline TrackedSection* TrackedSection::acquireChild( std::string const& childName ) { + if( TrackedSection* child = findChild( childName ) ) + return child; + m_children.insert( std::make_pair( childName, TrackedSection( childName, this ) ) ); + return findChild( childName ); + } + inline void TrackedSection::leave() { + for( TrackedSections::const_iterator it = m_children.begin(), itEnd = m_children.end(); + it != itEnd; + ++it ) + if( it->second.runState() != Completed ) { + m_runState = ExecutingChildren; + return; + } + m_runState = Completed; + } + + class TestCaseTracker { + public: + TestCaseTracker( std::string const& testCaseName ) + : m_testCase( testCaseName, NULL ), + m_currentSection( &m_testCase ), + m_completedASectionThisRun( false ) + {} + + bool enterSection( std::string const& name ) { + TrackedSection* child = m_currentSection->acquireChild( name ); + if( m_completedASectionThisRun || child->runState() == TrackedSection::Completed ) + return false; + + m_currentSection = child; + m_currentSection->enter(); + return true; + } + void leaveSection() { + m_currentSection->leave(); + m_currentSection = m_currentSection->getParent(); + assert( m_currentSection != NULL ); + m_completedASectionThisRun = true; + } + + bool currentSectionHasChildren() const { + return m_currentSection->hasChildren(); + } + bool isCompleted() const { + return m_testCase.runState() == TrackedSection::Completed; + } + + class Guard { + public: + Guard( TestCaseTracker& tracker ) : m_tracker( tracker ) { + m_tracker.enterTestCase(); + } + ~Guard() { + m_tracker.leaveTestCase(); + } + private: + Guard( Guard const& ); + void operator = ( Guard const& ); + TestCaseTracker& m_tracker; + }; + + private: + void enterTestCase() { + m_currentSection = &m_testCase; + m_completedASectionThisRun = false; + m_testCase.enter(); + } + void leaveTestCase() { + m_testCase.leave(); + } + + TrackedSection m_testCase; + TrackedSection* m_currentSection; + bool m_completedASectionThisRun; + }; + +} // namespace SectionTracking + +using SectionTracking::TestCaseTracker; + +} // namespace Catch + +// #included from: catch_fatal_condition.hpp +#define TWOBLUECUBES_CATCH_FATAL_CONDITION_H_INCLUDED + +namespace Catch { + + // Report the error condition then exit the process + inline void fatal( std::string const& message, int exitCode ) { + IContext& context = Catch::getCurrentContext(); + IResultCapture* resultCapture = context.getResultCapture(); + resultCapture->handleFatalErrorCondition( message ); + + if( Catch::alwaysTrue() ) // avoids "no return" warnings + exit( exitCode ); + } + +} // namespace Catch + +#if defined ( CATCH_PLATFORM_WINDOWS ) ///////////////////////////////////////// + +namespace Catch { + + struct FatalConditionHandler { + void reset() {} + }; + +} // namespace Catch + +#else // Not Windows - assumed to be POSIX compatible ////////////////////////// + +#include + +namespace Catch { + + struct SignalDefs { int id; const char* name; }; + extern SignalDefs signalDefs[]; + SignalDefs signalDefs[] = { + { SIGINT, "SIGINT - Terminal interrupt signal" }, + { SIGILL, "SIGILL - Illegal instruction signal" }, + { SIGFPE, "SIGFPE - Floating point error signal" }, + { SIGSEGV, "SIGSEGV - Segmentation violation signal" }, + { SIGTERM, "SIGTERM - Termination request signal" }, + { SIGABRT, "SIGABRT - Abort (abnormal termination) signal" } + }; + + struct FatalConditionHandler { + + static void handleSignal( int sig ) { + for( std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i ) + if( sig == signalDefs[i].id ) + fatal( signalDefs[i].name, -sig ); + fatal( "", -sig ); + } + + FatalConditionHandler() : m_isSet( true ) { + for( std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i ) + signal( signalDefs[i].id, handleSignal ); + } + ~FatalConditionHandler() { + reset(); + } + void reset() { + if( m_isSet ) { + for( std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i ) + signal( signalDefs[i].id, SIG_DFL ); + m_isSet = false; + } + } + + bool m_isSet; + }; + +} // namespace Catch + +#endif // not Windows + +#include +#include + +namespace Catch { + + class StreamRedirect { + + public: + StreamRedirect( std::ostream& stream, std::string& targetString ) + : m_stream( stream ), + m_prevBuf( stream.rdbuf() ), + m_targetString( targetString ) + { + stream.rdbuf( m_oss.rdbuf() ); + } + + ~StreamRedirect() { + m_targetString += m_oss.str(); + m_stream.rdbuf( m_prevBuf ); + } + + private: + std::ostream& m_stream; + std::streambuf* m_prevBuf; + std::ostringstream m_oss; + std::string& m_targetString; + }; + + /////////////////////////////////////////////////////////////////////////// + + class RunContext : public IResultCapture, public IRunner { + + RunContext( RunContext const& ); + void operator =( RunContext const& ); + + public: + + explicit RunContext( Ptr const& config, Ptr const& reporter ) + : m_runInfo( config->name() ), + m_context( getCurrentMutableContext() ), + m_activeTestCase( NULL ), + m_config( config ), + m_reporter( reporter ), + m_prevRunner( m_context.getRunner() ), + m_prevResultCapture( m_context.getResultCapture() ), + m_prevConfig( m_context.getConfig() ) + { + m_context.setRunner( this ); + m_context.setConfig( m_config ); + m_context.setResultCapture( this ); + m_reporter->testRunStarting( m_runInfo ); + } + + virtual ~RunContext() { + m_reporter->testRunEnded( TestRunStats( m_runInfo, m_totals, aborting() ) ); + m_context.setRunner( m_prevRunner ); + m_context.setConfig( NULL ); + m_context.setResultCapture( m_prevResultCapture ); + m_context.setConfig( m_prevConfig ); + } + + void testGroupStarting( std::string const& testSpec, std::size_t groupIndex, std::size_t groupsCount ) { + m_reporter->testGroupStarting( GroupInfo( testSpec, groupIndex, groupsCount ) ); + } + void testGroupEnded( std::string const& testSpec, Totals const& totals, std::size_t groupIndex, std::size_t groupsCount ) { + m_reporter->testGroupEnded( TestGroupStats( GroupInfo( testSpec, groupIndex, groupsCount ), totals, aborting() ) ); + } + + Totals runTest( TestCase const& testCase ) { + Totals prevTotals = m_totals; + + std::string redirectedCout; + std::string redirectedCerr; + + TestCaseInfo testInfo = testCase.getTestCaseInfo(); + + m_reporter->testCaseStarting( testInfo ); + + m_activeTestCase = &testCase; + m_testCaseTracker = TestCaseTracker( testInfo.name ); + + do { + do { + runCurrentTest( redirectedCout, redirectedCerr ); + } + while( !m_testCaseTracker->isCompleted() && !aborting() ); + } + while( getCurrentContext().advanceGeneratorsForCurrentTest() && !aborting() ); + + Totals deltaTotals = m_totals.delta( prevTotals ); + m_totals.testCases += deltaTotals.testCases; + m_reporter->testCaseEnded( TestCaseStats( testInfo, + deltaTotals, + redirectedCout, + redirectedCerr, + aborting() ) ); + + m_activeTestCase = NULL; + m_testCaseTracker.reset(); + + return deltaTotals; + } + + Ptr config() const { + return m_config; + } + + private: // IResultCapture + + virtual void assertionEnded( AssertionResult const& result ) { + if( result.getResultType() == ResultWas::Ok ) { + m_totals.assertions.passed++; + } + else if( !result.isOk() ) { + m_totals.assertions.failed++; + } + + if( m_reporter->assertionEnded( AssertionStats( result, m_messages, m_totals ) ) ) + m_messages.clear(); + + // Reset working state + m_lastAssertionInfo = AssertionInfo( "", m_lastAssertionInfo.lineInfo, "{Unknown expression after the reported line}" , m_lastAssertionInfo.resultDisposition ); + m_lastResult = result; + } + + virtual bool sectionStarted ( + SectionInfo const& sectionInfo, + Counts& assertions + ) + { + std::ostringstream oss; + oss << sectionInfo.name << "@" << sectionInfo.lineInfo; + + if( !m_testCaseTracker->enterSection( oss.str() ) ) + return false; + + m_lastAssertionInfo.lineInfo = sectionInfo.lineInfo; + + m_reporter->sectionStarting( sectionInfo ); + + assertions = m_totals.assertions; + + return true; + } + bool testForMissingAssertions( Counts& assertions ) { + if( assertions.total() != 0 || + !m_config->warnAboutMissingAssertions() || + m_testCaseTracker->currentSectionHasChildren() ) + return false; + m_totals.assertions.failed++; + assertions.failed++; + return true; + } + + virtual void sectionEnded( SectionInfo const& info, Counts const& prevAssertions, double _durationInSeconds ) { + if( std::uncaught_exception() ) { + m_unfinishedSections.push_back( UnfinishedSections( info, prevAssertions, _durationInSeconds ) ); + return; + } + + Counts assertions = m_totals.assertions - prevAssertions; + bool missingAssertions = testForMissingAssertions( assertions ); + + m_testCaseTracker->leaveSection(); + + m_reporter->sectionEnded( SectionStats( info, assertions, _durationInSeconds, missingAssertions ) ); + m_messages.clear(); + } + + virtual void pushScopedMessage( MessageInfo const& message ) { + m_messages.push_back( message ); + } + + virtual void popScopedMessage( MessageInfo const& message ) { + m_messages.erase( std::remove( m_messages.begin(), m_messages.end(), message ), m_messages.end() ); + } + + virtual std::string getCurrentTestName() const { + return m_activeTestCase + ? m_activeTestCase->getTestCaseInfo().name + : ""; + } + + virtual const AssertionResult* getLastResult() const { + return &m_lastResult; + } + + virtual void handleFatalErrorCondition( std::string const& message ) { + ResultBuilder resultBuilder = makeUnexpectedResultBuilder(); + resultBuilder.setResultType( ResultWas::FatalErrorCondition ); + resultBuilder << message; + resultBuilder.captureExpression(); + + handleUnfinishedSections(); + + // Recreate section for test case (as we will lose the one that was in scope) + TestCaseInfo const& testCaseInfo = m_activeTestCase->getTestCaseInfo(); + SectionInfo testCaseSection( testCaseInfo.lineInfo, testCaseInfo.name, testCaseInfo.description ); + + Counts assertions; + assertions.failed = 1; + SectionStats testCaseSectionStats( testCaseSection, assertions, 0, false ); + m_reporter->sectionEnded( testCaseSectionStats ); + + TestCaseInfo testInfo = m_activeTestCase->getTestCaseInfo(); + + Totals deltaTotals; + deltaTotals.testCases.failed = 1; + m_reporter->testCaseEnded( TestCaseStats( testInfo, + deltaTotals, + "", + "", + false ) ); + m_totals.testCases.failed++; + testGroupEnded( "", m_totals, 1, 1 ); + m_reporter->testRunEnded( TestRunStats( m_runInfo, m_totals, false ) ); + } + + public: + // !TBD We need to do this another way! + bool aborting() const { + return m_totals.assertions.failed == static_cast( m_config->abortAfter() ); + } + + private: + + void runCurrentTest( std::string& redirectedCout, std::string& redirectedCerr ) { + TestCaseInfo const& testCaseInfo = m_activeTestCase->getTestCaseInfo(); + SectionInfo testCaseSection( testCaseInfo.lineInfo, testCaseInfo.name, testCaseInfo.description ); + m_reporter->sectionStarting( testCaseSection ); + Counts prevAssertions = m_totals.assertions; + double duration = 0; + try { + m_lastAssertionInfo = AssertionInfo( "TEST_CASE", testCaseInfo.lineInfo, "", ResultDisposition::Normal ); + TestCaseTracker::Guard guard( *m_testCaseTracker ); + + Timer timer; + timer.start(); + if( m_reporter->getPreferences().shouldRedirectStdOut ) { + StreamRedirect coutRedir( Catch::cout(), redirectedCout ); + StreamRedirect cerrRedir( Catch::cerr(), redirectedCerr ); + invokeActiveTestCase(); + } + else { + invokeActiveTestCase(); + } + duration = timer.getElapsedSeconds(); + } + catch( TestFailureException& ) { + // This just means the test was aborted due to failure + } + catch(...) { + makeUnexpectedResultBuilder().useActiveException(); + } + handleUnfinishedSections(); + m_messages.clear(); + + Counts assertions = m_totals.assertions - prevAssertions; + bool missingAssertions = testForMissingAssertions( assertions ); + + if( testCaseInfo.okToFail() ) { + std::swap( assertions.failedButOk, assertions.failed ); + m_totals.assertions.failed -= assertions.failedButOk; + m_totals.assertions.failedButOk += assertions.failedButOk; + } + + SectionStats testCaseSectionStats( testCaseSection, assertions, duration, missingAssertions ); + m_reporter->sectionEnded( testCaseSectionStats ); + } + + void invokeActiveTestCase() { + FatalConditionHandler fatalConditionHandler; // Handle signals + m_activeTestCase->invoke(); + fatalConditionHandler.reset(); + } + + private: + + ResultBuilder makeUnexpectedResultBuilder() const { + return ResultBuilder( m_lastAssertionInfo.macroName.c_str(), + m_lastAssertionInfo.lineInfo, + m_lastAssertionInfo.capturedExpression.c_str(), + m_lastAssertionInfo.resultDisposition ); + } + + void handleUnfinishedSections() { + // If sections ended prematurely due to an exception we stored their + // infos here so we can tear them down outside the unwind process. + for( std::vector::const_reverse_iterator it = m_unfinishedSections.rbegin(), + itEnd = m_unfinishedSections.rend(); + it != itEnd; + ++it ) + sectionEnded( it->info, it->prevAssertions, it->durationInSeconds ); + m_unfinishedSections.clear(); + } + + struct UnfinishedSections { + UnfinishedSections( SectionInfo const& _info, Counts const& _prevAssertions, double _durationInSeconds ) + : info( _info ), prevAssertions( _prevAssertions ), durationInSeconds( _durationInSeconds ) + {} + + SectionInfo info; + Counts prevAssertions; + double durationInSeconds; + }; + + TestRunInfo m_runInfo; + IMutableContext& m_context; + TestCase const* m_activeTestCase; + Option m_testCaseTracker; + AssertionResult m_lastResult; + + Ptr m_config; + Totals m_totals; + Ptr m_reporter; + std::vector m_messages; + IRunner* m_prevRunner; + IResultCapture* m_prevResultCapture; + Ptr m_prevConfig; + AssertionInfo m_lastAssertionInfo; + std::vector m_unfinishedSections; + }; + + IResultCapture& getResultCapture() { + if( IResultCapture* capture = getCurrentContext().getResultCapture() ) + return *capture; + else + throw std::logic_error( "No result capture instance" ); + } + +} // end namespace Catch + +// #included from: internal/catch_version.h +#define TWOBLUECUBES_CATCH_VERSION_H_INCLUDED + +namespace Catch { + + // Versioning information + struct Version { + Version( unsigned int _majorVersion, + unsigned int _minorVersion, + unsigned int _patchNumber, + std::string const& _branchName, + unsigned int _buildNumber ); + + unsigned int const majorVersion; + unsigned int const minorVersion; + unsigned int const patchNumber; + + // buildNumber is only used if branchName is not null + std::string const branchName; + unsigned int const buildNumber; + + friend std::ostream& operator << ( std::ostream& os, Version const& version ); + + private: + void operator=( Version const& ); + }; + + extern Version libraryVersion; +} + +#include +#include +#include + +namespace Catch { + + class Runner { + + public: + Runner( Ptr const& config ) + : m_config( config ) + { + openStream(); + makeReporter(); + } + + Totals runTests() { + + RunContext context( m_config.get(), m_reporter ); + + Totals totals; + + context.testGroupStarting( "all tests", 1, 1 ); // deprecated? + + TestSpec testSpec = m_config->testSpec(); + if( !testSpec.hasFilters() ) + testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "~[.]" ).testSpec(); // All not hidden tests + + std::vector testCases; + getRegistryHub().getTestCaseRegistry().getFilteredTests( testSpec, *m_config, testCases ); + + int testsRunForGroup = 0; + for( std::vector::const_iterator it = testCases.begin(), itEnd = testCases.end(); + it != itEnd; + ++it ) { + testsRunForGroup++; + if( m_testsAlreadyRun.find( *it ) == m_testsAlreadyRun.end() ) { + + if( context.aborting() ) + break; + + totals += context.runTest( *it ); + m_testsAlreadyRun.insert( *it ); + } + } + std::vector skippedTestCases; + getRegistryHub().getTestCaseRegistry().getFilteredTests( testSpec, *m_config, skippedTestCases, true ); + + for( std::vector::const_iterator it = skippedTestCases.begin(), itEnd = skippedTestCases.end(); + it != itEnd; + ++it ) + m_reporter->skipTest( *it ); + + context.testGroupEnded( "all tests", totals, 1, 1 ); + return totals; + } + + private: + void openStream() { + // Open output file, if specified + if( !m_config->getFilename().empty() ) { + m_ofs.open( m_config->getFilename().c_str() ); + if( m_ofs.fail() ) { + std::ostringstream oss; + oss << "Unable to open file: '" << m_config->getFilename() << "'"; + throw std::domain_error( oss.str() ); + } + m_config->setStreamBuf( m_ofs.rdbuf() ); + } + } + void makeReporter() { + std::string reporterName = m_config->getReporterName().empty() + ? "console" + : m_config->getReporterName(); + + m_reporter = getRegistryHub().getReporterRegistry().create( reporterName, m_config.get() ); + if( !m_reporter ) { + std::ostringstream oss; + oss << "No reporter registered with name: '" << reporterName << "'"; + throw std::domain_error( oss.str() ); + } + } + + private: + Ptr m_config; + std::ofstream m_ofs; + Ptr m_reporter; + std::set m_testsAlreadyRun; + }; + + class Session : NonCopyable { + static bool alreadyInstantiated; + + public: + + struct OnUnusedOptions { enum DoWhat { Ignore, Fail }; }; + + Session() + : m_cli( makeCommandLineParser() ) { + if( alreadyInstantiated ) { + std::string msg = "Only one instance of Catch::Session can ever be used"; + Catch::cerr() << msg << std::endl; + throw std::logic_error( msg ); + } + alreadyInstantiated = true; + } + ~Session() { + Catch::cleanUp(); + } + + void showHelp( std::string const& processName ) { + Catch::cout() << "\nCatch v" << libraryVersion << "\n"; + + m_cli.usage( Catch::cout(), processName ); + Catch::cout() << "For more detail usage please see the project docs\n" << std::endl; + } + + int applyCommandLine( int argc, char* const argv[], OnUnusedOptions::DoWhat unusedOptionBehaviour = OnUnusedOptions::Fail ) { + try { + m_cli.setThrowOnUnrecognisedTokens( unusedOptionBehaviour == OnUnusedOptions::Fail ); + m_unusedTokens = m_cli.parseInto( argc, argv, m_configData ); + if( m_configData.showHelp ) + showHelp( m_configData.processName ); + m_config.reset(); + } + catch( std::exception& ex ) { + { + Colour colourGuard( Colour::Red ); + Catch::cerr() + << "\nError(s) in input:\n" + << Text( ex.what(), TextAttributes().setIndent(2) ) + << "\n\n"; + } + m_cli.usage( Catch::cout(), m_configData.processName ); + return (std::numeric_limits::max)(); + } + return 0; + } + + void useConfigData( ConfigData const& _configData ) { + m_configData = _configData; + m_config.reset(); + } + + int run( int argc, char* const argv[] ) { + + int returnCode = applyCommandLine( argc, argv ); + if( returnCode == 0 ) + returnCode = run(); + return returnCode; + } + + int run() { + if( m_configData.showHelp ) + return 0; + + try + { + config(); // Force config to be constructed + + std::srand( m_configData.rngSeed ); + + Runner runner( m_config ); + + // Handle list request + if( Option listed = list( config() ) ) + return static_cast( *listed ); + + return static_cast( runner.runTests().assertions.failed ); + } + catch( std::exception& ex ) { + Catch::cerr() << ex.what() << std::endl; + return (std::numeric_limits::max)(); + } + } + + Clara::CommandLine const& cli() const { + return m_cli; + } + std::vector const& unusedTokens() const { + return m_unusedTokens; + } + ConfigData& configData() { + return m_configData; + } + Config& config() { + if( !m_config ) + m_config = new Config( m_configData ); + return *m_config; + } + + private: + Clara::CommandLine m_cli; + std::vector m_unusedTokens; + ConfigData m_configData; + Ptr m_config; + }; + + bool Session::alreadyInstantiated = false; + +} // end namespace Catch + +// #included from: catch_registry_hub.hpp +#define TWOBLUECUBES_CATCH_REGISTRY_HUB_HPP_INCLUDED + +// #included from: catch_test_case_registry_impl.hpp +#define TWOBLUECUBES_CATCH_TEST_CASE_REGISTRY_IMPL_HPP_INCLUDED + +#include +#include +#include +#include +#include + +namespace Catch { + + class TestRegistry : public ITestCaseRegistry { + struct LexSort { + bool operator() (TestCase i,TestCase j) const { return (i const& getAllTests() const { + return m_functionsInOrder; + } + + virtual std::vector const& getAllNonHiddenTests() const { + return m_nonHiddenFunctions; + } + + virtual void getFilteredTests( TestSpec const& testSpec, IConfig const& config, std::vector& matchingTestCases, bool negated = false ) const { + + for( std::vector::const_iterator it = m_functionsInOrder.begin(), + itEnd = m_functionsInOrder.end(); + it != itEnd; + ++it ) { + bool includeTest = testSpec.matches( *it ) && ( config.allowThrows() || !it->throws() ); + if( includeTest != negated ) + matchingTestCases.push_back( *it ); + } + sortTests( config, matchingTestCases ); + } + + private: + + static void sortTests( IConfig const& config, std::vector& matchingTestCases ) { + + switch( config.runOrder() ) { + case RunTests::InLexicographicalOrder: + std::sort( matchingTestCases.begin(), matchingTestCases.end(), LexSort() ); + break; + case RunTests::InRandomOrder: + { + RandomNumberGenerator rng; + std::random_shuffle( matchingTestCases.begin(), matchingTestCases.end(), rng ); + } + break; + case RunTests::InDeclarationOrder: + // already in declaration order + break; + } + } + std::set m_functions; + std::vector m_functionsInOrder; + std::vector m_nonHiddenFunctions; + size_t m_unnamedCount; + }; + + /////////////////////////////////////////////////////////////////////////// + + class FreeFunctionTestCase : public SharedImpl { + public: + + FreeFunctionTestCase( TestFunction fun ) : m_fun( fun ) {} + + virtual void invoke() const { + m_fun(); + } + + private: + virtual ~FreeFunctionTestCase(); + + TestFunction m_fun; + }; + + inline std::string extractClassName( std::string const& classOrQualifiedMethodName ) { + std::string className = classOrQualifiedMethodName; + if( startsWith( className, "&" ) ) + { + std::size_t lastColons = className.rfind( "::" ); + std::size_t penultimateColons = className.rfind( "::", lastColons-1 ); + if( penultimateColons == std::string::npos ) + penultimateColons = 1; + className = className.substr( penultimateColons, lastColons-penultimateColons ); + } + return className; + } + + /////////////////////////////////////////////////////////////////////////// + + AutoReg::AutoReg( TestFunction function, + SourceLineInfo const& lineInfo, + NameAndDesc const& nameAndDesc ) { + registerTestCase( new FreeFunctionTestCase( function ), "", nameAndDesc, lineInfo ); + } + + AutoReg::~AutoReg() {} + + void AutoReg::registerTestCase( ITestCase* testCase, + char const* classOrQualifiedMethodName, + NameAndDesc const& nameAndDesc, + SourceLineInfo const& lineInfo ) { + + getMutableRegistryHub().registerTest + ( makeTestCase( testCase, + extractClassName( classOrQualifiedMethodName ), + nameAndDesc.name, + nameAndDesc.description, + lineInfo ) ); + } + +} // end namespace Catch + +// #included from: catch_reporter_registry.hpp +#define TWOBLUECUBES_CATCH_REPORTER_REGISTRY_HPP_INCLUDED + +#include + +namespace Catch { + + class ReporterRegistry : public IReporterRegistry { + + public: + + virtual ~ReporterRegistry() { + deleteAllValues( m_factories ); + } + + virtual IStreamingReporter* create( std::string const& name, Ptr const& config ) const { + FactoryMap::const_iterator it = m_factories.find( name ); + if( it == m_factories.end() ) + return NULL; + return it->second->create( ReporterConfig( config ) ); + } + + void registerReporter( std::string const& name, IReporterFactory* factory ) { + m_factories.insert( std::make_pair( name, factory ) ); + } + + FactoryMap const& getFactories() const { + return m_factories; + } + + private: + FactoryMap m_factories; + }; +} + +// #included from: catch_exception_translator_registry.hpp +#define TWOBLUECUBES_CATCH_EXCEPTION_TRANSLATOR_REGISTRY_HPP_INCLUDED + +#ifdef __OBJC__ +#import "Foundation/Foundation.h" +#endif + +namespace Catch { + + class ExceptionTranslatorRegistry : public IExceptionTranslatorRegistry { + public: + ~ExceptionTranslatorRegistry() { + deleteAll( m_translators ); + } + + virtual void registerTranslator( const IExceptionTranslator* translator ) { + m_translators.push_back( translator ); + } + + virtual std::string translateActiveException() const { + try { +#ifdef __OBJC__ + // In Objective-C try objective-c exceptions first + @try { + throw; + } + @catch (NSException *exception) { + return Catch::toString( [exception description] ); + } +#else + throw; +#endif + } + catch( TestFailureException& ) { + throw; + } + catch( std::exception& ex ) { + return ex.what(); + } + catch( std::string& msg ) { + return msg; + } + catch( const char* msg ) { + return msg; + } + catch(...) { + return tryTranslators( m_translators.begin() ); + } + } + + std::string tryTranslators( std::vector::const_iterator it ) const { + if( it == m_translators.end() ) + return "Unknown exception"; + + try { + return (*it)->translate(); + } + catch(...) { + return tryTranslators( it+1 ); + } + } + + private: + std::vector m_translators; + }; +} + +namespace Catch { + + namespace { + + class RegistryHub : public IRegistryHub, public IMutableRegistryHub { + + RegistryHub( RegistryHub const& ); + void operator=( RegistryHub const& ); + + public: // IRegistryHub + RegistryHub() { + } + virtual IReporterRegistry const& getReporterRegistry() const { + return m_reporterRegistry; + } + virtual ITestCaseRegistry const& getTestCaseRegistry() const { + return m_testCaseRegistry; + } + virtual IExceptionTranslatorRegistry& getExceptionTranslatorRegistry() { + return m_exceptionTranslatorRegistry; + } + + public: // IMutableRegistryHub + virtual void registerReporter( std::string const& name, IReporterFactory* factory ) { + m_reporterRegistry.registerReporter( name, factory ); + } + virtual void registerTest( TestCase const& testInfo ) { + m_testCaseRegistry.registerTest( testInfo ); + } + virtual void registerTranslator( const IExceptionTranslator* translator ) { + m_exceptionTranslatorRegistry.registerTranslator( translator ); + } + + private: + TestRegistry m_testCaseRegistry; + ReporterRegistry m_reporterRegistry; + ExceptionTranslatorRegistry m_exceptionTranslatorRegistry; + }; + + // Single, global, instance + inline RegistryHub*& getTheRegistryHub() { + static RegistryHub* theRegistryHub = NULL; + if( !theRegistryHub ) + theRegistryHub = new RegistryHub(); + return theRegistryHub; + } + } + + IRegistryHub& getRegistryHub() { + return *getTheRegistryHub(); + } + IMutableRegistryHub& getMutableRegistryHub() { + return *getTheRegistryHub(); + } + void cleanUp() { + delete getTheRegistryHub(); + getTheRegistryHub() = NULL; + cleanUpContext(); + } + std::string translateActiveException() { + return getRegistryHub().getExceptionTranslatorRegistry().translateActiveException(); + } + +} // end namespace Catch + +// #included from: catch_notimplemented_exception.hpp +#define TWOBLUECUBES_CATCH_NOTIMPLEMENTED_EXCEPTION_HPP_INCLUDED + +#include + +namespace Catch { + + NotImplementedException::NotImplementedException( SourceLineInfo const& lineInfo ) + : m_lineInfo( lineInfo ) { + std::ostringstream oss; + oss << lineInfo << ": function "; + oss << "not implemented"; + m_what = oss.str(); + } + + const char* NotImplementedException::what() const CATCH_NOEXCEPT { + return m_what.c_str(); + } + +} // end namespace Catch + +// #included from: catch_context_impl.hpp +#define TWOBLUECUBES_CATCH_CONTEXT_IMPL_HPP_INCLUDED + +// #included from: catch_stream.hpp +#define TWOBLUECUBES_CATCH_STREAM_HPP_INCLUDED + +// #included from: catch_streambuf.h +#define TWOBLUECUBES_CATCH_STREAMBUF_H_INCLUDED + +#include + +namespace Catch { + + class StreamBufBase : public std::streambuf { + public: + virtual ~StreamBufBase() CATCH_NOEXCEPT; + }; +} + +#include +#include +#include + +namespace Catch { + + template + class StreamBufImpl : public StreamBufBase { + char data[bufferSize]; + WriterF m_writer; + + public: + StreamBufImpl() { + setp( data, data + sizeof(data) ); + } + + ~StreamBufImpl() CATCH_NOEXCEPT { + sync(); + } + + private: + int overflow( int c ) { + sync(); + + if( c != EOF ) { + if( pbase() == epptr() ) + m_writer( std::string( 1, static_cast( c ) ) ); + else + sputc( static_cast( c ) ); + } + return 0; + } + + int sync() { + if( pbase() != pptr() ) { + m_writer( std::string( pbase(), static_cast( pptr() - pbase() ) ) ); + setp( pbase(), epptr() ); + } + return 0; + } + }; + + /////////////////////////////////////////////////////////////////////////// + + struct OutputDebugWriter { + + void operator()( std::string const&str ) { + writeToDebugConsole( str ); + } + }; + + Stream::Stream() + : streamBuf( NULL ), isOwned( false ) + {} + + Stream::Stream( std::streambuf* _streamBuf, bool _isOwned ) + : streamBuf( _streamBuf ), isOwned( _isOwned ) + {} + + void Stream::release() { + if( isOwned ) { + delete streamBuf; + streamBuf = NULL; + isOwned = false; + } + } + +#ifndef CATCH_CONFIG_NOSTDOUT // If you #define this you must implement this functions + std::ostream& cout() { + return std::cout; + } + std::ostream& cerr() { + return std::cerr; + } +#endif +} + +namespace Catch { + + class Context : public IMutableContext { + + Context() : m_config( NULL ), m_runner( NULL ), m_resultCapture( NULL ) {} + Context( Context const& ); + void operator=( Context const& ); + + public: // IContext + virtual IResultCapture* getResultCapture() { + return m_resultCapture; + } + virtual IRunner* getRunner() { + return m_runner; + } + virtual size_t getGeneratorIndex( std::string const& fileInfo, size_t totalSize ) { + return getGeneratorsForCurrentTest() + .getGeneratorInfo( fileInfo, totalSize ) + .getCurrentIndex(); + } + virtual bool advanceGeneratorsForCurrentTest() { + IGeneratorsForTest* generators = findGeneratorsForCurrentTest(); + return generators && generators->moveNext(); + } + + virtual Ptr getConfig() const { + return m_config; + } + + public: // IMutableContext + virtual void setResultCapture( IResultCapture* resultCapture ) { + m_resultCapture = resultCapture; + } + virtual void setRunner( IRunner* runner ) { + m_runner = runner; + } + virtual void setConfig( Ptr const& config ) { + m_config = config; + } + + friend IMutableContext& getCurrentMutableContext(); + + private: + IGeneratorsForTest* findGeneratorsForCurrentTest() { + std::string testName = getResultCapture()->getCurrentTestName(); + + std::map::const_iterator it = + m_generatorsByTestName.find( testName ); + return it != m_generatorsByTestName.end() + ? it->second + : NULL; + } + + IGeneratorsForTest& getGeneratorsForCurrentTest() { + IGeneratorsForTest* generators = findGeneratorsForCurrentTest(); + if( !generators ) { + std::string testName = getResultCapture()->getCurrentTestName(); + generators = createGeneratorsForTest(); + m_generatorsByTestName.insert( std::make_pair( testName, generators ) ); + } + return *generators; + } + + private: + Ptr m_config; + IRunner* m_runner; + IResultCapture* m_resultCapture; + std::map m_generatorsByTestName; + }; + + namespace { + Context* currentContext = NULL; + } + IMutableContext& getCurrentMutableContext() { + if( !currentContext ) + currentContext = new Context(); + return *currentContext; + } + IContext& getCurrentContext() { + return getCurrentMutableContext(); + } + + Stream createStream( std::string const& streamName ) { + if( streamName == "stdout" ) return Stream( Catch::cout().rdbuf(), false ); + if( streamName == "stderr" ) return Stream( Catch::cerr().rdbuf(), false ); + if( streamName == "debug" ) return Stream( new StreamBufImpl, true ); + + throw std::domain_error( "Unknown stream: " + streamName ); + } + + void cleanUpContext() { + delete currentContext; + currentContext = NULL; + } +} + +// #included from: catch_console_colour_impl.hpp +#define TWOBLUECUBES_CATCH_CONSOLE_COLOUR_IMPL_HPP_INCLUDED + +namespace Catch { + namespace { + + struct IColourImpl { + virtual ~IColourImpl() {} + virtual void use( Colour::Code _colourCode ) = 0; + }; + + struct NoColourImpl : IColourImpl { + void use( Colour::Code ) {} + + static IColourImpl* instance() { + static NoColourImpl s_instance; + return &s_instance; + } + }; + + } // anon namespace +} // namespace Catch + +#if !defined( CATCH_CONFIG_COLOUR_NONE ) && !defined( CATCH_CONFIG_COLOUR_WINDOWS ) && !defined( CATCH_CONFIG_COLOUR_ANSI ) +# ifdef CATCH_PLATFORM_WINDOWS +# define CATCH_CONFIG_COLOUR_WINDOWS +# else +# define CATCH_CONFIG_COLOUR_ANSI +# endif +#endif + +#if defined ( CATCH_CONFIG_COLOUR_WINDOWS ) ///////////////////////////////////////// + +#ifndef NOMINMAX +#define NOMINMAX +#endif + +#ifdef __AFXDLL +#include +#else +#include +#endif + +namespace Catch { +namespace { + + class Win32ColourImpl : public IColourImpl { + public: + Win32ColourImpl() : stdoutHandle( GetStdHandle(STD_OUTPUT_HANDLE) ) + { + CONSOLE_SCREEN_BUFFER_INFO csbiInfo; + GetConsoleScreenBufferInfo( stdoutHandle, &csbiInfo ); + originalAttributes = csbiInfo.wAttributes; + } + + virtual void use( Colour::Code _colourCode ) { + switch( _colourCode ) { + case Colour::None: return setTextAttribute( originalAttributes ); + case Colour::White: return setTextAttribute( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE ); + case Colour::Red: return setTextAttribute( FOREGROUND_RED ); + case Colour::Green: return setTextAttribute( FOREGROUND_GREEN ); + case Colour::Blue: return setTextAttribute( FOREGROUND_BLUE ); + case Colour::Cyan: return setTextAttribute( FOREGROUND_BLUE | FOREGROUND_GREEN ); + case Colour::Yellow: return setTextAttribute( FOREGROUND_RED | FOREGROUND_GREEN ); + case Colour::Grey: return setTextAttribute( 0 ); + + case Colour::LightGrey: return setTextAttribute( FOREGROUND_INTENSITY ); + case Colour::BrightRed: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_RED ); + case Colour::BrightGreen: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_GREEN ); + case Colour::BrightWhite: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE ); + + case Colour::Bright: throw std::logic_error( "not a colour" ); + } + } + + private: + void setTextAttribute( WORD _textAttribute ) { + SetConsoleTextAttribute( stdoutHandle, _textAttribute ); + } + HANDLE stdoutHandle; + WORD originalAttributes; + }; + + IColourImpl* platformColourInstance() { + static Win32ColourImpl s_instance; + return &s_instance; + } + +} // end anon namespace +} // end namespace Catch + +#elif defined( CATCH_CONFIG_COLOUR_ANSI ) ////////////////////////////////////// + +#include + +namespace Catch { +namespace { + + // use POSIX/ ANSI console terminal codes + // Thanks to Adam Strzelecki for original contribution + // (http://github.com/nanoant) + // https://github.com/philsquared/Catch/pull/131 + class PosixColourImpl : public IColourImpl { + public: + virtual void use( Colour::Code _colourCode ) { + switch( _colourCode ) { + case Colour::None: + case Colour::White: return setColour( "[0m" ); + case Colour::Red: return setColour( "[0;31m" ); + case Colour::Green: return setColour( "[0;32m" ); + case Colour::Blue: return setColour( "[0:34m" ); + case Colour::Cyan: return setColour( "[0;36m" ); + case Colour::Yellow: return setColour( "[0;33m" ); + case Colour::Grey: return setColour( "[1;30m" ); + + case Colour::LightGrey: return setColour( "[0;37m" ); + case Colour::BrightRed: return setColour( "[1;31m" ); + case Colour::BrightGreen: return setColour( "[1;32m" ); + case Colour::BrightWhite: return setColour( "[1;37m" ); + + case Colour::Bright: throw std::logic_error( "not a colour" ); + } + } + static IColourImpl* instance() { + static PosixColourImpl s_instance; + return &s_instance; + } + + private: + void setColour( const char* _escapeCode ) { + Catch::cout() << '\033' << _escapeCode; + } + }; + + IColourImpl* platformColourInstance() { + Ptr config = getCurrentContext().getConfig(); + return (config && config->forceColour()) || isatty(STDOUT_FILENO) + ? PosixColourImpl::instance() + : NoColourImpl::instance(); + } + +} // end anon namespace +} // end namespace Catch + +#else // not Windows or ANSI /////////////////////////////////////////////// + +namespace Catch { + + static IColourImpl* platformColourInstance() { return NoColourImpl::instance(); } + +} // end namespace Catch + +#endif // Windows/ ANSI/ None + +namespace Catch { + + Colour::Colour( Code _colourCode ) : m_moved( false ) { use( _colourCode ); } + Colour::Colour( Colour const& _other ) : m_moved( false ) { const_cast( _other ).m_moved = true; } + Colour::~Colour(){ if( !m_moved ) use( None ); } + + void Colour::use( Code _colourCode ) { + static IColourImpl* impl = isDebuggerActive() + ? NoColourImpl::instance() + : platformColourInstance(); + impl->use( _colourCode ); + } + +} // end namespace Catch + +// #included from: catch_generators_impl.hpp +#define TWOBLUECUBES_CATCH_GENERATORS_IMPL_HPP_INCLUDED + +#include +#include +#include + +namespace Catch { + + struct GeneratorInfo : IGeneratorInfo { + + GeneratorInfo( std::size_t size ) + : m_size( size ), + m_currentIndex( 0 ) + {} + + bool moveNext() { + if( ++m_currentIndex == m_size ) { + m_currentIndex = 0; + return false; + } + return true; + } + + std::size_t getCurrentIndex() const { + return m_currentIndex; + } + + std::size_t m_size; + std::size_t m_currentIndex; + }; + + /////////////////////////////////////////////////////////////////////////// + + class GeneratorsForTest : public IGeneratorsForTest { + + public: + ~GeneratorsForTest() { + deleteAll( m_generatorsInOrder ); + } + + IGeneratorInfo& getGeneratorInfo( std::string const& fileInfo, std::size_t size ) { + std::map::const_iterator it = m_generatorsByName.find( fileInfo ); + if( it == m_generatorsByName.end() ) { + IGeneratorInfo* info = new GeneratorInfo( size ); + m_generatorsByName.insert( std::make_pair( fileInfo, info ) ); + m_generatorsInOrder.push_back( info ); + return *info; + } + return *it->second; + } + + bool moveNext() { + std::vector::const_iterator it = m_generatorsInOrder.begin(); + std::vector::const_iterator itEnd = m_generatorsInOrder.end(); + for(; it != itEnd; ++it ) { + if( (*it)->moveNext() ) + return true; + } + return false; + } + + private: + std::map m_generatorsByName; + std::vector m_generatorsInOrder; + }; + + IGeneratorsForTest* createGeneratorsForTest() + { + return new GeneratorsForTest(); + } + +} // end namespace Catch + +// #included from: catch_assertionresult.hpp +#define TWOBLUECUBES_CATCH_ASSERTIONRESULT_HPP_INCLUDED + +namespace Catch { + + AssertionInfo::AssertionInfo( std::string const& _macroName, + SourceLineInfo const& _lineInfo, + std::string const& _capturedExpression, + ResultDisposition::Flags _resultDisposition ) + : macroName( _macroName ), + lineInfo( _lineInfo ), + capturedExpression( _capturedExpression ), + resultDisposition( _resultDisposition ) + {} + + AssertionResult::AssertionResult() {} + + AssertionResult::AssertionResult( AssertionInfo const& info, AssertionResultData const& data ) + : m_info( info ), + m_resultData( data ) + {} + + AssertionResult::~AssertionResult() {} + + // Result was a success + bool AssertionResult::succeeded() const { + return Catch::isOk( m_resultData.resultType ); + } + + // Result was a success, or failure is suppressed + bool AssertionResult::isOk() const { + return Catch::isOk( m_resultData.resultType ) || shouldSuppressFailure( m_info.resultDisposition ); + } + + ResultWas::OfType AssertionResult::getResultType() const { + return m_resultData.resultType; + } + + bool AssertionResult::hasExpression() const { + return !m_info.capturedExpression.empty(); + } + + bool AssertionResult::hasMessage() const { + return !m_resultData.message.empty(); + } + + std::string AssertionResult::getExpression() const { + if( isFalseTest( m_info.resultDisposition ) ) + return "!" + m_info.capturedExpression; + else + return m_info.capturedExpression; + } + std::string AssertionResult::getExpressionInMacro() const { + if( m_info.macroName.empty() ) + return m_info.capturedExpression; + else + return m_info.macroName + "( " + m_info.capturedExpression + " )"; + } + + bool AssertionResult::hasExpandedExpression() const { + return hasExpression() && getExpandedExpression() != getExpression(); + } + + std::string AssertionResult::getExpandedExpression() const { + return m_resultData.reconstructedExpression; + } + + std::string AssertionResult::getMessage() const { + return m_resultData.message; + } + SourceLineInfo AssertionResult::getSourceInfo() const { + return m_info.lineInfo; + } + + std::string AssertionResult::getTestMacroName() const { + return m_info.macroName; + } + +} // end namespace Catch + +// #included from: catch_test_case_info.hpp +#define TWOBLUECUBES_CATCH_TEST_CASE_INFO_HPP_INCLUDED + +namespace Catch { + + inline TestCaseInfo::SpecialProperties parseSpecialTag( std::string const& tag ) { + if( startsWith( tag, "." ) || + tag == "hide" || + tag == "!hide" ) + return TestCaseInfo::IsHidden; + else if( tag == "!throws" ) + return TestCaseInfo::Throws; + else if( tag == "!shouldfail" ) + return TestCaseInfo::ShouldFail; + else if( tag == "!mayfail" ) + return TestCaseInfo::MayFail; + else + return TestCaseInfo::None; + } + inline bool isReservedTag( std::string const& tag ) { + return parseSpecialTag( tag ) == TestCaseInfo::None && tag.size() > 0 && !isalnum( tag[0] ); + } + inline void enforceNotReservedTag( std::string const& tag, SourceLineInfo const& _lineInfo ) { + if( isReservedTag( tag ) ) { + { + Colour colourGuard( Colour::Red ); + Catch::cerr() + << "Tag name [" << tag << "] not allowed.\n" + << "Tag names starting with non alpha-numeric characters are reserved\n"; + } + { + Colour colourGuard( Colour::FileName ); + Catch::cerr() << _lineInfo << std::endl; + } + exit(1); + } + } + + TestCase makeTestCase( ITestCase* _testCase, + std::string const& _className, + std::string const& _name, + std::string const& _descOrTags, + SourceLineInfo const& _lineInfo ) + { + bool isHidden( startsWith( _name, "./" ) ); // Legacy support + + // Parse out tags + std::set tags; + std::string desc, tag; + bool inTag = false; + for( std::size_t i = 0; i < _descOrTags.size(); ++i ) { + char c = _descOrTags[i]; + if( !inTag ) { + if( c == '[' ) + inTag = true; + else + desc += c; + } + else { + if( c == ']' ) { + TestCaseInfo::SpecialProperties prop = parseSpecialTag( tag ); + if( prop == TestCaseInfo::IsHidden ) + isHidden = true; + else if( prop == TestCaseInfo::None ) + enforceNotReservedTag( tag, _lineInfo ); + + tags.insert( tag ); + tag.clear(); + inTag = false; + } + else + tag += c; + } + } + if( isHidden ) { + tags.insert( "hide" ); + tags.insert( "." ); + } + + TestCaseInfo info( _name, _className, desc, tags, _lineInfo ); + return TestCase( _testCase, info ); + } + + TestCaseInfo::TestCaseInfo( std::string const& _name, + std::string const& _className, + std::string const& _description, + std::set const& _tags, + SourceLineInfo const& _lineInfo ) + : name( _name ), + className( _className ), + description( _description ), + tags( _tags ), + lineInfo( _lineInfo ), + properties( None ) + { + std::ostringstream oss; + for( std::set::const_iterator it = _tags.begin(), itEnd = _tags.end(); it != itEnd; ++it ) { + oss << "[" << *it << "]"; + std::string lcaseTag = toLower( *it ); + properties = static_cast( properties | parseSpecialTag( lcaseTag ) ); + lcaseTags.insert( lcaseTag ); + } + tagsAsString = oss.str(); + } + + TestCaseInfo::TestCaseInfo( TestCaseInfo const& other ) + : name( other.name ), + className( other.className ), + description( other.description ), + tags( other.tags ), + lcaseTags( other.lcaseTags ), + tagsAsString( other.tagsAsString ), + lineInfo( other.lineInfo ), + properties( other.properties ) + {} + + bool TestCaseInfo::isHidden() const { + return ( properties & IsHidden ) != 0; + } + bool TestCaseInfo::throws() const { + return ( properties & Throws ) != 0; + } + bool TestCaseInfo::okToFail() const { + return ( properties & (ShouldFail | MayFail ) ) != 0; + } + bool TestCaseInfo::expectedToFail() const { + return ( properties & (ShouldFail ) ) != 0; + } + + TestCase::TestCase( ITestCase* testCase, TestCaseInfo const& info ) : TestCaseInfo( info ), test( testCase ) {} + + TestCase::TestCase( TestCase const& other ) + : TestCaseInfo( other ), + test( other.test ) + {} + + TestCase TestCase::withName( std::string const& _newName ) const { + TestCase other( *this ); + other.name = _newName; + return other; + } + + void TestCase::swap( TestCase& other ) { + test.swap( other.test ); + name.swap( other.name ); + className.swap( other.className ); + description.swap( other.description ); + tags.swap( other.tags ); + lcaseTags.swap( other.lcaseTags ); + tagsAsString.swap( other.tagsAsString ); + std::swap( TestCaseInfo::properties, static_cast( other ).properties ); + std::swap( lineInfo, other.lineInfo ); + } + + void TestCase::invoke() const { + test->invoke(); + } + + bool TestCase::operator == ( TestCase const& other ) const { + return test.get() == other.test.get() && + name == other.name && + className == other.className; + } + + bool TestCase::operator < ( TestCase const& other ) const { + return name < other.name; + } + TestCase& TestCase::operator = ( TestCase const& other ) { + TestCase temp( other ); + swap( temp ); + return *this; + } + + TestCaseInfo const& TestCase::getTestCaseInfo() const + { + return *this; + } + +} // end namespace Catch + +// #included from: catch_version.hpp +#define TWOBLUECUBES_CATCH_VERSION_HPP_INCLUDED + +namespace Catch { + + Version::Version + ( unsigned int _majorVersion, + unsigned int _minorVersion, + unsigned int _patchNumber, + std::string const& _branchName, + unsigned int _buildNumber ) + : majorVersion( _majorVersion ), + minorVersion( _minorVersion ), + patchNumber( _patchNumber ), + branchName( _branchName ), + buildNumber( _buildNumber ) + {} + + std::ostream& operator << ( std::ostream& os, Version const& version ) { + os << version.majorVersion << "." + << version.minorVersion << "." + << version.patchNumber; + + if( !version.branchName.empty() ) { + os << "-" << version.branchName + << "." << version.buildNumber; + } + return os; + } + + Version libraryVersion( 1, 2, 1, "", 0 ); + +} + +// #included from: catch_message.hpp +#define TWOBLUECUBES_CATCH_MESSAGE_HPP_INCLUDED + +namespace Catch { + + MessageInfo::MessageInfo( std::string const& _macroName, + SourceLineInfo const& _lineInfo, + ResultWas::OfType _type ) + : macroName( _macroName ), + lineInfo( _lineInfo ), + type( _type ), + sequence( ++globalCount ) + {} + + // This may need protecting if threading support is added + unsigned int MessageInfo::globalCount = 0; + + //////////////////////////////////////////////////////////////////////////// + + ScopedMessage::ScopedMessage( MessageBuilder const& builder ) + : m_info( builder.m_info ) + { + m_info.message = builder.m_stream.str(); + getResultCapture().pushScopedMessage( m_info ); + } + ScopedMessage::ScopedMessage( ScopedMessage const& other ) + : m_info( other.m_info ) + {} + + ScopedMessage::~ScopedMessage() { + getResultCapture().popScopedMessage( m_info ); + } + +} // end namespace Catch + +// #included from: catch_legacy_reporter_adapter.hpp +#define TWOBLUECUBES_CATCH_LEGACY_REPORTER_ADAPTER_HPP_INCLUDED + +// #included from: catch_legacy_reporter_adapter.h +#define TWOBLUECUBES_CATCH_LEGACY_REPORTER_ADAPTER_H_INCLUDED + +namespace Catch +{ + // Deprecated + struct IReporter : IShared { + virtual ~IReporter(); + + virtual bool shouldRedirectStdout() const = 0; + + virtual void StartTesting() = 0; + virtual void EndTesting( Totals const& totals ) = 0; + virtual void StartGroup( std::string const& groupName ) = 0; + virtual void EndGroup( std::string const& groupName, Totals const& totals ) = 0; + virtual void StartTestCase( TestCaseInfo const& testInfo ) = 0; + virtual void EndTestCase( TestCaseInfo const& testInfo, Totals const& totals, std::string const& stdOut, std::string const& stdErr ) = 0; + virtual void StartSection( std::string const& sectionName, std::string const& description ) = 0; + virtual void EndSection( std::string const& sectionName, Counts const& assertions ) = 0; + virtual void NoAssertionsInSection( std::string const& sectionName ) = 0; + virtual void NoAssertionsInTestCase( std::string const& testName ) = 0; + virtual void Aborted() = 0; + virtual void Result( AssertionResult const& result ) = 0; + }; + + class LegacyReporterAdapter : public SharedImpl + { + public: + LegacyReporterAdapter( Ptr const& legacyReporter ); + virtual ~LegacyReporterAdapter(); + + virtual ReporterPreferences getPreferences() const; + virtual void noMatchingTestCases( std::string const& ); + virtual void testRunStarting( TestRunInfo const& ); + virtual void testGroupStarting( GroupInfo const& groupInfo ); + virtual void testCaseStarting( TestCaseInfo const& testInfo ); + virtual void sectionStarting( SectionInfo const& sectionInfo ); + virtual void assertionStarting( AssertionInfo const& ); + virtual bool assertionEnded( AssertionStats const& assertionStats ); + virtual void sectionEnded( SectionStats const& sectionStats ); + virtual void testCaseEnded( TestCaseStats const& testCaseStats ); + virtual void testGroupEnded( TestGroupStats const& testGroupStats ); + virtual void testRunEnded( TestRunStats const& testRunStats ); + virtual void skipTest( TestCaseInfo const& ); + + private: + Ptr m_legacyReporter; + }; +} + +namespace Catch +{ + LegacyReporterAdapter::LegacyReporterAdapter( Ptr const& legacyReporter ) + : m_legacyReporter( legacyReporter ) + {} + LegacyReporterAdapter::~LegacyReporterAdapter() {} + + ReporterPreferences LegacyReporterAdapter::getPreferences() const { + ReporterPreferences prefs; + prefs.shouldRedirectStdOut = m_legacyReporter->shouldRedirectStdout(); + return prefs; + } + + void LegacyReporterAdapter::noMatchingTestCases( std::string const& ) {} + void LegacyReporterAdapter::testRunStarting( TestRunInfo const& ) { + m_legacyReporter->StartTesting(); + } + void LegacyReporterAdapter::testGroupStarting( GroupInfo const& groupInfo ) { + m_legacyReporter->StartGroup( groupInfo.name ); + } + void LegacyReporterAdapter::testCaseStarting( TestCaseInfo const& testInfo ) { + m_legacyReporter->StartTestCase( testInfo ); + } + void LegacyReporterAdapter::sectionStarting( SectionInfo const& sectionInfo ) { + m_legacyReporter->StartSection( sectionInfo.name, sectionInfo.description ); + } + void LegacyReporterAdapter::assertionStarting( AssertionInfo const& ) { + // Not on legacy interface + } + + bool LegacyReporterAdapter::assertionEnded( AssertionStats const& assertionStats ) { + if( assertionStats.assertionResult.getResultType() != ResultWas::Ok ) { + for( std::vector::const_iterator it = assertionStats.infoMessages.begin(), itEnd = assertionStats.infoMessages.end(); + it != itEnd; + ++it ) { + if( it->type == ResultWas::Info ) { + ResultBuilder rb( it->macroName.c_str(), it->lineInfo, "", ResultDisposition::Normal ); + rb << it->message; + rb.setResultType( ResultWas::Info ); + AssertionResult result = rb.build(); + m_legacyReporter->Result( result ); + } + } + } + m_legacyReporter->Result( assertionStats.assertionResult ); + return true; + } + void LegacyReporterAdapter::sectionEnded( SectionStats const& sectionStats ) { + if( sectionStats.missingAssertions ) + m_legacyReporter->NoAssertionsInSection( sectionStats.sectionInfo.name ); + m_legacyReporter->EndSection( sectionStats.sectionInfo.name, sectionStats.assertions ); + } + void LegacyReporterAdapter::testCaseEnded( TestCaseStats const& testCaseStats ) { + m_legacyReporter->EndTestCase + ( testCaseStats.testInfo, + testCaseStats.totals, + testCaseStats.stdOut, + testCaseStats.stdErr ); + } + void LegacyReporterAdapter::testGroupEnded( TestGroupStats const& testGroupStats ) { + if( testGroupStats.aborting ) + m_legacyReporter->Aborted(); + m_legacyReporter->EndGroup( testGroupStats.groupInfo.name, testGroupStats.totals ); + } + void LegacyReporterAdapter::testRunEnded( TestRunStats const& testRunStats ) { + m_legacyReporter->EndTesting( testRunStats.totals ); + } + void LegacyReporterAdapter::skipTest( TestCaseInfo const& ) { + } +} + +// #included from: catch_timer.hpp + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wc++11-long-long" +#endif + +#ifdef CATCH_PLATFORM_WINDOWS +#include +#else +#include +#endif + +namespace Catch { + + namespace { +#ifdef CATCH_PLATFORM_WINDOWS + uint64_t getCurrentTicks() { + static uint64_t hz=0, hzo=0; + if (!hz) { + QueryPerformanceFrequency( reinterpret_cast( &hz ) ); + QueryPerformanceCounter( reinterpret_cast( &hzo ) ); + } + uint64_t t; + QueryPerformanceCounter( reinterpret_cast( &t ) ); + return ((t-hzo)*1000000)/hz; + } +#else + uint64_t getCurrentTicks() { + timeval t; + gettimeofday(&t,NULL); + return static_cast( t.tv_sec ) * 1000000ull + static_cast( t.tv_usec ); + } +#endif + } + + void Timer::start() { + m_ticks = getCurrentTicks(); + } + unsigned int Timer::getElapsedMicroseconds() const { + return static_cast(getCurrentTicks() - m_ticks); + } + unsigned int Timer::getElapsedMilliseconds() const { + return static_cast(getElapsedMicroseconds()/1000); + } + double Timer::getElapsedSeconds() const { + return getElapsedMicroseconds()/1000000.0; + } + +} // namespace Catch + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif +// #included from: catch_common.hpp +#define TWOBLUECUBES_CATCH_COMMON_HPP_INCLUDED + +namespace Catch { + + bool startsWith( std::string const& s, std::string const& prefix ) { + return s.size() >= prefix.size() && s.substr( 0, prefix.size() ) == prefix; + } + bool endsWith( std::string const& s, std::string const& suffix ) { + return s.size() >= suffix.size() && s.substr( s.size()-suffix.size(), suffix.size() ) == suffix; + } + bool contains( std::string const& s, std::string const& infix ) { + return s.find( infix ) != std::string::npos; + } + void toLowerInPlace( std::string& s ) { + std::transform( s.begin(), s.end(), s.begin(), ::tolower ); + } + std::string toLower( std::string const& s ) { + std::string lc = s; + toLowerInPlace( lc ); + return lc; + } + std::string trim( std::string const& str ) { + static char const* whitespaceChars = "\n\r\t "; + std::string::size_type start = str.find_first_not_of( whitespaceChars ); + std::string::size_type end = str.find_last_not_of( whitespaceChars ); + + return start != std::string::npos ? str.substr( start, 1+end-start ) : ""; + } + + bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis ) { + bool replaced = false; + std::size_t i = str.find( replaceThis ); + while( i != std::string::npos ) { + replaced = true; + str = str.substr( 0, i ) + withThis + str.substr( i+replaceThis.size() ); + if( i < str.size()-withThis.size() ) + i = str.find( replaceThis, i+withThis.size() ); + else + i = std::string::npos; + } + return replaced; + } + + pluralise::pluralise( std::size_t count, std::string const& label ) + : m_count( count ), + m_label( label ) + {} + + std::ostream& operator << ( std::ostream& os, pluralise const& pluraliser ) { + os << pluraliser.m_count << " " << pluraliser.m_label; + if( pluraliser.m_count != 1 ) + os << "s"; + return os; + } + + SourceLineInfo::SourceLineInfo() : line( 0 ){} + SourceLineInfo::SourceLineInfo( char const* _file, std::size_t _line ) + : file( _file ), + line( _line ) + {} + SourceLineInfo::SourceLineInfo( SourceLineInfo const& other ) + : file( other.file ), + line( other.line ) + {} + bool SourceLineInfo::empty() const { + return file.empty(); + } + bool SourceLineInfo::operator == ( SourceLineInfo const& other ) const { + return line == other.line && file == other.file; + } + bool SourceLineInfo::operator < ( SourceLineInfo const& other ) const { + return line < other.line || ( line == other.line && file < other.file ); + } + + std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ) { +#ifndef __GNUG__ + os << info.file << "(" << info.line << ")"; +#else + os << info.file << ":" << info.line; +#endif + return os; + } + + void throwLogicError( std::string const& message, SourceLineInfo const& locationInfo ) { + std::ostringstream oss; + oss << locationInfo << ": Internal Catch error: '" << message << "'"; + if( alwaysTrue() ) + throw std::logic_error( oss.str() ); + } +} + +// #included from: catch_section.hpp +#define TWOBLUECUBES_CATCH_SECTION_HPP_INCLUDED + +namespace Catch { + + SectionInfo::SectionInfo + ( SourceLineInfo const& _lineInfo, + std::string const& _name, + std::string const& _description ) + : name( _name ), + description( _description ), + lineInfo( _lineInfo ) + {} + + Section::Section( SectionInfo const& info ) + : m_info( info ), + m_sectionIncluded( getResultCapture().sectionStarted( m_info, m_assertions ) ) + { + m_timer.start(); + } + + Section::~Section() { + if( m_sectionIncluded ) + getResultCapture().sectionEnded( m_info, m_assertions, m_timer.getElapsedSeconds() ); + } + + // This indicates whether the section should be executed or not + Section::operator bool() const { + return m_sectionIncluded; + } + +} // end namespace Catch + +// #included from: catch_debugger.hpp +#define TWOBLUECUBES_CATCH_DEBUGGER_HPP_INCLUDED + +#include + +#ifdef CATCH_PLATFORM_MAC + + #include + #include + #include + #include + #include + + namespace Catch{ + + // The following function is taken directly from the following technical note: + // http://developer.apple.com/library/mac/#qa/qa2004/qa1361.html + + // Returns true if the current process is being debugged (either + // running under the debugger or has a debugger attached post facto). + bool isDebuggerActive(){ + + int mib[4]; + struct kinfo_proc info; + size_t size; + + // Initialize the flags so that, if sysctl fails for some bizarre + // reason, we get a predictable result. + + info.kp_proc.p_flag = 0; + + // Initialize mib, which tells sysctl the info we want, in this case + // we're looking for information about a specific process ID. + + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PID; + mib[3] = getpid(); + + // Call sysctl. + + size = sizeof(info); + if( sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, NULL, 0) != 0 ) { + Catch::cerr() << "\n** Call to sysctl failed - unable to determine if debugger is active **\n" << std::endl; + return false; + } + + // We're being debugged if the P_TRACED flag is set. + + return ( (info.kp_proc.p_flag & P_TRACED) != 0 ); + } + } // namespace Catch + +#elif defined(_MSC_VER) + extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent(); + namespace Catch { + bool isDebuggerActive() { + return IsDebuggerPresent() != 0; + } + } +#elif defined(__MINGW32__) + extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent(); + namespace Catch { + bool isDebuggerActive() { + return IsDebuggerPresent() != 0; + } + } +#else + namespace Catch { + inline bool isDebuggerActive() { return false; } + } +#endif // Platform + +#ifdef CATCH_PLATFORM_WINDOWS + extern "C" __declspec(dllimport) void __stdcall OutputDebugStringA( const char* ); + namespace Catch { + void writeToDebugConsole( std::string const& text ) { + ::OutputDebugStringA( text.c_str() ); + } + } +#else + namespace Catch { + void writeToDebugConsole( std::string const& text ) { + // !TBD: Need a version for Mac/ XCode and other IDEs + Catch::cout() << text; + } + } +#endif // Platform + +// #included from: catch_tostring.hpp +#define TWOBLUECUBES_CATCH_TOSTRING_HPP_INCLUDED + +namespace Catch { + +namespace Detail { + + std::string unprintableString = "{?}"; + + namespace { + struct Endianness { + enum Arch { Big, Little }; + + static Arch which() { + union _{ + int asInt; + char asChar[sizeof (int)]; + } u; + + u.asInt = 1; + return ( u.asChar[sizeof(int)-1] == 1 ) ? Big : Little; + } + }; + } + + std::string rawMemoryToString( const void *object, std::size_t size ) + { + // Reverse order for little endian architectures + int i = 0, end = static_cast( size ), inc = 1; + if( Endianness::which() == Endianness::Little ) { + i = end-1; + end = inc = -1; + } + + unsigned char const *bytes = static_cast(object); + std::ostringstream os; + os << "0x" << std::setfill('0') << std::hex; + for( ; i != end; i += inc ) + os << std::setw(2) << static_cast(bytes[i]); + return os.str(); + } +} + +std::string toString( std::string const& value ) { + std::string s = value; + if( getCurrentContext().getConfig()->showInvisibles() ) { + for(size_t i = 0; i < s.size(); ++i ) { + std::string subs; + switch( s[i] ) { + case '\n': subs = "\\n"; break; + case '\t': subs = "\\t"; break; + default: break; + } + if( !subs.empty() ) { + s = s.substr( 0, i ) + subs + s.substr( i+1 ); + ++i; + } + } + } + return "\"" + s + "\""; +} +std::string toString( std::wstring const& value ) { + + std::string s; + s.reserve( value.size() ); + for(size_t i = 0; i < value.size(); ++i ) + s += value[i] <= 0xff ? static_cast( value[i] ) : '?'; + return Catch::toString( s ); +} + +std::string toString( const char* const value ) { + return value ? Catch::toString( std::string( value ) ) : std::string( "{null string}" ); +} + +std::string toString( char* const value ) { + return Catch::toString( static_cast( value ) ); +} + +std::string toString( const wchar_t* const value ) +{ + return value ? Catch::toString( std::wstring(value) ) : std::string( "{null string}" ); +} + +std::string toString( wchar_t* const value ) +{ + return Catch::toString( static_cast( value ) ); +} + +std::string toString( int value ) { + std::ostringstream oss; + oss << value; + if( value >= 255 ) + oss << " (0x" << std::hex << value << ")"; + return oss.str(); +} + +std::string toString( unsigned long value ) { + std::ostringstream oss; + oss << value; + if( value >= 255 ) + oss << " (0x" << std::hex << value << ")"; + return oss.str(); +} + +std::string toString( unsigned int value ) { + return Catch::toString( static_cast( value ) ); +} + +template +std::string fpToString( T value, int precision ) { + std::ostringstream oss; + oss << std::setprecision( precision ) + << std::fixed + << value; + std::string d = oss.str(); + std::size_t i = d.find_last_not_of( '0' ); + if( i != std::string::npos && i != d.size()-1 ) { + if( d[i] == '.' ) + i++; + d = d.substr( 0, i+1 ); + } + return d; +} + +std::string toString( const double value ) { + return fpToString( value, 10 ); +} +std::string toString( const float value ) { + return fpToString( value, 5 ) + "f"; +} + +std::string toString( bool value ) { + return value ? "true" : "false"; +} + +std::string toString( char value ) { + return value < ' ' + ? toString( static_cast( value ) ) + : Detail::makeString( value ); +} + +std::string toString( signed char value ) { + return toString( static_cast( value ) ); +} + +std::string toString( unsigned char value ) { + return toString( static_cast( value ) ); +} + +#ifdef CATCH_CONFIG_CPP11_NULLPTR +std::string toString( std::nullptr_t ) { + return "nullptr"; +} +#endif + +#ifdef __OBJC__ + std::string toString( NSString const * const& nsstring ) { + if( !nsstring ) + return "nil"; + return "@" + toString([nsstring UTF8String]); + } + std::string toString( NSString * CATCH_ARC_STRONG const& nsstring ) { + if( !nsstring ) + return "nil"; + return "@" + toString([nsstring UTF8String]); + } + std::string toString( NSObject* const& nsObject ) { + return toString( [nsObject description] ); + } +#endif + +} // end namespace Catch + +// #included from: catch_result_builder.hpp +#define TWOBLUECUBES_CATCH_RESULT_BUILDER_HPP_INCLUDED + +namespace Catch { + + ResultBuilder::ResultBuilder( char const* macroName, + SourceLineInfo const& lineInfo, + char const* capturedExpression, + ResultDisposition::Flags resultDisposition ) + : m_assertionInfo( macroName, lineInfo, capturedExpression, resultDisposition ), + m_shouldDebugBreak( false ), + m_shouldThrow( false ) + {} + + ResultBuilder& ResultBuilder::setResultType( ResultWas::OfType result ) { + m_data.resultType = result; + return *this; + } + ResultBuilder& ResultBuilder::setResultType( bool result ) { + m_data.resultType = result ? ResultWas::Ok : ResultWas::ExpressionFailed; + return *this; + } + ResultBuilder& ResultBuilder::setLhs( std::string const& lhs ) { + m_exprComponents.lhs = lhs; + return *this; + } + ResultBuilder& ResultBuilder::setRhs( std::string const& rhs ) { + m_exprComponents.rhs = rhs; + return *this; + } + ResultBuilder& ResultBuilder::setOp( std::string const& op ) { + m_exprComponents.op = op; + return *this; + } + + void ResultBuilder::endExpression() { + m_exprComponents.testFalse = isFalseTest( m_assertionInfo.resultDisposition ); + captureExpression(); + } + + void ResultBuilder::useActiveException( ResultDisposition::Flags resultDisposition ) { + m_assertionInfo.resultDisposition = resultDisposition; + m_stream.oss << Catch::translateActiveException(); + captureResult( ResultWas::ThrewException ); + } + + void ResultBuilder::captureResult( ResultWas::OfType resultType ) { + setResultType( resultType ); + captureExpression(); + } + + void ResultBuilder::captureExpression() { + AssertionResult result = build(); + getResultCapture().assertionEnded( result ); + + if( !result.isOk() ) { + if( getCurrentContext().getConfig()->shouldDebugBreak() ) + m_shouldDebugBreak = true; + if( getCurrentContext().getRunner()->aborting() || (m_assertionInfo.resultDisposition & ResultDisposition::Normal) ) + m_shouldThrow = true; + } + } + void ResultBuilder::react() { + if( m_shouldThrow ) + throw Catch::TestFailureException(); + } + + bool ResultBuilder::shouldDebugBreak() const { return m_shouldDebugBreak; } + bool ResultBuilder::allowThrows() const { return getCurrentContext().getConfig()->allowThrows(); } + + AssertionResult ResultBuilder::build() const + { + assert( m_data.resultType != ResultWas::Unknown ); + + AssertionResultData data = m_data; + + // Flip bool results if testFalse is set + if( m_exprComponents.testFalse ) { + if( data.resultType == ResultWas::Ok ) + data.resultType = ResultWas::ExpressionFailed; + else if( data.resultType == ResultWas::ExpressionFailed ) + data.resultType = ResultWas::Ok; + } + + data.message = m_stream.oss.str(); + data.reconstructedExpression = reconstructExpression(); + if( m_exprComponents.testFalse ) { + if( m_exprComponents.op == "" ) + data.reconstructedExpression = "!" + data.reconstructedExpression; + else + data.reconstructedExpression = "!(" + data.reconstructedExpression + ")"; + } + return AssertionResult( m_assertionInfo, data ); + } + std::string ResultBuilder::reconstructExpression() const { + if( m_exprComponents.op == "" ) + return m_exprComponents.lhs.empty() ? m_assertionInfo.capturedExpression : m_exprComponents.op + m_exprComponents.lhs; + else if( m_exprComponents.op == "matches" ) + return m_exprComponents.lhs + " " + m_exprComponents.rhs; + else if( m_exprComponents.op != "!" ) { + if( m_exprComponents.lhs.size() + m_exprComponents.rhs.size() < 40 && + m_exprComponents.lhs.find("\n") == std::string::npos && + m_exprComponents.rhs.find("\n") == std::string::npos ) + return m_exprComponents.lhs + " " + m_exprComponents.op + " " + m_exprComponents.rhs; + else + return m_exprComponents.lhs + "\n" + m_exprComponents.op + "\n" + m_exprComponents.rhs; + } + else + return "{can't expand - use " + m_assertionInfo.macroName + "_FALSE( " + m_assertionInfo.capturedExpression.substr(1) + " ) instead of " + m_assertionInfo.macroName + "( " + m_assertionInfo.capturedExpression + " ) for better diagnostics}"; + } + +} // end namespace Catch + +// #included from: catch_tag_alias_registry.hpp +#define TWOBLUECUBES_CATCH_TAG_ALIAS_REGISTRY_HPP_INCLUDED + +// #included from: catch_tag_alias_registry.h +#define TWOBLUECUBES_CATCH_TAG_ALIAS_REGISTRY_H_INCLUDED + +#include + +namespace Catch { + + class TagAliasRegistry : public ITagAliasRegistry { + public: + virtual ~TagAliasRegistry(); + virtual Option find( std::string const& alias ) const; + virtual std::string expandAliases( std::string const& unexpandedTestSpec ) const; + void add( char const* alias, char const* tag, SourceLineInfo const& lineInfo ); + static TagAliasRegistry& get(); + + private: + std::map m_registry; + }; + +} // end namespace Catch + +#include +#include + +namespace Catch { + + TagAliasRegistry::~TagAliasRegistry() {} + + Option TagAliasRegistry::find( std::string const& alias ) const { + std::map::const_iterator it = m_registry.find( alias ); + if( it != m_registry.end() ) + return it->second; + else + return Option(); + } + + std::string TagAliasRegistry::expandAliases( std::string const& unexpandedTestSpec ) const { + std::string expandedTestSpec = unexpandedTestSpec; + for( std::map::const_iterator it = m_registry.begin(), itEnd = m_registry.end(); + it != itEnd; + ++it ) { + std::size_t pos = expandedTestSpec.find( it->first ); + if( pos != std::string::npos ) { + expandedTestSpec = expandedTestSpec.substr( 0, pos ) + + it->second.tag + + expandedTestSpec.substr( pos + it->first.size() ); + } + } + return expandedTestSpec; + } + + void TagAliasRegistry::add( char const* alias, char const* tag, SourceLineInfo const& lineInfo ) { + + if( !startsWith( alias, "[@" ) || !endsWith( alias, "]" ) ) { + std::ostringstream oss; + oss << "error: tag alias, \"" << alias << "\" is not of the form [@alias name].\n" << lineInfo; + throw std::domain_error( oss.str().c_str() ); + } + if( !m_registry.insert( std::make_pair( alias, TagAlias( tag, lineInfo ) ) ).second ) { + std::ostringstream oss; + oss << "error: tag alias, \"" << alias << "\" already registered.\n" + << "\tFirst seen at " << find(alias)->lineInfo << "\n" + << "\tRedefined at " << lineInfo; + throw std::domain_error( oss.str().c_str() ); + } + } + + TagAliasRegistry& TagAliasRegistry::get() { + static TagAliasRegistry instance; + return instance; + + } + + ITagAliasRegistry::~ITagAliasRegistry() {} + ITagAliasRegistry const& ITagAliasRegistry::get() { return TagAliasRegistry::get(); } + + RegistrarForTagAliases::RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo ) { + try { + TagAliasRegistry::get().add( alias, tag, lineInfo ); + } + catch( std::exception& ex ) { + Colour colourGuard( Colour::Red ); + Catch::cerr() << ex.what() << std::endl; + exit(1); + } + } + +} // end namespace Catch + +// #included from: ../reporters/catch_reporter_xml.hpp +#define TWOBLUECUBES_CATCH_REPORTER_XML_HPP_INCLUDED + +// #included from: catch_reporter_bases.hpp +#define TWOBLUECUBES_CATCH_REPORTER_BASES_HPP_INCLUDED + +#include + +namespace Catch { + + struct StreamingReporterBase : SharedImpl { + + StreamingReporterBase( ReporterConfig const& _config ) + : m_config( _config.fullConfig() ), + stream( _config.stream() ) + {} + + virtual ~StreamingReporterBase(); + + virtual void noMatchingTestCases( std::string const& ) {} + + virtual void testRunStarting( TestRunInfo const& _testRunInfo ) { + currentTestRunInfo = _testRunInfo; + } + virtual void testGroupStarting( GroupInfo const& _groupInfo ) { + currentGroupInfo = _groupInfo; + } + + virtual void testCaseStarting( TestCaseInfo const& _testInfo ) { + currentTestCaseInfo = _testInfo; + } + virtual void sectionStarting( SectionInfo const& _sectionInfo ) { + m_sectionStack.push_back( _sectionInfo ); + } + + virtual void sectionEnded( SectionStats const& /* _sectionStats */ ) { + m_sectionStack.pop_back(); + } + virtual void testCaseEnded( TestCaseStats const& /* _testCaseStats */ ) { + currentTestCaseInfo.reset(); + } + virtual void testGroupEnded( TestGroupStats const& /* _testGroupStats */ ) { + currentGroupInfo.reset(); + } + virtual void testRunEnded( TestRunStats const& /* _testRunStats */ ) { + currentTestCaseInfo.reset(); + currentGroupInfo.reset(); + currentTestRunInfo.reset(); + } + + virtual void skipTest( TestCaseInfo const& ) { + // Don't do anything with this by default. + // It can optionally be overridden in the derived class. + } + + Ptr m_config; + std::ostream& stream; + + LazyStat currentTestRunInfo; + LazyStat currentGroupInfo; + LazyStat currentTestCaseInfo; + + std::vector m_sectionStack; + }; + + struct CumulativeReporterBase : SharedImpl { + template + struct Node : SharedImpl<> { + explicit Node( T const& _value ) : value( _value ) {} + virtual ~Node() {} + + typedef std::vector > ChildNodes; + T value; + ChildNodes children; + }; + struct SectionNode : SharedImpl<> { + explicit SectionNode( SectionStats const& _stats ) : stats( _stats ) {} + virtual ~SectionNode(); + + bool operator == ( SectionNode const& other ) const { + return stats.sectionInfo.lineInfo == other.stats.sectionInfo.lineInfo; + } + bool operator == ( Ptr const& other ) const { + return operator==( *other ); + } + + SectionStats stats; + typedef std::vector > ChildSections; + typedef std::vector Assertions; + ChildSections childSections; + Assertions assertions; + std::string stdOut; + std::string stdErr; + }; + + struct BySectionInfo { + BySectionInfo( SectionInfo const& other ) : m_other( other ) {} + BySectionInfo( BySectionInfo const& other ) : m_other( other.m_other ) {} + bool operator() ( Ptr const& node ) const { + return node->stats.sectionInfo.lineInfo == m_other.lineInfo; + } + private: + void operator=( BySectionInfo const& ); + SectionInfo const& m_other; + }; + + typedef Node TestCaseNode; + typedef Node TestGroupNode; + typedef Node TestRunNode; + + CumulativeReporterBase( ReporterConfig const& _config ) + : m_config( _config.fullConfig() ), + stream( _config.stream() ) + {} + ~CumulativeReporterBase(); + + virtual void testRunStarting( TestRunInfo const& ) {} + virtual void testGroupStarting( GroupInfo const& ) {} + + virtual void testCaseStarting( TestCaseInfo const& ) {} + + virtual void sectionStarting( SectionInfo const& sectionInfo ) { + SectionStats incompleteStats( sectionInfo, Counts(), 0, false ); + Ptr node; + if( m_sectionStack.empty() ) { + if( !m_rootSection ) + m_rootSection = new SectionNode( incompleteStats ); + node = m_rootSection; + } + else { + SectionNode& parentNode = *m_sectionStack.back(); + SectionNode::ChildSections::const_iterator it = + std::find_if( parentNode.childSections.begin(), + parentNode.childSections.end(), + BySectionInfo( sectionInfo ) ); + if( it == parentNode.childSections.end() ) { + node = new SectionNode( incompleteStats ); + parentNode.childSections.push_back( node ); + } + else + node = *it; + } + m_sectionStack.push_back( node ); + m_deepestSection = node; + } + + virtual void assertionStarting( AssertionInfo const& ) {} + + virtual bool assertionEnded( AssertionStats const& assertionStats ) { + assert( !m_sectionStack.empty() ); + SectionNode& sectionNode = *m_sectionStack.back(); + sectionNode.assertions.push_back( assertionStats ); + return true; + } + virtual void sectionEnded( SectionStats const& sectionStats ) { + assert( !m_sectionStack.empty() ); + SectionNode& node = *m_sectionStack.back(); + node.stats = sectionStats; + m_sectionStack.pop_back(); + } + virtual void testCaseEnded( TestCaseStats const& testCaseStats ) { + Ptr node = new TestCaseNode( testCaseStats ); + assert( m_sectionStack.size() == 0 ); + node->children.push_back( m_rootSection ); + m_testCases.push_back( node ); + m_rootSection.reset(); + + assert( m_deepestSection ); + m_deepestSection->stdOut = testCaseStats.stdOut; + m_deepestSection->stdErr = testCaseStats.stdErr; + } + virtual void testGroupEnded( TestGroupStats const& testGroupStats ) { + Ptr node = new TestGroupNode( testGroupStats ); + node->children.swap( m_testCases ); + m_testGroups.push_back( node ); + } + virtual void testRunEnded( TestRunStats const& testRunStats ) { + Ptr node = new TestRunNode( testRunStats ); + node->children.swap( m_testGroups ); + m_testRuns.push_back( node ); + testRunEndedCumulative(); + } + virtual void testRunEndedCumulative() = 0; + + virtual void skipTest( TestCaseInfo const& ) {} + + Ptr m_config; + std::ostream& stream; + std::vector m_assertions; + std::vector > > m_sections; + std::vector > m_testCases; + std::vector > m_testGroups; + + std::vector > m_testRuns; + + Ptr m_rootSection; + Ptr m_deepestSection; + std::vector > m_sectionStack; + + }; + + template + char const* getLineOfChars() { + static char line[CATCH_CONFIG_CONSOLE_WIDTH] = {0}; + if( !*line ) { + memset( line, C, CATCH_CONFIG_CONSOLE_WIDTH-1 ); + line[CATCH_CONFIG_CONSOLE_WIDTH-1] = 0; + } + return line; + } + +} // end namespace Catch + +// #included from: ../internal/catch_reporter_registrars.hpp +#define TWOBLUECUBES_CATCH_REPORTER_REGISTRARS_HPP_INCLUDED + +namespace Catch { + + template + class LegacyReporterRegistrar { + + class ReporterFactory : public IReporterFactory { + virtual IStreamingReporter* create( ReporterConfig const& config ) const { + return new LegacyReporterAdapter( new T( config ) ); + } + + virtual std::string getDescription() const { + return T::getDescription(); + } + }; + + public: + + LegacyReporterRegistrar( std::string const& name ) { + getMutableRegistryHub().registerReporter( name, new ReporterFactory() ); + } + }; + + template + class ReporterRegistrar { + + class ReporterFactory : public IReporterFactory { + + // *** Please Note ***: + // - If you end up here looking at a compiler error because it's trying to register + // your custom reporter class be aware that the native reporter interface has changed + // to IStreamingReporter. The "legacy" interface, IReporter, is still supported via + // an adapter. Just use REGISTER_LEGACY_REPORTER to take advantage of the adapter. + // However please consider updating to the new interface as the old one is now + // deprecated and will probably be removed quite soon! + // Please contact me via github if you have any questions at all about this. + // In fact, ideally, please contact me anyway to let me know you've hit this - as I have + // no idea who is actually using custom reporters at all (possibly no-one!). + // The new interface is designed to minimise exposure to interface changes in the future. + virtual IStreamingReporter* create( ReporterConfig const& config ) const { + return new T( config ); + } + + virtual std::string getDescription() const { + return T::getDescription(); + } + }; + + public: + + ReporterRegistrar( std::string const& name ) { + getMutableRegistryHub().registerReporter( name, new ReporterFactory() ); + } + }; +} + +#define INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) \ + namespace{ Catch::LegacyReporterRegistrar catch_internal_RegistrarFor##reporterType( name ); } +#define INTERNAL_CATCH_REGISTER_REPORTER( name, reporterType ) \ + namespace{ Catch::ReporterRegistrar catch_internal_RegistrarFor##reporterType( name ); } + +// #included from: ../internal/catch_xmlwriter.hpp +#define TWOBLUECUBES_CATCH_XMLWRITER_HPP_INCLUDED + +#include +#include +#include + +namespace Catch { + + class XmlWriter { + public: + + class ScopedElement { + public: + ScopedElement( XmlWriter* writer ) + : m_writer( writer ) + {} + + ScopedElement( ScopedElement const& other ) + : m_writer( other.m_writer ){ + other.m_writer = NULL; + } + + ~ScopedElement() { + if( m_writer ) + m_writer->endElement(); + } + + ScopedElement& writeText( std::string const& text, bool indent = true ) { + m_writer->writeText( text, indent ); + return *this; + } + + template + ScopedElement& writeAttribute( std::string const& name, T const& attribute ) { + m_writer->writeAttribute( name, attribute ); + return *this; + } + + private: + mutable XmlWriter* m_writer; + }; + + XmlWriter() + : m_tagIsOpen( false ), + m_needsNewline( false ), + m_os( &Catch::cout() ) + {} + + XmlWriter( std::ostream& os ) + : m_tagIsOpen( false ), + m_needsNewline( false ), + m_os( &os ) + {} + + ~XmlWriter() { + while( !m_tags.empty() ) + endElement(); + } + + XmlWriter& startElement( std::string const& name ) { + ensureTagClosed(); + newlineIfNecessary(); + stream() << m_indent << "<" << name; + m_tags.push_back( name ); + m_indent += " "; + m_tagIsOpen = true; + return *this; + } + + ScopedElement scopedElement( std::string const& name ) { + ScopedElement scoped( this ); + startElement( name ); + return scoped; + } + + XmlWriter& endElement() { + newlineIfNecessary(); + m_indent = m_indent.substr( 0, m_indent.size()-2 ); + if( m_tagIsOpen ) { + stream() << "/>\n"; + m_tagIsOpen = false; + } + else { + stream() << m_indent << "\n"; + } + m_tags.pop_back(); + return *this; + } + + XmlWriter& writeAttribute( std::string const& name, std::string const& attribute ) { + if( !name.empty() && !attribute.empty() ) { + stream() << " " << name << "=\""; + writeEncodedText( attribute ); + stream() << "\""; + } + return *this; + } + + XmlWriter& writeAttribute( std::string const& name, bool attribute ) { + stream() << " " << name << "=\"" << ( attribute ? "true" : "false" ) << "\""; + return *this; + } + + template + XmlWriter& writeAttribute( std::string const& name, T const& attribute ) { + if( !name.empty() ) + stream() << " " << name << "=\"" << attribute << "\""; + return *this; + } + + XmlWriter& writeText( std::string const& text, bool indent = true ) { + if( !text.empty() ){ + bool tagWasOpen = m_tagIsOpen; + ensureTagClosed(); + if( tagWasOpen && indent ) + stream() << m_indent; + writeEncodedText( text ); + m_needsNewline = true; + } + return *this; + } + + XmlWriter& writeComment( std::string const& text ) { + ensureTagClosed(); + stream() << m_indent << ""; + m_needsNewline = true; + return *this; + } + + XmlWriter& writeBlankLine() { + ensureTagClosed(); + stream() << "\n"; + return *this; + } + + void setStream( std::ostream& os ) { + m_os = &os; + } + + private: + XmlWriter( XmlWriter const& ); + void operator=( XmlWriter const& ); + + std::ostream& stream() { + return *m_os; + } + + void ensureTagClosed() { + if( m_tagIsOpen ) { + stream() << ">\n"; + m_tagIsOpen = false; + } + } + + void newlineIfNecessary() { + if( m_needsNewline ) { + stream() << "\n"; + m_needsNewline = false; + } + } + + void writeEncodedText( std::string const& text ) { + static const char* charsToEncode = "<&\""; + std::string mtext = text; + std::string::size_type pos = mtext.find_first_of( charsToEncode ); + while( pos != std::string::npos ) { + stream() << mtext.substr( 0, pos ); + + switch( mtext[pos] ) { + case '<': + stream() << "<"; + break; + case '&': + stream() << "&"; + break; + case '\"': + stream() << """; + break; + } + mtext = mtext.substr( pos+1 ); + pos = mtext.find_first_of( charsToEncode ); + } + stream() << mtext; + } + + bool m_tagIsOpen; + bool m_needsNewline; + std::vector m_tags; + std::string m_indent; + std::ostream* m_os; + }; + +} +namespace Catch { + class XmlReporter : public StreamingReporterBase { + public: + XmlReporter( ReporterConfig const& _config ) + : StreamingReporterBase( _config ), + m_sectionDepth( 0 ) + {} + + virtual ~XmlReporter(); + + static std::string getDescription() { + return "Reports test results as an XML document"; + } + + public: // StreamingReporterBase + virtual ReporterPreferences getPreferences() const { + ReporterPreferences prefs; + prefs.shouldRedirectStdOut = true; + return prefs; + } + + virtual void noMatchingTestCases( std::string const& s ) { + StreamingReporterBase::noMatchingTestCases( s ); + } + + virtual void testRunStarting( TestRunInfo const& testInfo ) { + StreamingReporterBase::testRunStarting( testInfo ); + m_xml.setStream( stream ); + m_xml.startElement( "Catch" ); + if( !m_config->name().empty() ) + m_xml.writeAttribute( "name", m_config->name() ); + } + + virtual void testGroupStarting( GroupInfo const& groupInfo ) { + StreamingReporterBase::testGroupStarting( groupInfo ); + m_xml.startElement( "Group" ) + .writeAttribute( "name", groupInfo.name ); + } + + virtual void testCaseStarting( TestCaseInfo const& testInfo ) { + StreamingReporterBase::testCaseStarting(testInfo); + m_xml.startElement( "TestCase" ).writeAttribute( "name", trim( testInfo.name ) ); + + if ( m_config->showDurations() == ShowDurations::Always ) + m_testCaseTimer.start(); + } + + virtual void sectionStarting( SectionInfo const& sectionInfo ) { + StreamingReporterBase::sectionStarting( sectionInfo ); + if( m_sectionDepth++ > 0 ) { + m_xml.startElement( "Section" ) + .writeAttribute( "name", trim( sectionInfo.name ) ) + .writeAttribute( "description", sectionInfo.description ); + } + } + + virtual void assertionStarting( AssertionInfo const& ) { } + + virtual bool assertionEnded( AssertionStats const& assertionStats ) { + const AssertionResult& assertionResult = assertionStats.assertionResult; + + // Print any info messages in tags. + if( assertionStats.assertionResult.getResultType() != ResultWas::Ok ) { + for( std::vector::const_iterator it = assertionStats.infoMessages.begin(), itEnd = assertionStats.infoMessages.end(); + it != itEnd; + ++it ) { + if( it->type == ResultWas::Info ) { + m_xml.scopedElement( "Info" ) + .writeText( it->message ); + } else if ( it->type == ResultWas::Warning ) { + m_xml.scopedElement( "Warning" ) + .writeText( it->message ); + } + } + } + + // Drop out if result was successful but we're not printing them. + if( !m_config->includeSuccessfulResults() && isOk(assertionResult.getResultType()) ) + return true; + + // Print the expression if there is one. + if( assertionResult.hasExpression() ) { + m_xml.startElement( "Expression" ) + .writeAttribute( "success", assertionResult.succeeded() ) + .writeAttribute( "type", assertionResult.getTestMacroName() ) + .writeAttribute( "filename", assertionResult.getSourceInfo().file ) + .writeAttribute( "line", assertionResult.getSourceInfo().line ); + + m_xml.scopedElement( "Original" ) + .writeText( assertionResult.getExpression() ); + m_xml.scopedElement( "Expanded" ) + .writeText( assertionResult.getExpandedExpression() ); + } + + // And... Print a result applicable to each result type. + switch( assertionResult.getResultType() ) { + case ResultWas::ThrewException: + m_xml.scopedElement( "Exception" ) + .writeAttribute( "filename", assertionResult.getSourceInfo().file ) + .writeAttribute( "line", assertionResult.getSourceInfo().line ) + .writeText( assertionResult.getMessage() ); + break; + case ResultWas::FatalErrorCondition: + m_xml.scopedElement( "Fatal Error Condition" ) + .writeAttribute( "filename", assertionResult.getSourceInfo().file ) + .writeAttribute( "line", assertionResult.getSourceInfo().line ) + .writeText( assertionResult.getMessage() ); + break; + case ResultWas::Info: + m_xml.scopedElement( "Info" ) + .writeText( assertionResult.getMessage() ); + break; + case ResultWas::Warning: + // Warning will already have been written + break; + case ResultWas::ExplicitFailure: + m_xml.scopedElement( "Failure" ) + .writeText( assertionResult.getMessage() ); + break; + default: + break; + } + + if( assertionResult.hasExpression() ) + m_xml.endElement(); + + return true; + } + + virtual void sectionEnded( SectionStats const& sectionStats ) { + StreamingReporterBase::sectionEnded( sectionStats ); + if( --m_sectionDepth > 0 ) { + XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResults" ); + e.writeAttribute( "successes", sectionStats.assertions.passed ); + e.writeAttribute( "failures", sectionStats.assertions.failed ); + e.writeAttribute( "expectedFailures", sectionStats.assertions.failedButOk ); + + if ( m_config->showDurations() == ShowDurations::Always ) + e.writeAttribute( "durationInSeconds", sectionStats.durationInSeconds ); + + m_xml.endElement(); + } + } + + virtual void testCaseEnded( TestCaseStats const& testCaseStats ) { + StreamingReporterBase::testCaseEnded( testCaseStats ); + XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResult" ); + e.writeAttribute( "success", testCaseStats.totals.assertions.allOk() ); + + if ( m_config->showDurations() == ShowDurations::Always ) + e.writeAttribute( "durationInSeconds", m_testCaseTimer.getElapsedSeconds() ); + + m_xml.endElement(); + } + + virtual void testGroupEnded( TestGroupStats const& testGroupStats ) { + StreamingReporterBase::testGroupEnded( testGroupStats ); + // TODO: Check testGroupStats.aborting and act accordingly. + m_xml.scopedElement( "OverallResults" ) + .writeAttribute( "successes", testGroupStats.totals.assertions.passed ) + .writeAttribute( "failures", testGroupStats.totals.assertions.failed ) + .writeAttribute( "expectedFailures", testGroupStats.totals.assertions.failedButOk ); + m_xml.endElement(); + } + + virtual void testRunEnded( TestRunStats const& testRunStats ) { + StreamingReporterBase::testRunEnded( testRunStats ); + m_xml.scopedElement( "OverallResults" ) + .writeAttribute( "successes", testRunStats.totals.assertions.passed ) + .writeAttribute( "failures", testRunStats.totals.assertions.failed ) + .writeAttribute( "expectedFailures", testRunStats.totals.assertions.failedButOk ); + m_xml.endElement(); + } + + private: + Timer m_testCaseTimer; + XmlWriter m_xml; + int m_sectionDepth; + }; + + INTERNAL_CATCH_REGISTER_REPORTER( "xml", XmlReporter ) + +} // end namespace Catch + +// #included from: ../reporters/catch_reporter_junit.hpp +#define TWOBLUECUBES_CATCH_REPORTER_JUNIT_HPP_INCLUDED + +#include + +namespace Catch { + + class JunitReporter : public CumulativeReporterBase { + public: + JunitReporter( ReporterConfig const& _config ) + : CumulativeReporterBase( _config ), + xml( _config.stream() ) + {} + + ~JunitReporter(); + + static std::string getDescription() { + return "Reports test results in an XML format that looks like Ant's junitreport target"; + } + + virtual void noMatchingTestCases( std::string const& /*spec*/ ) {} + + virtual ReporterPreferences getPreferences() const { + ReporterPreferences prefs; + prefs.shouldRedirectStdOut = true; + return prefs; + } + + virtual void testRunStarting( TestRunInfo const& runInfo ) { + CumulativeReporterBase::testRunStarting( runInfo ); + xml.startElement( "testsuites" ); + } + + virtual void testGroupStarting( GroupInfo const& groupInfo ) { + suiteTimer.start(); + stdOutForSuite.str(""); + stdErrForSuite.str(""); + unexpectedExceptions = 0; + CumulativeReporterBase::testGroupStarting( groupInfo ); + } + + virtual bool assertionEnded( AssertionStats const& assertionStats ) { + if( assertionStats.assertionResult.getResultType() == ResultWas::ThrewException ) + unexpectedExceptions++; + return CumulativeReporterBase::assertionEnded( assertionStats ); + } + + virtual void testCaseEnded( TestCaseStats const& testCaseStats ) { + stdOutForSuite << testCaseStats.stdOut; + stdErrForSuite << testCaseStats.stdErr; + CumulativeReporterBase::testCaseEnded( testCaseStats ); + } + + virtual void testGroupEnded( TestGroupStats const& testGroupStats ) { + double suiteTime = suiteTimer.getElapsedSeconds(); + CumulativeReporterBase::testGroupEnded( testGroupStats ); + writeGroup( *m_testGroups.back(), suiteTime ); + } + + virtual void testRunEndedCumulative() { + xml.endElement(); + } + + void writeGroup( TestGroupNode const& groupNode, double suiteTime ) { + XmlWriter::ScopedElement e = xml.scopedElement( "testsuite" ); + TestGroupStats const& stats = groupNode.value; + xml.writeAttribute( "name", stats.groupInfo.name ); + xml.writeAttribute( "errors", unexpectedExceptions ); + xml.writeAttribute( "failures", stats.totals.assertions.failed-unexpectedExceptions ); + xml.writeAttribute( "tests", stats.totals.assertions.total() ); + xml.writeAttribute( "hostname", "tbd" ); // !TBD + if( m_config->showDurations() == ShowDurations::Never ) + xml.writeAttribute( "time", "" ); + else + xml.writeAttribute( "time", suiteTime ); + xml.writeAttribute( "timestamp", "tbd" ); // !TBD + + // Write test cases + for( TestGroupNode::ChildNodes::const_iterator + it = groupNode.children.begin(), itEnd = groupNode.children.end(); + it != itEnd; + ++it ) + writeTestCase( **it ); + + xml.scopedElement( "system-out" ).writeText( trim( stdOutForSuite.str() ), false ); + xml.scopedElement( "system-err" ).writeText( trim( stdErrForSuite.str() ), false ); + } + + void writeTestCase( TestCaseNode const& testCaseNode ) { + TestCaseStats const& stats = testCaseNode.value; + + // All test cases have exactly one section - which represents the + // test case itself. That section may have 0-n nested sections + assert( testCaseNode.children.size() == 1 ); + SectionNode const& rootSection = *testCaseNode.children.front(); + + std::string className = stats.testInfo.className; + + if( className.empty() ) { + if( rootSection.childSections.empty() ) + className = "global"; + } + writeSection( className, "", rootSection ); + } + + void writeSection( std::string const& className, + std::string const& rootName, + SectionNode const& sectionNode ) { + std::string name = trim( sectionNode.stats.sectionInfo.name ); + if( !rootName.empty() ) + name = rootName + "/" + name; + + if( !sectionNode.assertions.empty() || + !sectionNode.stdOut.empty() || + !sectionNode.stdErr.empty() ) { + XmlWriter::ScopedElement e = xml.scopedElement( "testcase" ); + if( className.empty() ) { + xml.writeAttribute( "classname", name ); + xml.writeAttribute( "name", "root" ); + } + else { + xml.writeAttribute( "classname", className ); + xml.writeAttribute( "name", name ); + } + xml.writeAttribute( "time", Catch::toString( sectionNode.stats.durationInSeconds ) ); + + writeAssertions( sectionNode ); + + if( !sectionNode.stdOut.empty() ) + xml.scopedElement( "system-out" ).writeText( trim( sectionNode.stdOut ), false ); + if( !sectionNode.stdErr.empty() ) + xml.scopedElement( "system-err" ).writeText( trim( sectionNode.stdErr ), false ); + } + for( SectionNode::ChildSections::const_iterator + it = sectionNode.childSections.begin(), + itEnd = sectionNode.childSections.end(); + it != itEnd; + ++it ) + if( className.empty() ) + writeSection( name, "", **it ); + else + writeSection( className, name, **it ); + } + + void writeAssertions( SectionNode const& sectionNode ) { + for( SectionNode::Assertions::const_iterator + it = sectionNode.assertions.begin(), itEnd = sectionNode.assertions.end(); + it != itEnd; + ++it ) + writeAssertion( *it ); + } + void writeAssertion( AssertionStats const& stats ) { + AssertionResult const& result = stats.assertionResult; + if( !result.isOk() ) { + std::string elementName; + switch( result.getResultType() ) { + case ResultWas::ThrewException: + case ResultWas::FatalErrorCondition: + elementName = "error"; + break; + case ResultWas::ExplicitFailure: + elementName = "failure"; + break; + case ResultWas::ExpressionFailed: + elementName = "failure"; + break; + case ResultWas::DidntThrowException: + elementName = "failure"; + break; + + // We should never see these here: + case ResultWas::Info: + case ResultWas::Warning: + case ResultWas::Ok: + case ResultWas::Unknown: + case ResultWas::FailureBit: + case ResultWas::Exception: + elementName = "internalError"; + break; + } + + XmlWriter::ScopedElement e = xml.scopedElement( elementName ); + + xml.writeAttribute( "message", result.getExpandedExpression() ); + xml.writeAttribute( "type", result.getTestMacroName() ); + + std::ostringstream oss; + if( !result.getMessage().empty() ) + oss << result.getMessage() << "\n"; + for( std::vector::const_iterator + it = stats.infoMessages.begin(), + itEnd = stats.infoMessages.end(); + it != itEnd; + ++it ) + if( it->type == ResultWas::Info ) + oss << it->message << "\n"; + + oss << "at " << result.getSourceInfo(); + xml.writeText( oss.str(), false ); + } + } + + XmlWriter xml; + Timer suiteTimer; + std::ostringstream stdOutForSuite; + std::ostringstream stdErrForSuite; + unsigned int unexpectedExceptions; + }; + + INTERNAL_CATCH_REGISTER_REPORTER( "junit", JunitReporter ) + +} // end namespace Catch + +// #included from: ../reporters/catch_reporter_console.hpp +#define TWOBLUECUBES_CATCH_REPORTER_CONSOLE_HPP_INCLUDED + +namespace Catch { + + struct ConsoleReporter : StreamingReporterBase { + ConsoleReporter( ReporterConfig const& _config ) + : StreamingReporterBase( _config ), + m_headerPrinted( false ) + {} + + virtual ~ConsoleReporter(); + static std::string getDescription() { + return "Reports test results as plain lines of text"; + } + virtual ReporterPreferences getPreferences() const { + ReporterPreferences prefs; + prefs.shouldRedirectStdOut = false; + return prefs; + } + + virtual void noMatchingTestCases( std::string const& spec ) { + stream << "No test cases matched '" << spec << "'" << std::endl; + } + + virtual void assertionStarting( AssertionInfo const& ) { + } + + virtual bool assertionEnded( AssertionStats const& _assertionStats ) { + AssertionResult const& result = _assertionStats.assertionResult; + + bool printInfoMessages = true; + + // Drop out if result was successful and we're not printing those + if( !m_config->includeSuccessfulResults() && result.isOk() ) { + if( result.getResultType() != ResultWas::Warning ) + return false; + printInfoMessages = false; + } + + lazyPrint(); + + AssertionPrinter printer( stream, _assertionStats, printInfoMessages ); + printer.print(); + stream << std::endl; + return true; + } + + virtual void sectionStarting( SectionInfo const& _sectionInfo ) { + m_headerPrinted = false; + StreamingReporterBase::sectionStarting( _sectionInfo ); + } + virtual void sectionEnded( SectionStats const& _sectionStats ) { + if( _sectionStats.missingAssertions ) { + lazyPrint(); + Colour colour( Colour::ResultError ); + if( m_sectionStack.size() > 1 ) + stream << "\nNo assertions in section"; + else + stream << "\nNo assertions in test case"; + stream << " '" << _sectionStats.sectionInfo.name << "'\n" << std::endl; + } + if( m_headerPrinted ) { + if( m_config->showDurations() == ShowDurations::Always ) + stream << "Completed in " << _sectionStats.durationInSeconds << "s" << std::endl; + m_headerPrinted = false; + } + else { + if( m_config->showDurations() == ShowDurations::Always ) + stream << _sectionStats.sectionInfo.name << " completed in " << _sectionStats.durationInSeconds << "s" << std::endl; + } + StreamingReporterBase::sectionEnded( _sectionStats ); + } + + virtual void testCaseEnded( TestCaseStats const& _testCaseStats ) { + StreamingReporterBase::testCaseEnded( _testCaseStats ); + m_headerPrinted = false; + } + virtual void testGroupEnded( TestGroupStats const& _testGroupStats ) { + if( currentGroupInfo.used ) { + printSummaryDivider(); + stream << "Summary for group '" << _testGroupStats.groupInfo.name << "':\n"; + printTotals( _testGroupStats.totals ); + stream << "\n" << std::endl; + } + StreamingReporterBase::testGroupEnded( _testGroupStats ); + } + virtual void testRunEnded( TestRunStats const& _testRunStats ) { + printTotalsDivider( _testRunStats.totals ); + printTotals( _testRunStats.totals ); + stream << std::endl; + StreamingReporterBase::testRunEnded( _testRunStats ); + } + + private: + + class AssertionPrinter { + void operator= ( AssertionPrinter const& ); + public: + AssertionPrinter( std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages ) + : stream( _stream ), + stats( _stats ), + result( _stats.assertionResult ), + colour( Colour::None ), + message( result.getMessage() ), + messages( _stats.infoMessages ), + printInfoMessages( _printInfoMessages ) + { + switch( result.getResultType() ) { + case ResultWas::Ok: + colour = Colour::Success; + passOrFail = "PASSED"; + //if( result.hasMessage() ) + if( _stats.infoMessages.size() == 1 ) + messageLabel = "with message"; + if( _stats.infoMessages.size() > 1 ) + messageLabel = "with messages"; + break; + case ResultWas::ExpressionFailed: + if( result.isOk() ) { + colour = Colour::Success; + passOrFail = "FAILED - but was ok"; + } + else { + colour = Colour::Error; + passOrFail = "FAILED"; + } + if( _stats.infoMessages.size() == 1 ) + messageLabel = "with message"; + if( _stats.infoMessages.size() > 1 ) + messageLabel = "with messages"; + break; + case ResultWas::ThrewException: + colour = Colour::Error; + passOrFail = "FAILED"; + messageLabel = "due to unexpected exception with message"; + break; + case ResultWas::FatalErrorCondition: + colour = Colour::Error; + passOrFail = "FAILED"; + messageLabel = "due to a fatal error condition"; + break; + case ResultWas::DidntThrowException: + colour = Colour::Error; + passOrFail = "FAILED"; + messageLabel = "because no exception was thrown where one was expected"; + break; + case ResultWas::Info: + messageLabel = "info"; + break; + case ResultWas::Warning: + messageLabel = "warning"; + break; + case ResultWas::ExplicitFailure: + passOrFail = "FAILED"; + colour = Colour::Error; + if( _stats.infoMessages.size() == 1 ) + messageLabel = "explicitly with message"; + if( _stats.infoMessages.size() > 1 ) + messageLabel = "explicitly with messages"; + break; + // These cases are here to prevent compiler warnings + case ResultWas::Unknown: + case ResultWas::FailureBit: + case ResultWas::Exception: + passOrFail = "** internal error **"; + colour = Colour::Error; + break; + } + } + + void print() const { + printSourceInfo(); + if( stats.totals.assertions.total() > 0 ) { + if( result.isOk() ) + stream << "\n"; + printResultType(); + printOriginalExpression(); + printReconstructedExpression(); + } + else { + stream << "\n"; + } + printMessage(); + } + + private: + void printResultType() const { + if( !passOrFail.empty() ) { + Colour colourGuard( colour ); + stream << passOrFail << ":\n"; + } + } + void printOriginalExpression() const { + if( result.hasExpression() ) { + Colour colourGuard( Colour::OriginalExpression ); + stream << " "; + stream << result.getExpressionInMacro(); + stream << "\n"; + } + } + void printReconstructedExpression() const { + if( result.hasExpandedExpression() ) { + stream << "with expansion:\n"; + Colour colourGuard( Colour::ReconstructedExpression ); + stream << Text( result.getExpandedExpression(), TextAttributes().setIndent(2) ) << "\n"; + } + } + void printMessage() const { + if( !messageLabel.empty() ) + stream << messageLabel << ":" << "\n"; + for( std::vector::const_iterator it = messages.begin(), itEnd = messages.end(); + it != itEnd; + ++it ) { + // If this assertion is a warning ignore any INFO messages + if( printInfoMessages || it->type != ResultWas::Info ) + stream << Text( it->message, TextAttributes().setIndent(2) ) << "\n"; + } + } + void printSourceInfo() const { + Colour colourGuard( Colour::FileName ); + stream << result.getSourceInfo() << ": "; + } + + std::ostream& stream; + AssertionStats const& stats; + AssertionResult const& result; + Colour::Code colour; + std::string passOrFail; + std::string messageLabel; + std::string message; + std::vector messages; + bool printInfoMessages; + }; + + void lazyPrint() { + + if( !currentTestRunInfo.used ) + lazyPrintRunInfo(); + if( !currentGroupInfo.used ) + lazyPrintGroupInfo(); + + if( !m_headerPrinted ) { + printTestCaseAndSectionHeader(); + m_headerPrinted = true; + } + } + void lazyPrintRunInfo() { + stream << "\n" << getLineOfChars<'~'>() << "\n"; + Colour colour( Colour::SecondaryText ); + stream << currentTestRunInfo->name + << " is a Catch v" << libraryVersion << " host application.\n" + << "Run with -? for options\n\n"; + + if( m_config->rngSeed() != 0 ) + stream << "Randomness seeded to: " << m_config->rngSeed() << "\n\n"; + + currentTestRunInfo.used = true; + } + void lazyPrintGroupInfo() { + if( !currentGroupInfo->name.empty() && currentGroupInfo->groupsCounts > 1 ) { + printClosedHeader( "Group: " + currentGroupInfo->name ); + currentGroupInfo.used = true; + } + } + void printTestCaseAndSectionHeader() { + assert( !m_sectionStack.empty() ); + printOpenHeader( currentTestCaseInfo->name ); + + if( m_sectionStack.size() > 1 ) { + Colour colourGuard( Colour::Headers ); + + std::vector::const_iterator + it = m_sectionStack.begin()+1, // Skip first section (test case) + itEnd = m_sectionStack.end(); + for( ; it != itEnd; ++it ) + printHeaderString( it->name, 2 ); + } + + SourceLineInfo lineInfo = m_sectionStack.front().lineInfo; + + if( !lineInfo.empty() ){ + stream << getLineOfChars<'-'>() << "\n"; + Colour colourGuard( Colour::FileName ); + stream << lineInfo << "\n"; + } + stream << getLineOfChars<'.'>() << "\n" << std::endl; + } + + void printClosedHeader( std::string const& _name ) { + printOpenHeader( _name ); + stream << getLineOfChars<'.'>() << "\n"; + } + void printOpenHeader( std::string const& _name ) { + stream << getLineOfChars<'-'>() << "\n"; + { + Colour colourGuard( Colour::Headers ); + printHeaderString( _name ); + } + } + + // if string has a : in first line will set indent to follow it on + // subsequent lines + void printHeaderString( std::string const& _string, std::size_t indent = 0 ) { + std::size_t i = _string.find( ": " ); + if( i != std::string::npos ) + i+=2; + else + i = 0; + stream << Text( _string, TextAttributes() + .setIndent( indent+i) + .setInitialIndent( indent ) ) << "\n"; + } + + struct SummaryColumn { + + SummaryColumn( std::string const& _label, Colour::Code _colour ) + : label( _label ), + colour( _colour ) + {} + SummaryColumn addRow( std::size_t count ) { + std::ostringstream oss; + oss << count; + std::string row = oss.str(); + for( std::vector::iterator it = rows.begin(); it != rows.end(); ++it ) { + while( it->size() < row.size() ) + *it = " " + *it; + while( it->size() > row.size() ) + row = " " + row; + } + rows.push_back( row ); + return *this; + } + + std::string label; + Colour::Code colour; + std::vector rows; + + }; + + void printTotals( Totals const& totals ) { + if( totals.testCases.total() == 0 ) { + stream << Colour( Colour::Warning ) << "No tests ran\n"; + } + else if( totals.assertions.total() > 0 && totals.assertions.allPassed() ) { + stream << Colour( Colour::ResultSuccess ) << "All tests passed"; + stream << " (" + << pluralise( totals.assertions.passed, "assertion" ) << " in " + << pluralise( totals.testCases.passed, "test case" ) << ")" + << "\n"; + } + else { + + std::vector columns; + columns.push_back( SummaryColumn( "", Colour::None ) + .addRow( totals.testCases.total() ) + .addRow( totals.assertions.total() ) ); + columns.push_back( SummaryColumn( "passed", Colour::Success ) + .addRow( totals.testCases.passed ) + .addRow( totals.assertions.passed ) ); + columns.push_back( SummaryColumn( "failed", Colour::ResultError ) + .addRow( totals.testCases.failed ) + .addRow( totals.assertions.failed ) ); + columns.push_back( SummaryColumn( "failed as expected", Colour::ResultExpectedFailure ) + .addRow( totals.testCases.failedButOk ) + .addRow( totals.assertions.failedButOk ) ); + + printSummaryRow( "test cases", columns, 0 ); + printSummaryRow( "assertions", columns, 1 ); + } + } + void printSummaryRow( std::string const& label, std::vector const& cols, std::size_t row ) { + for( std::vector::const_iterator it = cols.begin(); it != cols.end(); ++it ) { + std::string value = it->rows[row]; + if( it->label.empty() ) { + stream << label << ": "; + if( value != "0" ) + stream << value; + else + stream << Colour( Colour::Warning ) << "- none -"; + } + else if( value != "0" ) { + stream << Colour( Colour::LightGrey ) << " | "; + stream << Colour( it->colour ) + << value << " " << it->label; + } + } + stream << "\n"; + } + + static std::size_t makeRatio( std::size_t number, std::size_t total ) { + std::size_t ratio = total > 0 ? CATCH_CONFIG_CONSOLE_WIDTH * number/ total : 0; + return ( ratio == 0 && number > 0 ) ? 1 : ratio; + } + static std::size_t& findMax( std::size_t& i, std::size_t& j, std::size_t& k ) { + if( i > j && i > k ) + return i; + else if( j > k ) + return j; + else + return k; + } + + void printTotalsDivider( Totals const& totals ) { + if( totals.testCases.total() > 0 ) { + std::size_t failedRatio = makeRatio( totals.testCases.failed, totals.testCases.total() ); + std::size_t failedButOkRatio = makeRatio( totals.testCases.failedButOk, totals.testCases.total() ); + std::size_t passedRatio = makeRatio( totals.testCases.passed, totals.testCases.total() ); + while( failedRatio + failedButOkRatio + passedRatio < CATCH_CONFIG_CONSOLE_WIDTH-1 ) + findMax( failedRatio, failedButOkRatio, passedRatio )++; + while( failedRatio + failedButOkRatio + passedRatio > CATCH_CONFIG_CONSOLE_WIDTH-1 ) + findMax( failedRatio, failedButOkRatio, passedRatio )--; + + stream << Colour( Colour::Error ) << std::string( failedRatio, '=' ); + stream << Colour( Colour::ResultExpectedFailure ) << std::string( failedButOkRatio, '=' ); + if( totals.testCases.allPassed() ) + stream << Colour( Colour::ResultSuccess ) << std::string( passedRatio, '=' ); + else + stream << Colour( Colour::Success ) << std::string( passedRatio, '=' ); + } + else { + stream << Colour( Colour::Warning ) << std::string( CATCH_CONFIG_CONSOLE_WIDTH-1, '=' ); + } + stream << "\n"; + } + void printSummaryDivider() { + stream << getLineOfChars<'-'>() << "\n"; + } + + private: + bool m_headerPrinted; + }; + + INTERNAL_CATCH_REGISTER_REPORTER( "console", ConsoleReporter ) + +} // end namespace Catch + +// #included from: ../reporters/catch_reporter_compact.hpp +#define TWOBLUECUBES_CATCH_REPORTER_COMPACT_HPP_INCLUDED + +namespace Catch { + + struct CompactReporter : StreamingReporterBase { + + CompactReporter( ReporterConfig const& _config ) + : StreamingReporterBase( _config ) + {} + + virtual ~CompactReporter(); + + static std::string getDescription() { + return "Reports test results on a single line, suitable for IDEs"; + } + + virtual ReporterPreferences getPreferences() const { + ReporterPreferences prefs; + prefs.shouldRedirectStdOut = false; + return prefs; + } + + virtual void noMatchingTestCases( std::string const& spec ) { + stream << "No test cases matched '" << spec << "'" << std::endl; + } + + virtual void assertionStarting( AssertionInfo const& ) { + } + + virtual bool assertionEnded( AssertionStats const& _assertionStats ) { + AssertionResult const& result = _assertionStats.assertionResult; + + bool printInfoMessages = true; + + // Drop out if result was successful and we're not printing those + if( !m_config->includeSuccessfulResults() && result.isOk() ) { + if( result.getResultType() != ResultWas::Warning ) + return false; + printInfoMessages = false; + } + + AssertionPrinter printer( stream, _assertionStats, printInfoMessages ); + printer.print(); + + stream << std::endl; + return true; + } + + virtual void testRunEnded( TestRunStats const& _testRunStats ) { + printTotals( _testRunStats.totals ); + stream << "\n" << std::endl; + StreamingReporterBase::testRunEnded( _testRunStats ); + } + + private: + class AssertionPrinter { + void operator= ( AssertionPrinter const& ); + public: + AssertionPrinter( std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages ) + : stream( _stream ) + , stats( _stats ) + , result( _stats.assertionResult ) + , messages( _stats.infoMessages ) + , itMessage( _stats.infoMessages.begin() ) + , printInfoMessages( _printInfoMessages ) + {} + + void print() { + printSourceInfo(); + + itMessage = messages.begin(); + + switch( result.getResultType() ) { + case ResultWas::Ok: + printResultType( Colour::ResultSuccess, passedString() ); + printOriginalExpression(); + printReconstructedExpression(); + if ( ! result.hasExpression() ) + printRemainingMessages( Colour::None ); + else + printRemainingMessages(); + break; + case ResultWas::ExpressionFailed: + if( result.isOk() ) + printResultType( Colour::ResultSuccess, failedString() + std::string( " - but was ok" ) ); + else + printResultType( Colour::Error, failedString() ); + printOriginalExpression(); + printReconstructedExpression(); + printRemainingMessages(); + break; + case ResultWas::ThrewException: + printResultType( Colour::Error, failedString() ); + printIssue( "unexpected exception with message:" ); + printMessage(); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::FatalErrorCondition: + printResultType( Colour::Error, failedString() ); + printIssue( "fatal error condition with message:" ); + printMessage(); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::DidntThrowException: + printResultType( Colour::Error, failedString() ); + printIssue( "expected exception, got none" ); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::Info: + printResultType( Colour::None, "info" ); + printMessage(); + printRemainingMessages(); + break; + case ResultWas::Warning: + printResultType( Colour::None, "warning" ); + printMessage(); + printRemainingMessages(); + break; + case ResultWas::ExplicitFailure: + printResultType( Colour::Error, failedString() ); + printIssue( "explicitly" ); + printRemainingMessages( Colour::None ); + break; + // These cases are here to prevent compiler warnings + case ResultWas::Unknown: + case ResultWas::FailureBit: + case ResultWas::Exception: + printResultType( Colour::Error, "** internal error **" ); + break; + } + } + + private: + // Colour::LightGrey + + static Colour::Code dimColour() { return Colour::FileName; } + +#ifdef CATCH_PLATFORM_MAC + static const char* failedString() { return "FAILED"; } + static const char* passedString() { return "PASSED"; } +#else + static const char* failedString() { return "failed"; } + static const char* passedString() { return "passed"; } +#endif + + void printSourceInfo() const { + Colour colourGuard( Colour::FileName ); + stream << result.getSourceInfo() << ":"; + } + + void printResultType( Colour::Code colour, std::string passOrFail ) const { + if( !passOrFail.empty() ) { + { + Colour colourGuard( colour ); + stream << " " << passOrFail; + } + stream << ":"; + } + } + + void printIssue( std::string issue ) const { + stream << " " << issue; + } + + void printExpressionWas() { + if( result.hasExpression() ) { + stream << ";"; + { + Colour colour( dimColour() ); + stream << " expression was:"; + } + printOriginalExpression(); + } + } + + void printOriginalExpression() const { + if( result.hasExpression() ) { + stream << " " << result.getExpression(); + } + } + + void printReconstructedExpression() const { + if( result.hasExpandedExpression() ) { + { + Colour colour( dimColour() ); + stream << " for: "; + } + stream << result.getExpandedExpression(); + } + } + + void printMessage() { + if ( itMessage != messages.end() ) { + stream << " '" << itMessage->message << "'"; + ++itMessage; + } + } + + void printRemainingMessages( Colour::Code colour = dimColour() ) { + if ( itMessage == messages.end() ) + return; + + // using messages.end() directly yields compilation error: + std::vector::const_iterator itEnd = messages.end(); + const std::size_t N = static_cast( std::distance( itMessage, itEnd ) ); + + { + Colour colourGuard( colour ); + stream << " with " << pluralise( N, "message" ) << ":"; + } + + for(; itMessage != itEnd; ) { + // If this assertion is a warning ignore any INFO messages + if( printInfoMessages || itMessage->type != ResultWas::Info ) { + stream << " '" << itMessage->message << "'"; + if ( ++itMessage != itEnd ) { + Colour colourGuard( dimColour() ); + stream << " and"; + } + } + } + } + + private: + std::ostream& stream; + AssertionStats const& stats; + AssertionResult const& result; + std::vector messages; + std::vector::const_iterator itMessage; + bool printInfoMessages; + }; + + // Colour, message variants: + // - white: No tests ran. + // - red: Failed [both/all] N test cases, failed [both/all] M assertions. + // - white: Passed [both/all] N test cases (no assertions). + // - red: Failed N tests cases, failed M assertions. + // - green: Passed [both/all] N tests cases with M assertions. + + std::string bothOrAll( std::size_t count ) const { + return count == 1 ? "" : count == 2 ? "both " : "all " ; + } + + void printTotals( const Totals& totals ) const { + if( totals.testCases.total() == 0 ) { + stream << "No tests ran."; + } + else if( totals.testCases.failed == totals.testCases.total() ) { + Colour colour( Colour::ResultError ); + const std::string qualify_assertions_failed = + totals.assertions.failed == totals.assertions.total() ? + bothOrAll( totals.assertions.failed ) : ""; + stream << + "Failed " << bothOrAll( totals.testCases.failed ) + << pluralise( totals.testCases.failed, "test case" ) << ", " + "failed " << qualify_assertions_failed << + pluralise( totals.assertions.failed, "assertion" ) << "."; + } + else if( totals.assertions.total() == 0 ) { + stream << + "Passed " << bothOrAll( totals.testCases.total() ) + << pluralise( totals.testCases.total(), "test case" ) + << " (no assertions)."; + } + else if( totals.assertions.failed ) { + Colour colour( Colour::ResultError ); + stream << + "Failed " << pluralise( totals.testCases.failed, "test case" ) << ", " + "failed " << pluralise( totals.assertions.failed, "assertion" ) << "."; + } + else { + Colour colour( Colour::ResultSuccess ); + stream << + "Passed " << bothOrAll( totals.testCases.passed ) + << pluralise( totals.testCases.passed, "test case" ) << + " with " << pluralise( totals.assertions.passed, "assertion" ) << "."; + } + } + }; + + INTERNAL_CATCH_REGISTER_REPORTER( "compact", CompactReporter ) + +} // end namespace Catch + +namespace Catch { + NonCopyable::~NonCopyable() {} + IShared::~IShared() {} + StreamBufBase::~StreamBufBase() CATCH_NOEXCEPT {} + IContext::~IContext() {} + IResultCapture::~IResultCapture() {} + ITestCase::~ITestCase() {} + ITestCaseRegistry::~ITestCaseRegistry() {} + IRegistryHub::~IRegistryHub() {} + IMutableRegistryHub::~IMutableRegistryHub() {} + IExceptionTranslator::~IExceptionTranslator() {} + IExceptionTranslatorRegistry::~IExceptionTranslatorRegistry() {} + IReporter::~IReporter() {} + IReporterFactory::~IReporterFactory() {} + IReporterRegistry::~IReporterRegistry() {} + IStreamingReporter::~IStreamingReporter() {} + AssertionStats::~AssertionStats() {} + SectionStats::~SectionStats() {} + TestCaseStats::~TestCaseStats() {} + TestGroupStats::~TestGroupStats() {} + TestRunStats::~TestRunStats() {} + CumulativeReporterBase::SectionNode::~SectionNode() {} + CumulativeReporterBase::~CumulativeReporterBase() {} + + StreamingReporterBase::~StreamingReporterBase() {} + ConsoleReporter::~ConsoleReporter() {} + CompactReporter::~CompactReporter() {} + IRunner::~IRunner() {} + IMutableContext::~IMutableContext() {} + IConfig::~IConfig() {} + XmlReporter::~XmlReporter() {} + JunitReporter::~JunitReporter() {} + TestRegistry::~TestRegistry() {} + FreeFunctionTestCase::~FreeFunctionTestCase() {} + IGeneratorInfo::~IGeneratorInfo() {} + IGeneratorsForTest::~IGeneratorsForTest() {} + TestSpec::Pattern::~Pattern() {} + TestSpec::NamePattern::~NamePattern() {} + TestSpec::TagPattern::~TagPattern() {} + TestSpec::ExcludedPattern::~ExcludedPattern() {} + + Matchers::Impl::StdString::Equals::~Equals() {} + Matchers::Impl::StdString::Contains::~Contains() {} + Matchers::Impl::StdString::StartsWith::~StartsWith() {} + Matchers::Impl::StdString::EndsWith::~EndsWith() {} + + void Config::dummy() {} +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#endif + +#ifdef CATCH_CONFIG_MAIN +// #included from: internal/catch_default_main.hpp +#define TWOBLUECUBES_CATCH_DEFAULT_MAIN_HPP_INCLUDED + +#ifndef __OBJC__ + +// Standard C/C++ main entry point +int main (int argc, char * const argv[]) { + return Catch::Session().run( argc, argv ); +} + +#else // __OBJC__ + +// Objective-C entry point +int main (int argc, char * const argv[]) { +#if !CATCH_ARC_ENABLED + NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; +#endif + + Catch::registerTestMethods(); + int result = Catch::Session().run( argc, (char* const*)argv ); + +#if !CATCH_ARC_ENABLED + [pool drain]; +#endif + + return result; +} + +#endif // __OBJC__ + +#endif + +#ifdef CLARA_CONFIG_MAIN_NOT_DEFINED +# undef CLARA_CONFIG_MAIN +#endif + +////// + +// If this config identifier is defined then all CATCH macros are prefixed with CATCH_ +#ifdef CATCH_CONFIG_PREFIX_ALL + +#define CATCH_REQUIRE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::Normal, "CATCH_REQUIRE" ) +#define CATCH_REQUIRE_FALSE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, "CATCH_REQUIRE_FALSE" ) + +#define CATCH_REQUIRE_THROWS( expr ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::Normal, "CATCH_REQUIRE_THROWS" ) +#define CATCH_REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, Catch::ResultDisposition::Normal, "CATCH_REQUIRE_THROWS_AS" ) +#define CATCH_REQUIRE_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, Catch::ResultDisposition::Normal, "CATCH_REQUIRE_NOTHROW" ) + +#define CATCH_CHECK( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK" ) +#define CATCH_CHECK_FALSE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, "CATCH_CHECK_FALSE" ) +#define CATCH_CHECKED_IF( expr ) INTERNAL_CATCH_IF( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECKED_IF" ) +#define CATCH_CHECKED_ELSE( expr ) INTERNAL_CATCH_ELSE( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECKED_ELSE" ) +#define CATCH_CHECK_NOFAIL( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, "CATCH_CHECK_NOFAIL" ) + +#define CATCH_CHECK_THROWS( expr ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK_THROWS" ) +#define CATCH_CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK_THROWS_AS" ) +#define CATCH_CHECK_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK_NOTHROW" ) + +#define CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK_THAT" ) +#define CATCH_REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::Normal, "CATCH_REQUIRE_THAT" ) + +#define CATCH_INFO( msg ) INTERNAL_CATCH_INFO( msg, "CATCH_INFO" ) +#define CATCH_WARN( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, "CATCH_WARN", msg ) +#define CATCH_SCOPED_INFO( msg ) INTERNAL_CATCH_INFO( msg, "CATCH_INFO" ) +#define CATCH_CAPTURE( msg ) INTERNAL_CATCH_INFO( #msg " := " << msg, "CATCH_CAPTURE" ) +#define CATCH_SCOPED_CAPTURE( msg ) INTERNAL_CATCH_INFO( #msg " := " << msg, "CATCH_CAPTURE" ) + +#ifdef CATCH_CONFIG_VARIADIC_MACROS + #define CATCH_TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ ) + #define CATCH_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ ) + #define CATCH_METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ ) + #define CATCH_SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ ) + #define CATCH_FAIL( ... ) INTERNAL_CATCH_MSG( Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, "CATCH_FAIL", __VA_ARGS__ ) + #define CATCH_SUCCEED( ... ) INTERNAL_CATCH_MSG( Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, "CATCH_SUCCEED", __VA_ARGS__ ) +#else + #define CATCH_TEST_CASE( name, description ) INTERNAL_CATCH_TESTCASE( name, description ) + #define CATCH_TEST_CASE_METHOD( className, name, description ) INTERNAL_CATCH_TEST_CASE_METHOD( className, name, description ) + #define CATCH_METHOD_AS_TEST_CASE( method, name, description ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, name, description ) + #define CATCH_SECTION( name, description ) INTERNAL_CATCH_SECTION( name, description ) + #define CATCH_FAIL( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, "CATCH_FAIL", msg ) + #define CATCH_SUCCEED( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, "CATCH_SUCCEED", msg ) +#endif +#define CATCH_ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE( "", "" ) + +#define CATCH_REGISTER_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_REPORTER( name, reporterType ) +#define CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) + +#define CATCH_GENERATE( expr) INTERNAL_CATCH_GENERATE( expr ) + +// "BDD-style" convenience wrappers +#ifdef CATCH_CONFIG_VARIADIC_MACROS +#define CATCH_SCENARIO( ... ) CATCH_TEST_CASE( "Scenario: " __VA_ARGS__ ) +#define CATCH_SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " __VA_ARGS__ ) +#else +#define CATCH_SCENARIO( name, tags ) CATCH_TEST_CASE( "Scenario: " name, tags ) +#define CATCH_SCENARIO_METHOD( className, name, tags ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " name, tags ) +#endif +#define CATCH_GIVEN( desc ) CATCH_SECTION( "Given: " desc, "" ) +#define CATCH_WHEN( desc ) CATCH_SECTION( " When: " desc, "" ) +#define CATCH_AND_WHEN( desc ) CATCH_SECTION( " And: " desc, "" ) +#define CATCH_THEN( desc ) CATCH_SECTION( " Then: " desc, "" ) +#define CATCH_AND_THEN( desc ) CATCH_SECTION( " And: " desc, "" ) + +// If CATCH_CONFIG_PREFIX_ALL is not defined then the CATCH_ prefix is not required +#else + +#define REQUIRE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::Normal, "REQUIRE" ) +#define REQUIRE_FALSE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, "REQUIRE_FALSE" ) + +#define REQUIRE_THROWS( expr ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::Normal, "REQUIRE_THROWS" ) +#define REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, Catch::ResultDisposition::Normal, "REQUIRE_THROWS_AS" ) +#define REQUIRE_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, Catch::ResultDisposition::Normal, "REQUIRE_NOTHROW" ) + +#define CHECK( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECK" ) +#define CHECK_FALSE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, "CHECK_FALSE" ) +#define CHECKED_IF( expr ) INTERNAL_CATCH_IF( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECKED_IF" ) +#define CHECKED_ELSE( expr ) INTERNAL_CATCH_ELSE( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECKED_ELSE" ) +#define CHECK_NOFAIL( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, "CHECK_NOFAIL" ) + +#define CHECK_THROWS( expr ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECK_THROWS" ) +#define CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, Catch::ResultDisposition::ContinueOnFailure, "CHECK_THROWS_AS" ) +#define CHECK_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECK_NOTHROW" ) + +#define CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::ContinueOnFailure, "CHECK_THAT" ) +#define REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::Normal, "REQUIRE_THAT" ) + +#define INFO( msg ) INTERNAL_CATCH_INFO( msg, "INFO" ) +#define WARN( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, "WARN", msg ) +#define SCOPED_INFO( msg ) INTERNAL_CATCH_INFO( msg, "INFO" ) +#define CAPTURE( msg ) INTERNAL_CATCH_INFO( #msg " := " << msg, "CAPTURE" ) +#define SCOPED_CAPTURE( msg ) INTERNAL_CATCH_INFO( #msg " := " << msg, "CAPTURE" ) + +#ifdef CATCH_CONFIG_VARIADIC_MACROS + #define TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ ) + #define TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ ) + #define METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ ) + #define SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ ) + #define FAIL( ... ) INTERNAL_CATCH_MSG( Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, "FAIL", __VA_ARGS__ ) + #define SUCCEED( ... ) INTERNAL_CATCH_MSG( Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, "SUCCEED", __VA_ARGS__ ) +#else + #define TEST_CASE( name, description ) INTERNAL_CATCH_TESTCASE( name, description ) + #define TEST_CASE_METHOD( className, name, description ) INTERNAL_CATCH_TEST_CASE_METHOD( className, name, description ) + #define METHOD_AS_TEST_CASE( method, name, description ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, name, description ) + #define SECTION( name, description ) INTERNAL_CATCH_SECTION( name, description ) + #define FAIL( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, "FAIL", msg ) + #define SUCCEED( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, "SUCCEED", msg ) +#endif +#define ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE( "", "" ) + +#define REGISTER_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_REPORTER( name, reporterType ) +#define REGISTER_LEGACY_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) + +#define GENERATE( expr) INTERNAL_CATCH_GENERATE( expr ) + +#endif + +#define CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature ) + +// "BDD-style" convenience wrappers +#ifdef CATCH_CONFIG_VARIADIC_MACROS +#define SCENARIO( ... ) TEST_CASE( "Scenario: " __VA_ARGS__ ) +#define SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " __VA_ARGS__ ) +#else +#define SCENARIO( name, tags ) TEST_CASE( "Scenario: " name, tags ) +#define SCENARIO_METHOD( className, name, tags ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " name, tags ) +#endif +#define GIVEN( desc ) SECTION( " Given: " desc, "" ) +#define WHEN( desc ) SECTION( " When: " desc, "" ) +#define AND_WHEN( desc ) SECTION( "And when: " desc, "" ) +#define THEN( desc ) SECTION( " Then: " desc, "" ) +#define AND_THEN( desc ) SECTION( " And: " desc, "" ) + +using Catch::Detail::Approx; + +// #included from: internal/catch_reenable_warnings.h + +#define TWOBLUECUBES_CATCH_REENABLE_WARNINGS_H_INCLUDED + +#ifdef __clang__ +# ifdef __ICC // icpc defines the __clang__ macro +# pragma warning(pop) +# else +# pragma clang diagnostic pop +# endif +#elif defined __GNUC__ +# pragma GCC diagnostic pop +#endif + +#endif // TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED + diff --git a/tests/conf.cpp b/tests/conf.cpp new file mode 100644 index 0000000..ce575fc --- /dev/null +++ b/tests/conf.cpp @@ -0,0 +1,161 @@ +#include "../src/core/const.h" +#include "../src/core/conf.h" +#include "catch.hpp" + + +using std::string; + + +TEST_CASE("Test Conf class") +{ + Conf conf; + + SECTION("test write") + { + conf.header = "GIADACONFTEST"; + conf.logMode = 1; + conf.soundSystem = 2; + conf.soundDeviceOut = 3; + conf.soundDeviceIn = 4; + conf.channelsOut = 5; + conf.channelsIn = 6; + conf.samplerate = 7; + conf.buffersize = 8; + conf.delayComp = 9; + conf.limitOutput = true; + conf.rsmpQuality = 10; + conf.midiSystem = 11; + conf.midiPortOut = 12; + conf.midiPortIn = 13; + conf.noNoteOff = false; + conf.midiMapPath = "path/to/midi/map"; + conf.lastFileMap = "path/to/last/midi/map"; + conf.midiSync = 14; + conf.midiTCfps = 15.1f; + conf.midiInRewind = 16; + conf.midiInStartStop = 17; + conf.midiInActionRec = 18; + conf.midiInInputRec = 19; + conf.midiInMetronome = 20; + conf.midiInVolumeIn = 21; + conf.midiInVolumeOut = 22; + conf.midiInBeatDouble = 23; + conf.midiInBeatHalf = 24; + conf.recsStopOnChanHalt = true; + conf.chansStopOnSeqHalt = false; + conf.treatRecsAsLoops = true; + conf.resizeRecordings = false; + conf.pluginPath = "path/to/plugins"; + conf.patchPath = "path/to/patches"; + conf.samplePath = "path/to/samples"; + conf.mainWindowX = 0; + conf.mainWindowY = 0; + conf.mainWindowW = 800; + conf.mainWindowH = 600; + conf.browserX = 0; + conf.browserY = 0; + conf.browserW = 800; + conf.browserH = 600; + conf.actionEditorX = 0; + conf.actionEditorY = 0; + conf.actionEditorW = 800; + conf.actionEditorH = 600; + conf.actionEditorZoom = 1; + conf.actionEditorGridVal = 10; + conf.actionEditorGridOn = 1; + conf.sampleEditorX = 0; + conf.sampleEditorY = 0; + conf.sampleEditorW = 800; + conf.sampleEditorH = 600; + conf.sampleEditorGridVal = 4; + conf.sampleEditorGridOn = 0; + conf.pianoRollY = 0; + conf.pianoRollH = 900; + conf.pluginListX = 0; + conf.pluginListY = 50; + conf.configX = 20; + conf.configY = 20; + conf.bpmX = 30; + conf.bpmY = 36; + conf.beatsX = 1; + conf.beatsY = 1; + conf.aboutX = 2; + conf.aboutY = 2; + + REQUIRE(conf.write() == 1); + } + + SECTION("test read") + { + REQUIRE(conf.read() == 1); + REQUIRE(conf.header == "GIADACONFTEST"); + REQUIRE(conf.logMode == 1); + REQUIRE(conf.soundSystem == 2); + REQUIRE(conf.soundDeviceOut == 3); + REQUIRE(conf.soundDeviceIn == 4); + REQUIRE(conf.channelsOut == 5); + REQUIRE(conf.channelsIn == 6); + REQUIRE(conf.samplerate == 44100); // sanitized + REQUIRE(conf.buffersize == 8); + REQUIRE(conf.delayComp == 9); + REQUIRE(conf.limitOutput == true); + REQUIRE(conf.rsmpQuality == 0); // sanitized + REQUIRE(conf.midiSystem == 11); + REQUIRE(conf.midiPortOut == 12); + REQUIRE(conf.midiPortIn == 13); + REQUIRE(conf.noNoteOff == false); + REQUIRE(conf.midiMapPath == "path/to/midi/map"); + REQUIRE(conf.lastFileMap == "path/to/last/midi/map"); + REQUIRE(conf.midiSync == 14); + REQUIRE(conf.midiTCfps == Approx(15.1)); + REQUIRE(conf.midiInRewind == 16); + REQUIRE(conf.midiInStartStop == 17); + REQUIRE(conf.midiInActionRec == 18); + REQUIRE(conf.midiInInputRec == 19); + REQUIRE(conf.midiInMetronome == 20); + REQUIRE(conf.midiInVolumeIn == 21); + REQUIRE(conf.midiInVolumeOut == 22); + REQUIRE(conf.midiInBeatDouble == 23); + REQUIRE(conf.midiInBeatHalf == 24); + REQUIRE(conf.recsStopOnChanHalt == true); + REQUIRE(conf.chansStopOnSeqHalt == false); + REQUIRE(conf.treatRecsAsLoops == true); + REQUIRE(conf.resizeRecordings == false); + REQUIRE(conf.pluginPath == "path/to/plugins"); + REQUIRE(conf.patchPath == "path/to/patches"); + REQUIRE(conf.samplePath == "path/to/samples"); + REQUIRE(conf.mainWindowX == 0); + REQUIRE(conf.mainWindowY == 0); + REQUIRE(conf.mainWindowW == 800); + REQUIRE(conf.mainWindowH == 600); + REQUIRE(conf.browserX == 0); + REQUIRE(conf.browserY == 0); + REQUIRE(conf.browserW == 800); + REQUIRE(conf.browserH == 600); + REQUIRE(conf.actionEditorX == 0); + REQUIRE(conf.actionEditorY == 0); + REQUIRE(conf.actionEditorW == 800); + REQUIRE(conf.actionEditorH == 600); + REQUIRE(conf.actionEditorZoom == 100); // sanitized + REQUIRE(conf.actionEditorGridVal == 10); + REQUIRE(conf.actionEditorGridOn == 1); + REQUIRE(conf.sampleEditorX == 0); + REQUIRE(conf.sampleEditorY == 0); + REQUIRE(conf.sampleEditorW == 800); + REQUIRE(conf.sampleEditorH == 600); + REQUIRE(conf.sampleEditorGridVal == 4); + REQUIRE(conf.sampleEditorGridOn == 0); + REQUIRE(conf.pianoRollY == 0); + REQUIRE(conf.pianoRollH == 900); + REQUIRE(conf.pluginListX == 0); + REQUIRE(conf.pluginListY == 50); + REQUIRE(conf.configX == 20); + REQUIRE(conf.configY == 20); + REQUIRE(conf.bpmX == 30); + REQUIRE(conf.bpmY == 36); + REQUIRE(conf.beatsX == 1); + REQUIRE(conf.beatsY == 1); + REQUIRE(conf.aboutX == 2); + REQUIRE(conf.aboutY == 2); + } +} diff --git a/tests/main.cpp b/tests/main.cpp new file mode 100644 index 0000000..fb54463 --- /dev/null +++ b/tests/main.cpp @@ -0,0 +1,2 @@ +#define CATCH_CONFIG_MAIN +#include "catch.hpp" diff --git a/tests/midiMapConf.cpp b/tests/midiMapConf.cpp new file mode 100644 index 0000000..ebc9645 --- /dev/null +++ b/tests/midiMapConf.cpp @@ -0,0 +1,124 @@ +#include "../src/core/const.h" +#include "../src/core/midiMapConf.h" +#include "catch.hpp" + + +using std::string; + + +TEST_CASE("Test MidiMapConf class") +{ + MidiMapConf midimap; + + SECTION("test default values") + { + midimap.setDefault(); + REQUIRE(midimap.brand == ""); + REQUIRE(midimap.device == ""); + REQUIRE(midimap.muteOn.channel == 0); + REQUIRE(midimap.muteOn.valueStr == ""); + REQUIRE(midimap.muteOn.offset == -1); + REQUIRE(midimap.muteOn.value == 0); + REQUIRE(midimap.muteOff.channel == 0); + REQUIRE(midimap.muteOff.valueStr == ""); + REQUIRE(midimap.muteOff.offset == -1); + REQUIRE(midimap.muteOff.value == 0); + REQUIRE(midimap.soloOn.channel == 0); + REQUIRE(midimap.soloOn.valueStr == ""); + REQUIRE(midimap.soloOn.offset == -1); + REQUIRE(midimap.soloOn.value == 0); + REQUIRE(midimap.soloOff.channel == 0); + REQUIRE(midimap.soloOff.valueStr == ""); + REQUIRE(midimap.soloOff.offset == -1); + REQUIRE(midimap.soloOff.value == 0); + REQUIRE(midimap.waiting.channel == 0); + REQUIRE(midimap.waiting.valueStr == ""); + REQUIRE(midimap.waiting.offset == -1); + REQUIRE(midimap.waiting.value == 0); + REQUIRE(midimap.playing.channel == 0); + REQUIRE(midimap.playing.valueStr == ""); + REQUIRE(midimap.playing.offset == -1); + REQUIRE(midimap.playing.value == 0); + REQUIRE(midimap.stopping.channel == 0); + REQUIRE(midimap.stopping.valueStr == ""); + REQUIRE(midimap.stopping.offset == -1); + REQUIRE(midimap.stopping.value == 0); + REQUIRE(midimap.stopped.channel == 0); + REQUIRE(midimap.stopped.valueStr == ""); + REQUIRE(midimap.stopped.offset == -1); + REQUIRE(midimap.stopped.value == 0); + } + +#ifdef RUN_TESTS_WITH_LOCAL_FILES + + SECTION("test read") + { + midimap.init(); + midimap.setDefault(); + + /* expect more than 2 midifiles */ + + REQUIRE(midimap.maps.size() >= 2); + + /* try with deprecated mode */ + + int res = midimap.read("akai-lpd8.giadamap"); + if (res != MIDIMAP_READ_OK) + res = midimap.readMap_DEPR_("akai-lpd8.giadamap"); + + REQUIRE(res == MIDIMAP_READ_OK); + + REQUIRE(midimap.brand == "AKAI"); + REQUIRE(midimap.device == "LPD8"); + + REQUIRE(midimap.initCommands.size() == 2); + REQUIRE(midimap.initCommands[0].channel == 0); + REQUIRE(midimap.initCommands[0].value == 0xB0000000); + REQUIRE(midimap.initCommands[1].channel == 0); + REQUIRE(midimap.initCommands[1].value == 0xB0002800); + + /* TODO - can't check 'valueStr' until deprecated methods are alive */ + + REQUIRE(midimap.muteOn.channel == 0); + //REQUIRE(midimap.muteOn.valueStr == "90nn3F00"); + REQUIRE(midimap.muteOn.offset == 16); + REQUIRE(midimap.muteOn.value == 0x90003F00); + + REQUIRE(midimap.muteOff.channel == 0); + //REQUIRE(midimap.muteOff.valueStr == "90nn0C00"); + REQUIRE(midimap.muteOff.offset == 16); + REQUIRE(midimap.muteOff.value == 0x90000C00); + + REQUIRE(midimap.soloOn.channel == 0); + //REQUIRE(midimap.soloOn.valueStr == "90nn0F00"); + REQUIRE(midimap.soloOn.offset == 16); + REQUIRE(midimap.soloOn.value == 0x90000F00); + + REQUIRE(midimap.soloOff.channel == 0); + //REQUIRE(midimap.soloOff.valueStr == "90nn0C00"); + REQUIRE(midimap.soloOff.offset == 16); + REQUIRE(midimap.soloOff.value == 0x90000C00); + + REQUIRE(midimap.waiting.channel == 0); + //REQUIRE(midimap.waiting.valueStr == "90nn7f00"); + REQUIRE(midimap.waiting.offset == 16); + REQUIRE(midimap.waiting.value == 0x90007f00); + + REQUIRE(midimap.playing.channel == 0); + //REQUIRE(midimap.playing.valueStr == "90nn7f00"); + REQUIRE(midimap.playing.offset == 16); + REQUIRE(midimap.playing.value == 0x90007f00); + + REQUIRE(midimap.stopping.channel == 0); + //REQUIRE(midimap.stopping.valueStr == "90nn7f00"); + REQUIRE(midimap.stopping.offset == 16); + REQUIRE(midimap.stopping.value == 0x90007f00); + + REQUIRE(midimap.stopped.channel == 0); + //REQUIRE(midimap.stopped.valueStr == "80nn7f00"); + REQUIRE(midimap.stopped.offset == 16); + REQUIRE(midimap.stopped.value == 0x80007f00); + } + +#endif // #ifdef RUN_TESTS_WITH_LOCAL_FILES +} diff --git a/tests/patch.cpp b/tests/patch.cpp new file mode 100644 index 0000000..991ff1f --- /dev/null +++ b/tests/patch.cpp @@ -0,0 +1,232 @@ +#include "../src/core/patch.h" +#include "../src/core/const.h" +#include "catch.hpp" + + +using std::string; +using std::vector; + + +TEST_CASE("Test Patch class") +{ + Patch patch; + string filename = "./test-patch.json"; + + SECTION("test write") + { + Patch::action_t action1; + Patch::action_t action2; + Patch::channel_t channel1; + Patch::channel_t channel2; + Patch::column_t column; +#ifdef WITH_VST + Patch::plugin_t plugin1; + Patch::plugin_t plugin2; + Patch::plugin_t plugin3; +#endif + + action1.type = 0; + action1.frame = 50000; + action1.fValue = 0.3f; + action1.iValue = 1000; + action2.type = 2; + action2.frame = 589; + action2.fValue = 1.0f; + action2.iValue = 130; + channel1.actions.push_back(action1); + channel1.actions.push_back(action2); + +#ifdef WITH_VST + plugin1.path = "/path/to/plugin1"; + plugin1.bypass = false; + plugin1.params.push_back(0.0f); + plugin1.params.push_back(0.1f); + plugin1.params.push_back(0.2f); + channel1.plugins.push_back(plugin1); + + plugin2.path = "/another/path/to/plugin2"; + plugin2.bypass = true; + plugin2.params.push_back(0.6f); + plugin2.params.push_back(0.6f); + plugin2.params.push_back(0.6f); + plugin2.params.push_back(0.0f); + plugin2.params.push_back(1.0f); + plugin2.params.push_back(1.0f); + plugin2.params.push_back(0.333f); + channel1.plugins.push_back(plugin2); +#endif + + channel1.type = CHANNEL_SAMPLE; + channel1.index = 666; + channel1.column = 0; + channel1.mute = 0; + channel1.mute_s = 0; + channel1.solo = 0; + channel1.volume = 1.0f; + channel1.panLeft = 0.5f; + channel1.panRight = 0.5f; + channel1.midiIn = true; + channel1.midiInKeyPress = UINT32_MAX; // check maximum value + channel1.midiInKeyRel = 1; + channel1.midiInKill = 2; + channel1.midiInArm = 11; + channel1.midiInVolume = 3; + channel1.midiInMute = 4; + channel1.midiInSolo = 5; + channel1.midiOutL = true; + channel1.midiOutLplaying = 7; + channel1.midiOutLmute = 8; + channel1.midiOutLsolo = 9; + channel1.samplePath = "/tmp/test.wav"; + channel1.key = 666; + channel1.mode = 0; + channel1.begin = 0; + channel1.end = 0; + channel1.boost = 0; + channel1.recActive = 0; + channel1.pitch = 1.2f; + channel1.midiInReadActions = 0; + channel1.midiInPitch = 0; + channel1.midiOut = 0; + channel1.midiOutChan = 5; + patch.channels.push_back(channel1); + + column.index = 0; + column.width = 500; + patch.columns.push_back(column); + + patch.header = "GPTCH"; + patch.version = "1.0"; + patch.versionMajor = 6; + patch.versionMinor = 6; + patch.versionPatch = 6; + patch.name = "test patch"; + patch.bpm = 100.0f; + patch.bars = 4; + patch.beats = 23; + patch.quantize = 1; + patch.masterVolIn = 1.0f; + patch.masterVolOut = 0.7f; + patch.metronome = 0; + patch.lastTakeId = 0; + patch.samplerate = 44100; + +#ifdef WITH_VST + + patch.masterInPlugins.push_back(plugin1); + patch.masterOutPlugins.push_back(plugin2); + +#endif + + REQUIRE(patch.write(filename) == 1); + } + + SECTION("test read") + { + REQUIRE(patch.read(filename) == PATCH_READ_OK); + REQUIRE(patch.header == "GPTCH"); + REQUIRE(patch.version == "1.0"); + REQUIRE(patch.versionMajor == 6); + REQUIRE(patch.versionMinor == 6); + REQUIRE(patch.versionPatch == 6); + REQUIRE(patch.name == "test patch"); + REQUIRE(patch.bpm == Approx(100.0f)); + REQUIRE(patch.bars == 4); + REQUIRE(patch.beats == 23); + REQUIRE(patch.quantize == 1); + REQUIRE(patch.masterVolIn == Approx(1.0f)); + REQUIRE(patch.masterVolOut == Approx(0.7f)); + REQUIRE(patch.metronome == 0); + REQUIRE(patch.lastTakeId == 0); + REQUIRE(patch.samplerate == 44100); + + Patch::column_t column0 = patch.columns.at(0); + REQUIRE(column0.index == 0); + REQUIRE(column0.width == 500); + + Patch::channel_t channel0 = patch.channels.at(0); + REQUIRE(channel0.type == CHANNEL_SAMPLE); + REQUIRE(channel0.index == 666); + REQUIRE(channel0.column == 0); + REQUIRE(channel0.mute == 0); + REQUIRE(channel0.mute_s == 0); + REQUIRE(channel0.solo == 0); + REQUIRE(channel0.volume == Approx(1.0f)); + REQUIRE(channel0.panLeft == Approx(0.5f)); + REQUIRE(channel0.panRight == Approx(0.5f)); + REQUIRE(channel0.midiIn == true); + REQUIRE(channel0.midiInKeyPress == UINT32_MAX); + REQUIRE(channel0.midiInKeyRel == 1); + REQUIRE(channel0.midiInKill == 2); + REQUIRE(channel0.midiInArm == 11); + REQUIRE(channel0.midiInVolume == 3); + REQUIRE(channel0.midiInMute == 4); + REQUIRE(channel0.midiInSolo == 5); + REQUIRE(channel0.midiOutL == true); + REQUIRE(channel0.midiOutLplaying == 7); + REQUIRE(channel0.midiOutLmute == 8); + REQUIRE(channel0.midiOutLsolo == 9); + REQUIRE(channel0.samplePath == "/tmp/test.wav"); + REQUIRE(channel0.key == 666); + REQUIRE(channel0.mode == 0); + REQUIRE(channel0.begin == 0); + REQUIRE(channel0.end == 0); + REQUIRE(channel0.boost == 0); + REQUIRE(channel0.recActive == 0); + REQUIRE(channel0.pitch == Approx(1.2f)); + REQUIRE(channel0.midiInReadActions == 0); + REQUIRE(channel0.midiInPitch == 0); + REQUIRE(channel0.midiOut == 0); + REQUIRE(channel0.midiOutChan == 5); + + Patch::action_t action0 = channel0.actions.at(0); + REQUIRE(action0.type == 0); + REQUIRE(action0.frame == 50000); + REQUIRE(action0.fValue == Approx(0.3f)); + REQUIRE(action0.iValue == 1000); + + Patch::action_t action1 = channel0.actions.at(1); + REQUIRE(action1.type == 2); + REQUIRE(action1.frame == 589); + REQUIRE(action1.fValue == Approx(1.0f)); + REQUIRE(action1.iValue == 130); + +#ifdef WITH_VST + Patch::plugin_t plugin0 = channel0.plugins.at(0); + REQUIRE(plugin0.path == "/path/to/plugin1"); + REQUIRE(plugin0.bypass == false); + REQUIRE(plugin0.params.at(0) == Approx(0.0f)); + REQUIRE(plugin0.params.at(1) == Approx(0.1f)); + REQUIRE(plugin0.params.at(2) == Approx(0.2f)); + + Patch::plugin_t plugin1 = channel0.plugins.at(1); + REQUIRE(plugin1.path == "/another/path/to/plugin2"); + REQUIRE(plugin1.bypass == true); + REQUIRE(plugin1.params.at(0) == Approx(0.6f)); + REQUIRE(plugin1.params.at(1) == Approx(0.6f)); + REQUIRE(plugin1.params.at(2) == Approx(0.6f)); + REQUIRE(plugin1.params.at(3) == Approx(0.0f)); + REQUIRE(plugin1.params.at(4) == Approx(1.0f)); + REQUIRE(plugin1.params.at(5) == Approx(1.0f)); + REQUIRE(plugin1.params.at(6) == Approx(0.333f)); + + Patch::plugin_t masterPlugin0 = patch.masterInPlugins.at(0); + REQUIRE(masterPlugin0.path == "/path/to/plugin1"); + REQUIRE(masterPlugin0.bypass == false); + REQUIRE(masterPlugin0.params.at(0) == Approx(0.0f)); + REQUIRE(masterPlugin0.params.at(1) == Approx(0.1f)); + REQUIRE(masterPlugin0.params.at(2) == Approx(0.2f)); + + Patch::plugin_t masterPlugin1 = patch.masterOutPlugins.at(0); + REQUIRE(masterPlugin1.path == "/another/path/to/plugin2"); + REQUIRE(masterPlugin1.bypass == true); + REQUIRE(masterPlugin1.params.at(0) == Approx(0.6f)); + REQUIRE(masterPlugin1.params.at(1) == Approx(0.6f)); + REQUIRE(masterPlugin1.params.at(2) == Approx(0.6f)); + REQUIRE(masterPlugin1.params.at(3) == Approx(0.0f)); + REQUIRE(masterPlugin1.params.at(4) == Approx(1.0f)); + REQUIRE(masterPlugin1.params.at(5) == Approx(1.0f)); + REQUIRE(masterPlugin1.params.at(6) == Approx(0.333f)); +#endif + } +} diff --git a/tests/pluginHost.cpp b/tests/pluginHost.cpp new file mode 100644 index 0000000..3ae679c --- /dev/null +++ b/tests/pluginHost.cpp @@ -0,0 +1,34 @@ +#ifdef WITH_VST +#ifdef RUN_TESTS_WITH_LOCAL_FILES + +// temporarily disabled due to entangled deps (WIP) +#if 0 + +#include "../src/core/pluginHost.h" +#include "catch.hpp" + + +TEST_CASE("Test PluginHost class") +{ + PluginHost ph; + pthread_mutex_t mutex; + pthread_mutex_init(&mutex, NULL); + + SECTION("test read & write") + { + REQUIRE(ph.countPlugins(PluginHost::MASTER_IN) == 0); + REQUIRE(ph.scanDir(".") > 0); + REQUIRE(ph.saveList("test-plugin-list.xml") == 1); + REQUIRE(ph.loadList("test-plugin-list.xml") == 1); + REQUIRE(ph.addPlugin(0, PluginHost::MASTER_IN, &mutex) != NULL); + REQUIRE(ph.countPlugins(PluginHost::MASTER_IN) == 1); + + ph.freeStack(PluginHost::MASTER_IN, &mutex); + REQUIRE(ph.countPlugins(PluginHost::MASTER_IN) == 0); + } +} + +#endif + +#endif +#endif diff --git a/tests/resources/test.wav b/tests/resources/test.wav new file mode 100644 index 0000000000000000000000000000000000000000..1e52d36421348915a78897bc55c8aadffaf0b30f GIT binary patch literal 81316 zcmWh#Wq=gN5^kB9-Mhmf5Zv9}-66QUy9a`6aCdiicXxNUpuvNl?9NPgSG{`N9}aeB zN4l%(vu#+jTD28rIcrw2Y1OWM24zUZ7~}L)Wd~zZnsX*tBG$RXpbkUnHEA$Aj>Ya! z9gAUNHXJkHaLk5)o@s0-kN5nx9M!+nZ4$c$!BJmmqzQ{ zrnG5c((62?k@?LeFjGxa^E|}lr(>vD>{`~Ovin%YncnWwJBiQ!D*et7(K{b)Aw}}Gt)$w@#c`eXV#n3 zrn)(;N0^0XJH5YwDMqj9X^NOEW;wm5npr@fTh^q7tY(g$qpO>*^l2qcX+2Jj(gW!{ z_v%=EM|UwJ%xII|BsTZ8(Jf3KE%j;ruYRT5sb?yQ$*6DZY5J^cr88=+6YKM8qf$CR z=Uz-N)?s5M^KBbH5W2%*zYo?gOI;8ID$NGt0LjO|R{HfdP7rK}^ zLib??UH>y3()sCn@8~RMx5)v^Om_WJcQx7Q?60e=a9AhQ&r~|PM{#tVx~8{2qZ_N! z`hdQne%E((cRfmfR`XS174jqXN;Onp_B;Ap)I^nBH&boZeBDZyQeFHOs;L_4-_*aE z;<}ap-hZf9()+&qcXdKcuNx@eAFH#OfAx4UW;R`c1z{L!Qkl`v5oh5fGgjxv9GH^U z$ysOv(Pk%!Z;{JpIFi91+DSFus^#8NY5pPWfpm$ntp9E^F8c zG~WuIp8&G)ntVKLH`8If$p_zYJ!FBC`Xn15`e80zADW7aq7IgXvpAQB*(f~5f8Z)~ z_qGrKJiq3WY3KY-MP)O^TK>5_JRsHMhxS% z^+>gVG3zy=S__%+4D(D&sDuTf2OPk9SX;+aBe5dif=$$3zdi=pKI7}tW+z6m2+R-3 z%|!FctmD&pPt#2wgFE~a%dBtc9qg<)g9(j>LogENvP=9n+AtU9@Rp(wyik2uAL|;A z51hTrfq z|Ci;#PUe;#&m>!iqo~Sl7iC38{YY&#Z^d1{2kV<&W)d7Wi(wxw)Q3z;y3YgkXxCP^ zu)jD5mDMg?9GV$Ld%BA`U|P}h3K-X`<(-1Jd^$AN-&GmBhW+&^)f-cZ`urJIHyQkM z`U(R7%zo&W<|rm*jr9WmGo<6|#WZ-R{!vZ!TvMN~zzuNIG-rP!Gi{WopU6zsPRL{~ znop1u*Rad1ERW=CSUz5XHCAcNO)G5x#7#J4{^WOf5q2Cui_TV6SyBwfC1y9g;%l6j z)<1Aj=V#AFa{OxAvCWpY`rt7QIyrPRn0V){j9n8r+3?HiZ9q)SPhkUbxaPs%uZca=hb64vzKsmuZQEq zEPmW9(QDxjyMrH8FTa{rIx9Yrb7W>+)bFcviG#e1Sz;<%`K_^#m;S#Ookt(BSM+0^ zF0Z>YTRf*86%xDcLVUkB-!IMgS$X9aT61HuBQ{|h7*LPPVqI~xNbAq`-}2Pf|Na%muBRWQf=j(E`NWff-o*aT~moTsbm=D3enl|T74-Ut?YcU3YL zPd31zrZ11-jbXUE-(}*U6mklyVG_~ah&3D7LzW4r!40{PPxgLFXl1EH6xgXUp&jL&ptZ;vKQWDos533@4Jwd z4KxfA&`ubr`h*I&|5(w^5!2E=t@q$Zwawd(>8;mdF+9`P^eo-X)E2S9iq2ZMs5{U~ z?IbiaRSpZC^j1QQuYY4{tatpEykyN2u3pa@*dNVkkI83|51i(vxqhnz4zmi$@^Yyx z!?v1~EDo2bUgBEh8+(ar?Y)OBqNVIDtME3gkXVdc;3hY?PsfTkQ9VTbxS#5C;Hni+ zr&L}mCQw%{^w0SB*g=*YYuUqszjU?O)h18G59r|8B3G~tU+AugvyA7QwjcXf%nC73 zo%2o#Zl6~X{vqqNW%S?f4O3Ef77K8wZi<`iK4!Cb4q|jumD=ePT;tXJHO6$X%J6;4 zRR!5f-d}GCr&Co#J*=cAvk`1uY=`hAds^_dJ3bT*+vR<+-CGv&c+EgBSnS3LPdxW3 z!y+dIui)-AwIU~5J9J?$AvF{*RjTtYXiGk zMYRsASXJrW{Y)w=f!GJl1B>m|;SOd@RE>xRdb7$aVyq^z22_UgETxrCP`|ZS!}o9u zCWzc%ckw>y4^{&EH(lAg%J&BsiB|qXbZDG6r8D~mqx5S)!2P6{j_)fD0R`N~kJ2u6i{FOf7RpRCC zXx7(v)hSWT+KSD+8@icO9K&8!v}Iaz$^V@Vl>OnGpA$ihz-P4YYFI^VY2tZ1*(-5F z2UJ=)NnC>QJP&{3f7Ctj5i2Ff^K@#ee_jTxoc>QeQC8zo`UEhMLO<32;Br=jC5Ahu zIBROfnq8r9;=C0Bv&}Z~oVCz@!5((Stl`<%Bi4|8#v;(hjJ93{rqKLWR5WtNVM5-F z*Tm`KU&&cCR1h)L6AxO$#2NL@)Nqp9*UT`OD$9$>RCAuw#PUkyvim^^u}Ow-JyWt9 z8?JY99=Voh!u8v65;U&SJBggPzWvKVg)v|*pkM`&+S=@}*^eqtwZA#O7zy_I?a-)XfplOa1V zt}8)a_S%%f9=xf1tVTn8oZzR!zSb_*(d;v4#X?hxJ!ZT8kH)b|T1#|BlS}M1Zy=Ag zLL^~3X?FNY9r7*T$=-|8b`ieRRK)7mF&59BVVz(N%}$X}PN4HGWQRpjE^!jv#1A4u z0CeDwMR}Nm(fqg!>YOHZ7n1}lPXs54C>;?Qp z?6B3DFJs^ED0_fKOk0>{-Df*67BBK__%93<+2nlQ2#=`iJc}I5Z$my4g-t~h`HEK~ zq>`Je*&LC8HR0{VM{$i8R}Ixxkuh*fTJk8n>;DOJ?X1oOIgWj!dF49WWb*5)cG1Z3 zR#9^w2BXjZVa=@k>bu`>1^0YC<*_)gs6vzhi0h3ubz2+<`2am5H74ck7CflsaxcecNvWe>gt#ReM?i zFTI`GcFxOX>K{Hp#89m*tM;*qk((o3^DzAXD{qRM#lk=fJ30KvG&`uD;|05l{g+i; zlrR*>`_nP|{HnL^-+0J8q$8LH_IF*0Q$q`uwbHEHcH8crjR5$oTnN9vD zDwwEnHgy`yiG;kPE}*OM7#X7*x~riTe`AWUBz8Vl(fz3#@)OX*1o;80ytCR=i`^V9 zAFzU}c{QBIE9jD58Pm(&FY{B+Yb`S3b3d7P*(w)rmUSb1TlKZN$XHX39mGm334bax z@svjT!~Kb@hrO5;G3j}0Jm$A`TfsB2%epCNz=Y7a@Ee`gS!4zH3PO)d*;}>DT_~Cc zdx|MuMw(^It5~=tnmT1fI@J=U$!&H`zp7WzY3w-cH!Q_V${c1NG~?y1@qt@>CJyFR z%@c1js~`VBL_vSOciRlW{9+(qAgWs5c{bH&X&4tXStg{_wX^B})j$;RoYzkv*~Rh;M$S*ex(Dut{W+E&K^_|I)MT z?oU^L@s+X+jONATCdb}oNv&RTr#a)*(y1}K*u_@+_x!f9wN=eD(%>g{8_U^F8m0A6 zr<>@m>U#h9@5FiMD0|~gH&OKNwrZ)#9q*nf67Ft|<9Xd^OdSy1cRphL*huL{?W76q zCd($PS=)GbHBw(Oo%rRzQTdW5WEsNqMMUI6o&hh(-7+a|2^O+yh1T)~5!oYa1W%|^ z{tiT8oE%Oa(M7F;`SwLo0fx{HP8+o#vXg%}6a}5_djY8?x=HN#c5e2_?C0I#x>vyY zH*zz3@2Anlom%WK&l0b!DS;$`MzN=2yG0BM_J_leM{u|&9?KD-milMFw-!4^+2xox zZ>$^=(NiXOYp99#N@uy8595DT(U+2(j!YH~d(Bu#L`5uS^JDTjB4HO6$5@ib@yC{;QuXDrcBPv_H?Y#e9)^2&4QavCp|^HZ{S^Di8*Uztjr1iu@8> zz}JKudC3ULpSN5VZK740Z~|w6xF2^&clL(KM+q*N!(VrM^{x4$9gp#2etPnGykllYBg+wgmAh}!0ctT`4< zU2*xbP>=^!z~@kcP!0Q*oazNcZp`Vuw_8UH@v{8tVwyyyvf7%3;SOF#{#Nhvva=ud zDQu@Q>Wkhm_lkEF3kLIBcik)gKK(lGsQF+ev-`;Uap6pq?d9hhy z`uUgOyM0ew2v_5+g7dM4moa+3S{aqdn!{4E30?t{+iIiw`rRU3tBV>YHk(o5UVM~O zg0~lm`D(wWm=K8aQ^kB?+Y=Uuyr4*M}M9j8!M%3W;8DM5i@NR3_ocN&25ym(_HZnpA{|R-%H`sOOypxa3c9XgN19MXR2CL#y&|0)$p(ug< zoViX-S=6cy6XGK9xHBgHYUig}s1#P^ZFFnKBF91tJ0xeQc} zf6EX1r}^;Ua;)qQ;!&c|&w5_{s04T;oR^>B6HP?8a%iF#i2Ul*{&~dvWi9j9N6!gI zI9p^MmB{2Vlj1Ukp2K(1EwG-2V|tn8a=B|_Zp!$9?a*4L(MjUQo5z?inAHByJS2u? zuOFtnl$4!{%p6f99A6K#es@Q^3xac;l%g5dQf-~v>}{wK6pZ*4Ib0rhpXqVI0W8ta zN>-!Z~^Tq4wmU+|Z^ z>9M|Zh$r=I-{zZTLa%M;3@_ufmaE-Qzmi~uz-|A!-!3?T$2Z9cku*`~RWqL0N*$3N z&PTU4K0n6Z(=Kf(>hL3KEA_~H7{r4?hY(LKucs$vR#D#_?4^ntF4x9%R0)Zp>ux_+ zX;@C?hn|H8Q?Je&4wzNe(!gZ=`7@nY$(qkc!ws=rZH3f)K-@XrMy!k77f6D#FPRV-#-{PhX%hHpTR$Wr#d{?Kr5@n@i(liPF^*@C(J65%AQpWQIt1kbpu z5)Kv4_toq(b*&Uq%4%jbW@b0sZPxCn`F;zQk@n33e@g69J8i-~UMRd#td@@6;eJ!& zcpj&R7!0H22a_Z;0FF3uR(_bH`viM9H^04%Js%ioowpB(!)l`6$NSS;&tl?*{Q5EL z18-!t9~)y@%3JXgi&vq?>MPFYwar*JnVe;}#%kscH?oU+CcMpQZ!h3i;0UV`sHo0_ zTHy{^I$nD_)>Y<-$%R{az2M`B$L@6AB^VX1=XWAZw=`mtDWdK}L0&+B>SY&{wY)v7 zHKEI{ENd*s(}D3Y*Ia{!xXphPP6-qFBasHGs21{pS`br^7qmJC!zNF3YItKdg&NC@ z@)XX~+2TfMU)~bE#0kh@Zx>mkJ40MxFJah%rna@it`)wiis3QHfUfUiMQb+eqArA@M0}GV z=18DaWO;Q*PxTswud)KpO1_GCy^G#$l~G({1(=63?csqe-k!L}fuq)2GuQ5=Q^8Z~ z0n}1Ut!nJ5H=K92zv>F25&NhkbuM+pZWPtbdmn0xwe&rhL>$t1!mDH4zJ4R?i2m#q zu=56vVnTO!>{S~gm+0SsvF!e1Li2r0QhdsidrL$-rwXwNyX8ujO;7X>vut*9e#~$) zOmEdiVHU0S0$9f19rrhHE7F)3q8Y2}cJ@yDm+>@%7CJd`xO+w)vdZzt zm@ZIIYygW*3zuPAg0a|ET(>r=w>klTCj{}0MND?yJ9wW*`cn+(|Kw1s5`M55)v?4f ziGR$iY_BDb?Gfg3)5dmS+)l#|I=4jb*!8}{j+;rCLG@tyP&1lb(1B*=6q&EYAZ|ZP_M1I z89FAj+s$y7DC6I7e^c48ja3?tu#CdjSJeSsK&JMWg;xhx+c&)!`3Qc5&O#~JBsSWM zxpr%4+jfX;oaTHFOtjwn&tsKY4$1s+*6(&zR$4EME2H+v;&?XhAbbyg!)LMRH-{%s z-1sUY&=1GDWjKZw>ErxK=qT?K*%&WI-}H*%SHHVe%$W`ySOWLYa3}E%i|}NkEX}yH za9X&X>1uy4m%$aMR90^Qzs0)X0#VPe=|2&Pp&Y#i!|NbXZt%{; z4282|ER<4G|EAU8mn&hqs$HlhhIf(npW-_Vk;Rc~8%HaiN4!J*e zoZFena3S?grpKo+#BbyMFngh6Xr`*_%+q&cCAQ&%%pNs5=C#>pbyc^`N|DlyjqT$7 z0b9icp4G1ei%ps6UU3cV?!ilhzE@n2wa(}gW_3j3;L=d$ z@E93S6yas{I{%g&1DVurF^etm7L)3@TP(LXt1P;#)dFh76@jV2lXiNU3oiK^VIowN z4fRkYmFB#_(}0mRe~PQ3Y?;r5I2!e)X}F* z88OAkxcaJQyj}JR*YYdthw3rAt_GMV!CUP1uN>Y?v5oY~t$c@7Nezs9rYbs{n9U)Bk#hgxM8XuUBg<^@NzpxMT_V{ zp|Px^USbjlvs=f!I^mUZEwOi?GW3RV;%_}#e{lQhC(d^8!K@I3ZGI0K(g%2GL*gWWYR)kaUq8crdY2hXi7W|cRG z4dE?$1DvkXS(&Y{x^IS9V~K?y$ot7MRu{MvdXMw1cWQ@eW7l)GU_7^O+(>?sSn(9t zk@)C^E_zwmWoHDR=Pko#BFgi;r`CI!nYS~iU57ojtD9@=o7knssMG8;za_sx;&4ap z^6|PktPJ$#H{4X=to{K)Vkw}BH65qLoCr4oZKhd!SRyst98fo-Kf|QRI4mAchTC-# zebu|<2AqSE#pQI@vLCp|4~A>1L9(0492YWYoE?53HU+6x_gOnU>5bs8OtEk;_1Fv+ zxkY|bwy&6LZtZZa?Cdo5_Yj-)!s|jDz*Q5&cfk|&7aPr1>0H{w>!J#*)`ziOpg5}% z7dA6x33iYV=aDQoy!D%_KGu-nYvMr12-JUvhN}nG(8zgWa;U4H1IGKYtgRSBYSWzX z(zyEkvsF{oP|vIhFw)zvwh}5TZ7kQ2w_uC8EBYGn`=RjDxR1oCz;W2(AJY56NBkr- zspWGbWu|Z?uau|Uw=B2pVYf2h!*$%C`3bqqFOh@aQ&mX+9j+3f()r+{eGq@SZ}kYw zL|j`-y^>UpY?v-|z$|0a+)libot<~b?cpL`5uOW*>h0op>Pk`WeYX_fA^YGkQ%YnM zMh(}Ua66mGeQz#qw6Cd`{&70u6rE1}#MJVX9IAJPhMDO)3AD93i=*tg&J{Z9=jW|3 zJAZ6{WT)bOi+!%wSjXio*n=x!M_d{=hxGU(?qNmD!Wa18-Qhf5u#{*(euDbMhwotb zbx%CV6Z!e{WRZlQG7r4&DuG;0`r~8SP-<^|s4n}$ON#%*f2Ij3wr9hR>1$cmK9P?0 zK{@l;w~UdaowRV>6qUc=so&W&fLF4M^h^bjpQZMDtNA$4>Mjqk7uX78b>X-eeayKa z>WBz@?9Mdz{a)TWdv-)Rw!`nHtLOoKI@6CYuosFG?f`uff2y;XMBHH?txe{ZcM~`8 zy}Gh)1A^wQ%1l6fnhsXP6~`FsKaqz}Qw5dYujvkh&VhyYa`>!X_{+V!-YxdlsU7SM z6T_L?c8@V$Cw@3oSVSD+gEoi~Nf zq+YF}s&R#U5cfIpGvJhE+yy|a=q&_@@*5h1NSI>1v2rjOR2j+FmAhQe$ zo8Ndvd5-s|p7@Or_I~l2^q!Ygz4m&E*d}q%sVKU*=SW|#Nz>s?meWZtM*4@%B|v%9 zbTv;1O(ch>{E_*WX1d3wAg{>U$#UXfJ=fy02l;Pui}B7SR$TYvU35~cAv*w)mj6ZH z$7ofa{Vqo0TQMGrzzA$(itt19EH&?^F0oiv-!u_zXx*14jCW0}W%KzRdB-cxo(I2+ zQN-|$hBtf^YiE_Wx{0YyXLUopv+9bUGNBx7HRtog!>Jaew5Yq8mN?hW%WmT+zE|D~ z?A4uEqE*_&fcoyb>nnAN5Z^5npDG5swk$Mp(;<0Y7T> z#IxQ79YL;*&oCUWf+P*k(o1!BI~^P*-8(T>;5pP}bHq+eUYadff<2ZOSvk^!9Ly~L zz+d93)y9lt9r+X5m(wv;*0ENRV`q?_!NOKkwpb|EnLHw&VYZwn-|DksB|l^u<3sb0 z7;Z)2FzBHQ;~z3N`%QRw&9sL^P~Iv=8tM`1;3xGmSfJOkQC3O9M^|+PyhuKwb#`vB zy(-i>rjdsV;SFY06(JdWratli*jDw*oM5w{fle&GiNPu-xj!1Jmza{TL@E24`1-5q zh@E6<{BFMTc%2<1-TdQKVN0AzJXW2B!jOwpoV5H6KPT3+Iq=lW5A&^#qOg2I=qEk(?Q7x+ zzs7n<#+Kqv_J*p*ZhlWzWsC43aZ^GUl;6Z<2YJ(XoPO~TcsEAWTJsT2O_gFWU!C9`kmsF-@J9T!^&n_vq<2)=6 z8FWbWwtwUM#2m4n>gy$T6sqVNPN}FBgzgK%SrYO8Us0 zb_o^VD<(#X06m*ab{*CV^HcB6i@D?rvD~~S?aNk4U^8Ee9-ICdeKIG#3UDU zFcEtwYsh1M$WOz+=n%{a#^dqS5ZsUF{DP*8J;8|-d+?!7>-VCo=_UsA$ud26y-E6r zYzDpAQ2Vi{3L7-jpRl!@%{!R#VuVu;a;d9uhF_Pt_&#}skI;8$WgLd{q@0&Qnj831 zwJAJ@XS5HnVwe{rNaY!*3$xLL5z_FPY@69+Hu)<|ET6-Z(e5ZNE9lxPF(lAw@H^kg zR^toZ#QVpK4{P~hs0fwCcGE9zw_Anyi=`}vTpAJjq2A~h(M6oT_DfYz$HG#5#=V9c z>_%d~sbadB3fPAnkkzrHn`=qidXErKTvG4 zW7JHqzbXe^_&RuGQt?m1(v?ErbZt=`H{(u`jd+cxekoo9l{w4mvUYS{6TGv2BRS3*LWm#4=iRx&5b?9w~+cIiv1{9i8lnmC%BKCkR`B)9BJ>yYOtPNFjduU*k!p^6#O(> zSb7##oy;-ji6pWsOj8fBgGu1MWjmaM%r&oMV>ZVnRBLgoq1A=wq!kyzrjchYeQz*DMEe&+yoj-~6!|@x^OL3z zF;Vp_6>*ZKB(^)kd2LpD#qD0fSD3~ufR|JlJ8hI=nuQpRdVkB20N@f!K|!BXq?ej0{x#_6i67n0X6i0jy@&|M((v%`<veD$?XEhao9L^)@NP~F7tjZa5pJ3!i?B-2!Tur= zsrue5mP+I|Hc#NBrfLzZCH#OR(^^oDzb>rE`Ex@(p7HmM(xSp74w-lM!Hqv@Cuvx4=+rlPV75HAWPi!Ri zAg{X3dt2FHDDkrAsoS5X9$ZSy<4vg!J;qir*z2m_imak1{LUhIjJMqTPF}^lEUh>~ z-tRH6jVj?0V0L^~OaDjw%`11BH=6t=2QVNWvV^8FtkBV_g>y164$nbb8Hqdn5KIaT za5{<2Yz=P>6-@{W*wq4?^b50|6q8--6tSM=#A-9oUCNpUr-=mq2~`ZP>E#%ZXXSq3o0s7)+7?gA zu@dc+3iQ|Gy;Y{HISX+zr=5`vr@g?;T2qI(i$m>eL*Kj$i%vo?hyvA{?BDqCMhM$Ec`Gppm3wX*N z!g7R)y7yRr!f7i+ZJvO=H^v)aTvh zWP6_YDOzyaLAr_j!}`K9*dOh~I>sA_BSi*zMC1}Xtd%mUS?-^~+xFiw#Io9($uU>N zFM?s2PBtb_&T?K)%<%tm%gB_$?(8jFD*N+a;S%m_n!Yp3_H3QmX!4u3eiuK#7-6So zC-pDvqjI^~%p3k%TB0cYtETvkOBMC zrnDnQgU{v?4xAf)jc)_9MRO>{=h7P96&_`#*)8pDBC~l1H*hL>b^qcSBCg6&x&=9K z&-shZF*(6ufe`rWf$2jk{-4;vIS?#Q&!aG_36;%C{)GG~YuF&%r8=5}EC>G1r`dlJ znptfILM5HRw3L~hI_#@DY8pa0bxt!YO1|dT;h^da)nUDV6dDI|+x^vkHHqB7=(nJm zvZfWE*HESX3uYZG_AjCn`BPl-w&~(5i|(wh@Cc_isnhZO;;IyRva+x!`P44Uhj@F; zF+PuUogz*adjtHXnnOmmkxd|{S_`q76#oF^BhLsq9eE|I8&&Op$Zb9nX234&%a-s$ zY?dCX+Q9En$y}Bd>??kZ`@v-85A_UPQV!>tDO=FOA7z?gC%6u?`9=0uctbb^&rNz_ zN*Ks;v3mZs&_I6xx!-f^oA8+bp|XZr(FB;CyShA^N3&BQcQ*N;>T+_eLpl2Cpig+! z&3vlG@hN+7g|*X1yngsqQq96fVj_=+7yL!Kwd^Igu+id*wb4BH)roS5sG&E$n5Y zJxec+@Y6cJKEqa9j%>#!^EdLGe&H=)Kdm7G_&FYnNUX|Kn8Iht0yL98;!n}j$)KJb zOpMZY_Q4v@4*K)V2ELm7_kZ*Dd=R0D2=8y!-OkT0z-eNj+biF@j>APC{(#-Xzc4e7 zHE~+=#LgGUsmsZ`vb0{V#>ijf8`=f0Sv5X@{f?Yn7kfk+;uj*VOjagJygaf-$|LXw z4)JO{5hmdoMKzgEe21khgPe|u%qQ92S<7y!mc%xdh6wUeH4|aNDyQHeB^oBeDK^kq zM#}RZRh9VP)`SV~S(#;h9i_j32gxYWu#;=~8)otbswlAqdg;3SiuH`&AvR|g)qH`! zm=V8=)Nsbjp;L!##(97$fy_$;4@q13|%$O;+p3;9BSl6&+$O=BA% zJ=kcmcr=-BV`u_uD8yk8^AS&oW4PP<<&UGP&;BZaZuR+kkgteHNEI~YtE(MetOzFQ&gf z?LWoqG6PRyK5NFRo7Z|PYfB!xyM7AY*AncPmj~)wW63oYG~2~aI%X2{iFb8;{)ZWZ zzwuJ+y?V+jMO2bIRW5Unk~`%gnjQN8nr85xH6-L1RO=~;)>^-X3}Tf8D4>$) z$&_B}#%72uJRaex7k;$2jQV!QTUf>sXplpvaXCajCI9deoDOo zyUGYT2{%%jFQogMm1Yr?6UBK`z0AL4dck?qgk|P8O;I|dB4)U`s3${1r~{i!26NMo zPYJYJ)IGy`sQ;bboyz?cE`|?q89DN6sd0Wgx4rw1UP&pkxqcQ5;|G+fj8n^2@rj-~e2jgqH8)b06zC_Nyf5Z+xo_6m}NWeOX z$(C)e715?W64u8qT-)3pi?dlxD>W0rBok%$gfJunrqrpYHus0?UF$&bx-+xDmHfW z`1zm{PSOk2X57dc=mP#>J(InH^puDj!b{-^@1mcF7li}rKeGo1m>(*o%1CRY8(hW5 z<_4kjvVJeWjwuh_;RG9i1O0TAojapy>b^X^+(z0#Vw0ad4IiN%t3&r@x$dPK;v;c| zT((2vJy7R{-9cypJ7n7;N{mEYQocunU!H3Zi zGMT-^N}d%5ARY@+t^1^Jn5uGzoWTx}ySuuS{E5DcLu}*^pfP_dgUm6Tc@ZlcU(des zq*#i0jbiK{?8oPeDl8qd|DPbiF}xoufrnT|o&!%2=hTkakVlj=D9=;Ccuk2uo`Q9N zSVFt~Ss8W6ONvW*W6CnF#FsEhJ@@yLUx4$!p`-t=|2uT>SGZltH?!O1^RKG|>biGX zS0pCekiP%io8f;pIZa>xxZVdF%p}!V`;<*gM(Nnvq^m_y9&Vf3p;nl@7jv zfvtHmyiH2MCRKp&PGV}aRc!qD& z-P|F#NwlDJR>F&Q<{Z!77KX` zn%XznlwyMyty3N9rHJyuf>_jZeU+f(=^H>>P5qGg1yqqfI zZ7@k>4}OsJfg1did99K{Vwz{R(^L3_!)eubFj-i3LC+o9iB$3+hV%zGK>E)_u~uAQ z!${Mn*eAP+KWX=#rUdPFQAp0FBuP2(15QzwdMg^rTihm(MIyeK7{#2ti0&V3Z?MH%3=ELs-hDJZX5mygs>{Ybu)vNHu}M=T@E8#1Xd zOevOhrn1^cb*c@`vlR)xl%Rz7bO5mRebXD;L22Tnzv&;8==iA)>#VFe+oQv#wb|p> z_s+uxQIKU-&2=+fM>Y2AnF&~p?=!o+>namwqwI5S5fbZlTK6HT)7{~hdIZy0eC((H zQcIv4Wvq;Ui7&8vk-k3*=d*&8SnJE<+2?tFN~9;l6F3HMVP)&C+-v@#{M2OfWZmNr z2m!n$l%1I^!833S5ApfD5bmZP_JOMKH~n7hv^V2ta-)|gJ!Tj@lZ}ZLO-d=m%A%i0 z#Lx2Hj&H3qrS!jIi*uRIr@qzNnn-T{nY^@hk$;Ar>;>s4oAe*VWSkPKOgUI28q2Yi zzbDT!pMeQ5J>?o-!4ESBzwlagWpmg?aUT+>%vgzLx|x(I+{u2aZ~i{Wz#9_VJqEL} zdhnCx;m@4O-X^6hJVv$w_xfK}<@N`A9f=rbs}DW+$G;oTj7dO0KElG{+6X{uo0{?jrpL zhVy}}EIelqpn+O`>?G~;chsE=(-i|ggpF7OR;v%tMRcX-h#1Z0kz&!1 zEoMHUB3jF^kCkG>;Tz77jrbtq=;BefrY@y#Yx1dVBhN~;y)=%d>Eb5#67~2o7{UgM zFI3-3i+SQI8;tGfl>^um!r`+-3;8G8K{Ip#(U$Ub3uSWI6Ix(1KAXQHhfY17Rg|P` z`e4YgPcu_1TubPxv?;?X z^IxF#2VB7v{6-vRYhHjwz+yPgrt>&c5AxGz9)q&bLJSc}uqDfGF>4mxy_TF4dThap z$?o=T>oD}jZPt3y9_PuHP6KNTBJP#B?SXQJSZ+PEC-XsUqO56;vBvP!vX5O)3}7MN z&w3%M@od&wtCzUWEY?B(5^=bLM_CQz5V}jJ_&}Np+tBaTWJS1YYVse9QC-@`^6;-% z$IOND97x;y3b%MCRzq(z+u1tSh`#S2ExQhD^e*aDRUilf^TEWjK9uR3>_4R5)>D1f zv8KLWqspuOIu}jVh7_JYCTLdpZ%K8EfbXg>l*O4QK6JwcScen}Mj7ix&<`d+Mq*!< z(NVkb%IuBbO8o=ppu7pg28`5URgJRL7E8+7!)=q5xLYaC55buo<_P{4B>-V=t(Nz zb~PBEP+mSa)$UACPIu84sTNnpw!~1@!t(fy@WeAx(DsuW_LN7Fvmv8dihBrer^JFx zV>h$N-(<2-MJ5##0S}n9r1+E(@d?Z4gn3v2Qb9Es$e*(ukkY(>x3m*S&>UKX4JO|1 zDK_LGdc|OPK^P>KR(gK66E3T}W(B**h)t)|+zptZvye+CIeTU9(>ptYFzty?8bdRB zGF=an@nQJdrC@+6iK6U1QXw?@(81OZT@4LE4~Nr>ap}q8?`AJzJwRxVKz+X@ki^-q%=Rmlk5!Eq1?b~ z-57FE|2wEVn4VOJ&-g(zn!SOH<`^bmBu&-$#@V((t6KkX25Jh7Ne-^R1-c_fY($_j?yc> zvr1wWuZ2}iGseVn`o5GVvMNsn!}LL_9#1GO|B|KPlJ_v@ps`3o{=YHsH=jPn7k%SE3s9^HrTso(EwMm$XM0UM!YW_HG@8_o%hr62iI01@C9m;5d?`QA z=hL@gbYvsNbYj<9Q&oIN$=?5QbQW+`En66$+4~^2Vk>rc7q-|fc6WCxDh7692X4nR);7e)o6ZyUO95eP-6I_`YwgsVXnYgLIy~m48Jp^7BcuuGp+| zi=9%*T|{+n(^(8M)zms&(7I`=I+;{m`bE|`t<89`%3O5jIR`*5qI85RYTTj<$iZ5b z3DhD0++d|{tR0<2yUc4-+k9|p&<*~}Jfs@4KtCrMA$-Jg(~unv(W^}pxd;^HuNWkE znRVbRQ($egSs_@=5$lAkhnAb?>tqac^o%y!Wk<#KTmg~(*tTHI9oO;2l|baOu2%4cGVo@B~c8PO+3>;8PJY-Wzh zDev%&I&r${%74t@IipvQMc8JdK5o{SocPUwat&ualoNLkE}*fQ!Z|BT9x#F#Ka=5G z8_EQB9hsG$=oO+o{h*7jM=}(wv5)mcE)}Uw7BR}IYX89>#Ku;r%BT!Ds!JeMKLUNWPJ`#c`seuhmW# zm1V3A@}u^XY2mk}4O7IFi6-}!nTEK(u)&DtzaG07zhmVMW(fECSQH8e5gjw{Vc zPHwC`B_4o}FV|X~SB*?BriApvCXeZQrhpmGdCO-V7pdD_klUnrRrbWNk%gQ2dV2hVkTGPxL zYvr;+M1OOIEOw}8ssQ;gd(;c(f)nTV)+a?(`9w!LyVY%X6L)=`Ql4iv*D5v6 zDdwbeS5bRRF`3rfLP>ef`RG26ml$pJgx#A#eH83eQ3Fjn>lkt51-jf0OdRT@tMmGn zDxGuJy})S*7VuDS*3l}9TI?R+bmDKe>w8T8x`H}DBOvKXDjWHW!wQ?)Cu zQhY+VsSo;>jzEjNIR3f2ooYnn>Y>-GdMegw3o_B3c{yX$D09ZtQ^}o@dIfW)EHO&# z;{?ZoOJ3BzLWvXfSG6~mGtFHOT%jJ7$Q^UYS8=C>5x=!fJa z>K2zi>ZDXP@eE$#F;;h02kCyQj+4vySbtX=`KxE@C1k#XWItKnl-Eymh|a*eofk$fU@8%+2i4MP zr;Ey$);E5m4cfY#WE=6aw7pIwSE24g>IT_+H`$zNQF)mMc3N-L9rSCli@g3I^R7DR z#-~_7g{#Y?g({UIQIgEw2*R z2&a!4ARbwbnZeV|E4lSU`Eyp&*$3@{b~gL8eUV8QCrnb;EUUfPY#p@&MJ;mEPa;&@ zmIbUPXs@QAvTIGhy_@rV+_l1W$(&FMFWbgCY|VBpL`hQ?-utyx(v{S1WEZmj>fIoX z&+J!L3Sw+OV&)cIlYAkA{Z}TE`{=klL{uGXwQ?CU+H|(Cj!=K>l#wV~lh|{mpPXUZ z=u)CA>VRN)Y(Lo5^zGT`)=~O(79O7p>W4Br%8YhYmHG4zJ=FwDMZ{g={X+5FIf?5Wv&UaA`BjTx`?vg$d%%r>}DlHbP|Yw=aw|ymP>~1L{RgY^oJxI-sE9g0QklqVOBGg6D(EWB| z7%q#-J3E39%bk2gE97)cKmO;)!(`znxdkB@g#AYsGZwSkbZ}{Tz)=X0ZK- zW}5)p!ctd|rH&($52R-4LC$ea*HivZC79wd%rnVqe86)SqjT&jK9J+4P%-Xj;IwIo z`I|kjciO?179)F0fCjmwcns^1QoTpNwMzXlN_5xboOdc0SwKswgfi4-dvs3c8~WjN zDvcT@d&uELvtOvGQOdj+p2RJ{?ncsMT zx;wl1g5qE<9V65AZ8HCx8?Og;DX`O>baOT}<>W?tifx34 zGaMq`t2;WYYn@kVYc;#O66|HM9%`R=9c7wLU(m~`dXQ-$=hy@7%KDG{n>woQtHJPX z?@>1dJIs$^8fUNu0~fYV?=*F~=^ISitPFoTQIB_gsXHgTU#pa2Bs#P>CuzLDvsd+` zHcf9&5%KX?;(F;fWYRU5QP|m}RVUqE_}M}x4Rc>=!=6XDPdnvdzL)Evq78a&Umfh& zeBO8}$9uA{bynSq^L5IYz9yRpvJQ)T&gS?qcR!{??4c?sh|h|O+Z2D!6p;t@COv__ z*%#l=nMgeRsJn?EaE%?RGq!tGyg{2if_YN@Dy=(Q@3&UU3^K%?Z}on?V3N7n+qsp2eA3uP<057ogR*$@>_dzkbQ za)dPfa;16ZEy&M3RPU9=F()sI*2;D{E1g)UKXBrUqc^T>Cvy#zlk{+W zz*bi6tL%su%Eu|1sVAV9a7246()uJ8gU(dc`N=vS;&YyvflR!cK-K(8_`052Gf=Zw z+FN%=S^B}w;i_vwP|SAI{g^L0#JXXnFwJ#weH!#2si`G$*z56uDO6bzX2zmp-(@qk z3xuQ_8TnjY44z@9CFNpO%eh9Cd=3P6vHi&mQh%ISXE}&VfDE!`$>}mEl?U?^q{pk{ZWssYj-adE*YG)41Sy~_6@0mgqMs`quK8=r}D2(w@CO~PE1jX|)PC##_(#`>q ztB5_i(Eu!@tLnWfVI`FD<`~$|0p{R_=wkFi%6K`QYAD)ys^*x2^baHih3l{9Gt=^mjuQ`cdPV*VYMIo@h>EbUS>cpos@Z$B zF+RQfGnhd=mDF8=K9!{II`L6XIB_l#W&caZaa-sevx}vor&GZB;dF8{>NUQgS!jx& zc32%>oOw^p@b_y?YyCGq987SbJCQ1Y3iTIVY(Y$*O{^ZMq++h9DLShQY6EK<;#|;P zvY31ctF!?PYcSYUYk5t66V-HUs@O5qvByLq*9Q?!e$vA@Dg>X>(Qa+^Bc46c`SH4Y zbP21Ayob+FZxQ>Z-!up83L=ja>rMvF)0>K{w7hB#sW|#&X2biP)+<I~%ZK}@-; z1=_M!UBzomWY$R{cB=wt%wMv==lU58gogJm2Fkwj89FiN zL|Dz_N51oE(O)M~Sxq~uuvHm0rn#ACvXR>y6-De7@-dZLRdAVmXnum2D)-2=F-1ik zaS4_3N~^pTE{5vse9AGVU%dl8C?!Von>RT%b@-B+!WwMIM$!zSa%(TsnisCbzZvs%BE2u0!%LP_*(?RWFB5gKv6*kOU{wF#) z38}a8(Pi~hj}ni}0_Ep)bQ-txB65?7a?cI(ed zG$NlIDWB;++9uX$_--W{%B@VK-9pV$3|y4| zOduNg4&t!cLS8dS<>TBgHIq^Kw^XgsHPoi2+Rn7fUaE`p)jfiq$lO#ESZ|U# zoR2Q-_cqiVJDe}@u2JMdw?MR>>*H#UdyU#{rf^cSnPqf`jv-=RG#@~qck%i(BCkkl zR$~dJsbkWa+ioXOoxMk+!d>>$Ea{p%LoH^%B15+nUH2ZP4U6sOWjMwPdNEcK9jMjh1zTt6A# zyE(OPb2U)4hqLLVHaVr;RY8Kn!K9`#F@B+Ypu3?{kIubbYJwW>Y+_X#!6lB@eVL=a zle)D&QL(1pt6QpXP7$=A73jjKp?%3LyTdP4qF&EVesfo+C-YIJB{|j$v)jzk&-5MU zbDuF?(O{G{hs-8JrO&GeFgd@0Sx(nOZ{sJv@p~K13KN5MR+8SLzF9}lSfl^*GLoF{ zjXn)xHUJIwNOHbR;vf-g8B?Kqhy$XP_<>!u;W_G>O!&?6Vkc_8&=sDBohk~J+THxq)lq=1a*nCb;1VOjq~CDvR!|Y0C#vq_ zJzZeerQuy3iDYy#?FOsTx-gj07O@xPr;+E}>$5yTFxBH4yk-D=ZVlqVW3$XK5sOn$ z-Bi}m+JIub)sM|ZZ{JN9wN3mMXx0y70p*%p+9Fm+`Sp=rUOd z>zdx2;ED5yNRt^$yo#1?ktr$C&^9h@I9HoeWv78EwwvllZQpnK^7+ zW+4R&k$#wFe{%u0E zxWpXx;rP6de22-zZ$D;Q-6O+#&)L`{@{%V_$Cs@$IRt1jk^DcR#ZsnU?;$!QMw7J* ze&r0v5qUKy^sP>yvxyhzVCRw>jKtq`GHb*uG)?b0WuKXvx&);OdNLFsUGe{AU^J)7 z$KYjy!Ausjo)N4Zuo+u!P$!6r?|AFPcqBiR&j)>kME!nWxrf{R3 z#a;OKA+iZ<(iM|JbY>rhfV!q<2G(;CYknAu3}Uj#NKDzma|Vj$;tNx`-oY7^MZf$2 z4|WmMw>m6H7*E>`KT((GzpD>p!ADIyB0@Xrh4OIpQ^h>k+4Hb7>+u?HShqYd=q|II z`z`#*ke;)C*`MAD%n`)qnrn( z<8V8MMfZ1dafGu!fS)vkoli~PP?G375Oyh(X@-6NGE1>HDaymiosz3%Ww}RWMZrBp z76e&(CFd}~!Q1*Ile4a!`R=XYPa=9I}B`j1J}Bq8a_Yi%|8=^`O`2VfxWSd=-AWvNaT>=mJ{L{4x{&B(bhD z`LVa`3L~2mObF!wT7$~6h3qW);vF);(^}NB9tM{@1GZc1Wb9Z3R-2Ps7k;W8;6KH= zVIdAp#1OR+?x83Vya+X96K?g0U?M_(l^d>LC6g&jDxsdZsnOwWcT$gKP>s<)1gY2R z4@#saXpNt1gO9#J6sV#fgPX3RHmpu}T?;iDPWCaC#|)78JLF9N@J9FWIQxjaIbdG8 zg5Ip8uK3HIr!>1bIU6{gEln_a^B&{I8kX~#Px!CHVgOcBgqS#6??emH&rCJ()L3Q7 z+P~^OIw3YW#S}G>SjlfrdRM%#$FF#cXwG_lat~vwQn8Ie1(=;DNk!eBm}$@1`5bS! zgd^ssUWwgLqt@$5Uv)P#j&<{V)a-x&fS; zzq+D=%n#+IpMs+eQNvZB+Kac!K%Fy5r`O-$z}$GN9>g)rgn=AaCA&#VT&kj1Q;l@f zU)4c8>Q&Tj9ZgQ2;wjnKcs^@Cwa;Aqata-#4}#EzP^-M8r?3xMZh5@$b^i27cV$XZ zUAQMh?X(hZJOOpc6P~azKI|TTjabi}8{zzom)Ju*ABK{oeS&UigV^ zc){^93=U$qzGY_MhkA%~@+W!1S$&^eaiwVi+7?W%|Bi@O8f<7N7BGOSsJZqf|9DD` zwMYM^d%B5!uO^w6dJXLHK@+W8>LPsqGd$~cUDi}U)AI_i(cVzEP%p+~hu>gpM}of2 zHkUx_Z(w<2+4pUF8gbw)>vvOyvf7V`&Y4;BI31-bfmqkp^jxYd{F_rvQcKv8ro@Ha zAbKC@p~{Kp|Ejh*=&AHnF#dt;Vg$&?IR1csTy}1}Z#6TM9Pl9M=QWc6 z`?ZL@9r15F#Ay8S7*?tm`N?diL~220H0;|uZa=Ce9*K{lu<(=h<#X|yZ`n%vTfwp=>_JiT zdN1@8wX7Y)wr1QMvPe#nfpP<0ZnF#kDVSz`pf+yhYX&kPgHs8}Wa-9w-8-HZl^mmS6`h$+SwKvE4<6(UG<3 z4r{)J`(x6Iv|zlEJk?ckmKCbSJAD-OiO_e*|C^yTsUflx=`*misi;l{$;SN2AI(gyHadt9gfgfv0_NGvN$rY6>Npbh2oaXO}F}1xlDG0<(Q3zVld2{FYh)2UD|a%c`i>;fcf-K zP^gR}lI{T+DQw-dj$6B}1@;W9I8L7UHHK; zW~3E_E7`3ZqRQ$*PP3m=8O?cm!|5wzo|<)>vhtj*&eSdwu&mr>yZMP1Zfla^Y14@~ zJZ5z~N)>pvII7&4<~ucuKi>7VerOuP$ZH*hXL*h{nr)hJug)n|fpZ-OnlzaRIhee2 zksgNcq!$}s8;ZBuN^GlU_TeQx`in)n1@+cM;&EeqNI9zC$*Q2rr)Ps>l>j?VL$B{e z>fqgKA-5f5LCc&}wA?WKM@9o0K-==$dr z*OOIOCj?zZJ~f*Ai|X;4KOL!)sT^Pj*HsF9Zj|E2FuJ*OqflK!UXWfF)v0-^L#+Nm zG*?r}9u`ppF!_tU*hXGkN^~Q3okla{EBcZ7(A+iQ~^r$0lDA8w752)GZj#=oCK3E zr`|Zk?oo@&FKxQ(8W2#4L^6NqZ@$EZgzKduXT<(JGsBd-`VVBN29UdX+|9% zj}~x@N=()@U1h<(i?UX&`J^G_CE@Uft+`*WJU+h*IaFI+P|RZLR}b?EuYSW^qa)?9 z_-VL(OI$MJWd?fJcgQ0sbo(=hU zPUVpi4|No!vlm*RHQ+df#R78*zB!J3Vm@{CB(om8GazIlpPR{Je$g5A5PjJc@X_YxtNI4tG>a(Sik-g& z%2h_MSHg_qRPUiiJw)E{oRb^Linr!<0b~}l^h)KcGm;sV015E0ag(t8F?7Sk>7i7f zhrwc6>Uvk}=_20b7NvLxPj!?A^TZP zhS^xx=fvD0508b}=!VVZqz0xpM?~s0crX{$?FKTKp?u>>`Xh+ocd~#Ex;%)&4l=yQ z`ZpaV&-6^Z(HLsH&1N9~<>B;O(82PC_@Cb_C&wB=_Hm3Is|of#n-!DX5|IF(dzPMr zew>4x==%PVLo|oyb@K{0ey0+(^-VJxq_i|hVjt6!_e+U4??*gq0?*q^-Zg2+80j35 z^N4~rF>pIK0ELRz#HaS0$_K3BO3{MVjuEwpiI4d66%cm?GE$B@=#(ecGu6*uK1Fq~ zj#})Y@x>x1@)-{WH^YfaFego^On;H5oM%7A;G^GB1t)?%uMQg(L5JLT(4t}DuxtwA z8BRvqn)rQ!z1fM+`~qV09Zv2&`P>b@2NT0gEB56y`BX|+hBv6?2H=5vV)Z}ptwY$g zZ`?q)4RuineD)Kp^c)fA7I~PaR#}1;b|d>%g^ZyQ{X&yL9KKRNqb$Xz-^9*m@EfuC z;;Q)ScSMtex(n#VCC)}&RM>URBXZcvocM6~xM%$I1qehapOBc!cok7=fi6TB#|JXx zHtbMpGJ3<$`tm82sORq!VZP!;GmxP-BVR60)e(!|iXb0H>qx{3H4E@4Kgha|fZ^vy zQ{9SM{RvNypJqxi)2tQM6#h*CMSM@uB+Ogtigd5e`N z#49@}?YpApE z?wJPceLr+@?Wx8aGQr>r&vS@OHHwu_gx%hSjT#6(xQ*CZhMcSko~I}I_yoQeXan(M zC0dq@#MA?zh3BblDzF>(u!t3?w=AmHvkz(U?FIBa zUhxutbAu@4PtA9Yn>sG*BkWlydzqAT7D-KajF=8{X#A+$l7M=Yrb7BlMYNH9dH~8c z3dP3^^M_L$hQI4&;!R`GgtK>)O69Rmjc3RNp7b3*`rL!J>%~;2J}8gsihZz=D`01C zkO%DL9>6T-J^A|^PNEN|whrI-1<|G*XVQQ+PexyNkQ%ZX7-&D@!whQV3+QI^lYw;O z&o_9b!)%EZVh)-zKQWk$KIdFcC_4sx=Q z9hg9#e9q*fMtm&R@cA7%!9)4v^x!0E*|%h%O&P^e(0?D0>N>1^Fi%~Q7+PMWrw;OX zx3~D9!bGOo<{T@Yo$AuxWEDTyU0*zpr~7pn`?Q~T_2rXyq8E>$7w9DUd`6I)C7i*Z z^gi^*r#vzBgb$k2)S?Vn(Nn%jm11o4D%&AksBojDL6x7`e-t5VAGOMCgt=Y6NefW6}s`83(FVQ?hCMsNkxBuxuwK+e&)C;qz zMV1gVx|8SD#nV0FB!!E{V0DLBpTw|UtFZoR?8-fJ9=vn{k+{0ZD-Yv)+M1-O^P8d_ zNkawF4V#{ekIy5{^Bq0;;$tH6G&F6`c%nn#$9=JgT&&0*osr4TKd`hI>;z1dGjooU z{t#QQ&*^XsoHQt0NBqSg>eUY79EkBRGT#Mcq-)F^w8VjAU6Xk)4d$acT^dZU2X@y7 z+iXY`Rf7ND1qQMo#CkNgJs->aNY*p~57Hb@bdRXzS>wB);i2sJZmRe#M7D&SlK`x{ z4{YfJof7VTD4fMLPOUGe{UcFt5-a-|bV!@|oXYw9#%8MYtJqL$PU8kn*)~0x3PJoo zGwQ%IcL3{YO*G47=$s?ZtBp4+%MOenBMBwSzGq*PaKbuck2iUaq{O$XVB8h3-lO^y zOnhaNirTCLtC*Be-%h3w!I_$mGPNwYZ%UL!TS2-W@sr}<9%J!+{^VdwSoih(_IRzp znQzjw@`pX7PYplakNragPyJm?B_#qZ9Osndy|W=hpCHG zr_h71hFJ+va~%hct}Z&|R{Z>%{X&b+pm+8VT7|HV~FZk*`>Ku5p(FBIH-$*?lK3NC;XsGGOzCq zH_JxDy!v1>uc>E-v%bD~zTY5v++@j0Ujw~uPloV_xLT1Dkeb;?ZHaKRc!oFNLHev`@P`qik$UFYH-v6E5o%qMs(S z3c#y2ggrCPeDzL^qyHn4s^+%yjryk#_}W~o?7V8qPc~2i5--v44J49GA_9go2j@4o z|C}>EgNp14xmzwV2-IvEj8zXh);5^UM8w=M+6y>Y)jZk-eB@PXn3CukYU?iO=?}w@ zOe8XAXMH_q%)CzrYz7YsU>#02_VH2lSbG?E};yov$ss84iS6%R2@6imd#WohG z4`ky3{8m-0FHnc66`acH#JFIh+!^xhFN*sLvEH#DzS%rGOg(aoOnVyn$}0BMhtH^s zJrv>TAK_Um((7;=uG*#JxhrB18q50pxgUG%rxt-JKLC*$Mkc=s>+H|@&qr38i~nV6 zF+6ce&R#1z@^XtVFfjZ6kLB)zf64%(Ucnkk^q+_S+h@Kp5qd2dVN>E%S8$p^oX;$> z7c6E)QAoB0ars2{(SrGUC%`bH$sTHno@5xcz$e!-w`??axl_C)M%^U#oHOe|owLZK zavGfy?eRy=;KKHTv%eyO=A-AWG7N5i(L{zYVYduB{tT{dF|61~B2GQd{aJLMo5=s& zbUww=RMT;=ZtIIWMlL$6J7^0nDS zQu@h>RIfNEkIi+{8ea1+t3MSF9;;vCY3S+aWcS5i&Lsz1L^kB0@A>frQ`pZm;s9LQ4)d1Seu8tngq-PuX#=_zK)*==&hsU*9 z<@~3H_41~EC?Ez=hh*WlIg2OoMAGwQ)?c+BuRTK4+(UKa&o|1?6C}eodYJTToW<_E z?k{n8Jf3wvYdeFviV_U;hnZ#CQsa2;SkfBC)VDs6 zCmunkSDe=l1fMO!&U-4r2x_h%_F*~*E*vqZFb1q(Eof~z>BD@)u_!P85m9%-wO4?5 z`K>ckT~#whz#C42xpN~7)z>;AQzN`&R*-<^@IKyTQB&15{O~5wt3T8~gIT@wgET%!pZSL%c*jjpt#@1cic;CXa$bD9FI^D4^@W#xEJK2k!127?CL&o^%yy2dh(7U zXgV%Z!560E@RwQ+eh2r=3LJ#L%TKo34fX#_l-Gq}@{Xu(MEhszx%z^hVuOPsN!^6^ zYo(^CL(VJup=zP-E(7AZPC3pT;^t!YAKcVN-CnIzg;X*;aDClYP1gJ9<0(!gre7Yf z{RmaTUDO^Y!BJX(y{sh{d;wFKkUDA|{=FfQo4ZZv1@<5gE_T72Sl<$kEIBdo8u9-Y z*uzg%9DD5o?!x33?0z-Y4NgWbI}u!T4)yE=c$~X+ttOB2BWr{%E~0>Ig)X{Dv@6%6HD|HwM|b!BeI02Y)AbT zs_v-DJlj4#+f(^YWWAyrL12Ivos`K55$Vxtin*Dhc`8H7xMF4c-yV4&``Y7M3G-4#wTRLPhZee z@pl#zm4@<3UZ@2+puPA;b{z(OS{ItRbonxO@A&oR%l<< zM|IY@(fmv#i^z^*zLQFhR<$ZqiKl`OpK{ugUnbOFoTYRl9!5E%>ZrQw-ei+^^aquSbvZ<>I*eG*iOBO3JGx39T>`%r56&Bcs%0Mk-9zNY z!-rG9eBh^BsZJ6xOFJ5-@iI}*pIqxAnbs*7wz=4=gdKiO2HJ>Rdng#&80ydJWTo?| zL0TC!l%Twx&WgXpl$N}HAT`ebEG2}RDU-R2&sfQOPXtHkLLEHTG}X^_G_}l2@Rf2@ zz&Xg0SN-1#Z|mmN5+42Vc5Jy8RgE7W`WE$~qEd+^S_cvD$CwDv3NNbR%|tW_Q>XEJ z8g0ODd}k!PQh_-YZZNAdo_bnv+q1c@JF^ni#5*_#G7r3+AMr5EG{=)?;oG(X5%=fW z#^XsA^IqAA6yrG)>siZ8)W^^CcYNXnP>7A7SqGSoy+!Y$g1S%rzKk`T1p-=~jBYuf zT?nqenVABEo!=A%3(Z2^wFAcB4w>XCPVzhA=~b17oGwUJ#%2eih;EAVcsHkkN*81| zD~Q+xl@6vnrw*cy-lU!|_56)0qbz>si>iQXI}Gk%mWp%Ma`JYmVN93*>$FnG=|#z< z4{>AZ5GN0MgR*ez)3B;m%F^eZdA##?Is-k5-+_3$`cw#^WR49?L(cgneIC{3Y%1%j zXk5+`L69o2Lyzh1{to7|6!yD@oFenV$jw5*x(XEXs!VKoOD#TwZmeaJf=6M~ln&wz zaA$|aEzpB&{Kg|Vf-fjMBfvxoF@tLu2>B?wHTS~ww1v}~N$+fKI{u=`rrOf;{T~QQ z5hl*>rY2k^IuLJ*5Cc~b>-Q30+JG|lBm4hKjQ|W>`gY~DT3cIDZ(uYtNpAc+*VYw4otApt12EfL*XQvy0V2mQ4Z;I6{N9}Zj ztnsJLZH{BLJxzUGmkEhS!NF>yy=aDdqBb+6@{?Ju;nl10W2r&WJSzLlR1f#q=VxjW zXxVXn%@n74DM;48Q*8oed#V#0DjmO`l)#ua;Jr9_p!RIcKU+ewtFxRh?m|W;@TE zDNMR7soyavxC--x-#V-4mrcY>!$2p5K9v%xw9bfazN0Q2;m+?YAYRTx4}8EGNw3i` zx_2X-BhFSPwdGJ5oTl!V?w6?N6FPpx+#n|#9l)R6vz>|P8}sA;Lfup8_POr-2Z!_1 zsY|Ep+4#B62*+_sIJKO|?pp4~?vlNpdbMVOfL zkd;3C5p)k9bFXmk!|I_O-Q0*>3*KiIZUq;qrXg{ zYU67+5y#fC9^+LiHHv5zhZ?1#ZVqM;PTY>-3}t}XoDC-zjQ{G0wju+#bOrWiE-1)g zJn?;)htX&TmSTk?(eB0ay1LW~Kgr!aeii+e?nK1cs3-7xUt*CTD9Cg0@2;o}Qq#4R z$aKUq-m|lZ=^?I9Z1Y0}RuL?9Gstp1a|+(J5!K=$7>xD012)7}=;_Du9?0aCskl;@~=#W#vkQ^`e6!5QTsB4xu5wbaj5J8a;%vxd&=BQQp-)GcQ^ zsK7L5pX#bs!Kfus{jrrB>aaUIK53_OmoA~KSWP>yfJ15?ctaU|nw+5!>||A<-X-GT zd=TI;)f>z=hELo>(u zWhW|u`+1Zx53%v-Jk<_9W4gW%dLPaEKc-v8bK5~mFx9vy`yi zMb2pEqzbIN82vowoHlryU#c3~V1ehU=bU#BBp+Yy%yhCiYnf4Sz}X0{H_LImzdOH} zTaZTecXqiW+%Md1@j`dm<@ru|#|sVieek|GEF&kc&#q>|(x-!Q?%_OgKX#(jSL%cG zWbIiU3vW4Ezrwcu@_jn!@i14>uoxYkR;d`l)NMOAY8; z?vIb(3OCyVY$JfY{fl}_{r(tE#nZRdo~&XBD8y#ot)t;4G2Uf481^1;C(nrt=hTgW z8H=JO_MoNpL6NWHtKN~lZX=It0GjlS6Mcg@9D}q?6>mxJ-+L;YNuCW!88AF z1EqMX_GIrV^>?hP1zylkm!f;920sN=z#{MSo`2{!8pboXAYUIs_BfX8rn_oL<+&11 zxRUy<8wgoR@=%YO72AX18O7i32ir?cjvY=;e}psfgITp64zHQvsQN(+ z8K!!A)adw(Kj;p+;|Eje-_(weReS7sv}*6H#?$=s^gPfnk^=A1T|HF`$mwHGd;>)Scg=gxjuc`B@fj*;#(?=i5dyhcD8AUwks>%`JH}Tuw=<@L( z&TGMs4}i1BVTbm0`c!~6^=$}9{R=YJ z-K_UWYUA&CzdO9oarVXFbrxemxu^=0!pg+q+seZ7J||mAhegMOvfid%`U3X30;@?$ z)jXbD`~vk)5cV_|4azw5fF1CqU-->FrVlvEBjR#8EHWki5dGjlD~lNF;uz4sR8)%x z;5fR#cO56M&xW7OOVny_Y;wG^R0Q*=9&!u10^tBsnLeliQj#H`1Fb(p_E`tqBAoov z4^>=gSU2uV0e$nsazBvSwI@R;N{!Q;TH`tS;!Ls!Zn4*ozz^QzlOO3XDkJq#Ht?zu zU~kbX3co&xXHQFoTa3=3qrAs{>fF;zguBlO{V8VW)1(&>rEw>o_>zq z|F8JQp!)rU*Juw;JQ2lj7JO$l>i3SQLXyLarlU?AMAm&9>|h!DGT2-~Kl}o3@P&-& z1(rtNg!-W#^O`yMvblJg<9NZBx&qlq8*-BLc>PIu%sym@$BB3^)kaQEUUk#?hPSAJ zul>r7{BV{yImyoFz!mJq<6b0JZAfQvGu9<5%+Y*4vjCp{y(&bFSP#5t1xlt|Jf%l1 zexFEDo7h@`2pP*t2nBYy|fNK&18gEkHX=Z@qQZiEdv z3WghlC0!;mT-W314XKPat{LZfKgiu=G%rWczT~6Uuf{j>f;X8326>Y@?Gvj$#ta9A zX-+=15Pz|fSno|1-wK=mjV(Q6OhTI@^I;jsh`;anZmX!x4!}g*B&yA#w#;O< zlTr0xm0p7FHwQ~tf~9+IiE9flor&+hiC&xr<}%n{J#xZ~@UF=@JHJ7?`s4M^uy@St z(G#)oAv`NO0Wg{ZMB(fpL~HoA8;Sevz{4l-?Yr@s^=Ki->3k@?Y<6h8`Jqd59xBsk z(wCi>&-+KhmlY!7Rs&i7N#xv5Olyo5)P{vJ4ALMs57h!oDfvlb6)F+D|Q8^%8hcXH|R?k zJ3JLUECht_IE;obJ2j77DS&eqL$rC#=QSfkn!q=TW<9#Irn#||u|&B6^gH^|DR&Y) zsxGV43O4Hz2){=W^A(lMQgFJ$D2EU8d4c%gv?%bVpc{>ajTlWNZG<9!7}GvSi|Zh= zN5KwDq@nN%wQWmw4r}pduzTcZ#XgikQoSeZ^PYq&U3-QfL@XuW=`V36p7T#+*UimLO zf0b&r6Pdzmv=xc4n@o7t*ZfW^)Ij09OB^}QdiB{H#|qAI{ymzEh4}SLR1qcE?JLv* zWXSlC;n+w)e6}~IY&iCN2D|Qu?cL=$n6!n@yUFvXB7^#e`r{x!?Mn8uO6TPrhJl0C z<-IDB6QtvJc7Q=801Ky5WLI;cH@U%oPXbq4%Ky*CpAV$^`$uN6lH6Ld7M`hT-Ld~@ z&USh{*FSxd_c((`SxjZ)gRKtdq}&JN{7S_>7)uI+#cd0dlL~)af+&6hX0r+WPzLn; z?@V`50e$Ral%0P--&&xp8G?H4EA}-BR(OWk0K->Nb^?X(4gz?a9gKh-oB;b%hN+ul zvARdhNfzwtVg7MAwLL-eQj1=2s6q6&akC&kC=-@=n|xt2>sb~qD}-;A0zTv#D@n!6 z&s?Z|w(<1Lr^DyoK%qPZ#Xv_gx=2>H2y-!u!9h-ge{6&n=(u$y8NKZv5$FzfsZyPV7k$$&rp&*N+GsDg^L1iqx1xX#I(gvAcx zf7{XZKbxsAmGC~h&?yFCN&8q+CRB5FLx}i8;a$_wFTzdc;KMceFUcJ52(V=kVeIKV z);9*U*atqq8-KNeUG?OXvq6lb$YF-FD`n_K9FF}pWB2ZJ1I`xk~K4SY!$q zvpMWeNoK*8V@}=^n8(UQgjZP71T?O5P?yyrPpyfcEI{AUZ!?MMLIM2#SKf0S-gz7| z$oBKQZ_x6dAwvmAb$1;*iy-aYkk=0$tntODKhlwt?bY*ju)4LW+ z4$-rzt$UM^ROL5@P`Bm<6Iev{TN!-kCOF6)bxpsg!}*}uLS|SF_3Umv95g!(^DW*J z$Nh)_J|ZhowLWbBU9j`!%(PfQ>`#os>@8Wq93sm-Y%&SwvkvS$_jzFZ7x?K0vjQ!~ z1v*7z#1wjcuE=*{pp3^$zhDOc4%yRc4Wm7r8C}oC5=%3$IkUBx8|d$#8fn73%Dz@@ z=3Q-oE#EH_T7lMB`fjGvC3BJ46!~EeHprGR1^=usa0Z{b*JC4-&$h#&XGc??OE#2^ zWG(qnbf=5^0ls1m>Xp~zZxuk^gYcmvIB#cQqH2m|M7#&6JjS6Pnh5({$sDEnxu-8M zm!pXBr;jZcdFNJ;gT5vl#A*<0=s`VB;tvuq%{D2hzJ%erPeu`nswE##TY0*Q26Kyp`OH}{BZInig7C;z)e*Vaa+)z-yATC-*eSf!(=t`no#JtuyOjdV)v zrUTiNrN)UP^m(}D%;7;gOLYHW27TG&fu^xg2QP3ijh zkBI%1xR9L=4S5SEGrQ3%aHAE6JndB%7o8!-ebz7pzcPiQ_s-n+$`+a=Rl-vSk zbWgaUY49NFQH!Rfx_XU84#K9IpsZ<)9&-gY_yb>mjs4hXT%+*q)m}m z=@MqjFulbAR#tX*72Rq(v7Dx|7&E&|S`*=s{8+COOaVz}HK)sB6Wz!w(Y}IChxPByIJ{JGK?oW&;0&dB>DgeX^#3c7@!~0Gatn6lN3OS!JYg)+ zb~k7KE>%(ryhVL7z78mQ*W!WF5ozdmq&uxE9xfEmRZ~_&Pu!L1AnmY&6{zkH@!aEN zbyWMl^i-E)*7*qQHQy-;4b2*Pm>#iF)-3BbcdCY2AFQGF9J`lY(;i`Gw#(YzEHC=U zt}wlFzdggwV+YuS?4))QyE|)B-fm`Jqn|9loyG3WT)>=mRlBSeV0Gs2^70-*c1x=s z9c=flYJA>)rnGmlN?Q%-Ck(T4+54^abR-V2R?^3j*Lr06S&6K0sW|~Pti@Ivt2;Nc zjbma^3MO|IvAZzAZz4Ae=d>QsXP4KiZu{Et%*w9M4by4)gilNr+-udcjeXa)*wH84 ze_YA+$+lb{?Im_q*ARP{Ym)1O{gS`)aTT;9UBg_LT)AA+T!~yO?8Mya+r?GXRe;x| zb4_urwb!^Nx}Lbs*`-_wT?gr{>)?vE`?!wtT?^VZT!XoxE}N^D>x%t`IhtN}GS=aN z_18{oFW_73mGha#*$``+ZMDMo;zc()>$VV6!{{g6fdws>t=So0`H@*WBk1T{#SDSh zOtFe#x1JE)Pts}OPad3=`^pAjBYUV31bKQmJ&E^WE?Y6dU@Wm zW*>9f!FFj^YFk;!td&ghu8tk~@$XCQHk6*oNp=p~&Aii#^k25;y_?x5IH^(er{d*g zgtV*~Xu;kQNmAfRxgWw>#i|U$*Ts-)^`V2mJ-l;Uxr5%&w)l})ro9H@f7h^%^U2gw zz$&Mc2dGv&bDOhsVsDaX)@CQ#vxl`5y$ecmXyGFXc+hbgxTqF3M)~+{ndv?HL zYq(~)yg9L_`7^g;TEAJ(U--NW))s3be(t9F^}+0zSqklgPL>rz){5oxi}^-PMr z$4?DbenVs?vRz_|(i%CB+l1Q7I@s9_&O;XaJigB#e;H`?x3XKAt)udv%x0~_LmAnS zSqy>PsXm1}<_^eVa-cPYIn@`eR?IocXT7l!*s<1Q%drm1dUU!rWtDI9o)O&SIf{FJ zUdtX<5B$jhtF2YtdSPX-$1pXQJ2KgUT=s11C!ZB&CC0lwVH$RJD^ez+o2-m|%DQD; zmoZrIOcbuou;xMBa@msGD{nIqWHz9Gi#z3R(O)$oBI6kIPrq}7Rs@zDXnuv;EufOWqOGl(D}PScDK^8 z@(by{9!v!9&ua?tDF=x+2Q4rBp2vn9xtkv1fjr+LD~H|2-oV{E1MCHMoSnZ z=?r%4hpr^90j@CDf3DiDyY>@TsOu0ml9QWk-`VqBg}u(Xf?cJ(PGUi+yn1_$@_Oc) z@2ckt^g8Ow?-lF{@Or~5)4T4v*6^zGu19ne&#>>=KkQOoL$JrvcC77kov}CLL9gNK z`dR<1+4fS-ZaLeRb6C)d!se>jW!R5Oa+vi=`rAItC!2tm*oTsD3QE0I{MS0_jUTL9 zBwc2OnYz(HOl0kQl97aog=o%tlOY#H5gUzSF(t9=9J<6PG`~HGcb;1uPEZ9*q24J( zZ-z#xlYu)N@^ErLk%yKAt35@wa0kWdI%>wfWMe(?VP%*?GL?5oDVvi~PG@aXG0W-( z-fKO!70j&>Rji-b2Q!wLj}{^KSZ>*uIGM`cZGC0Vv17Gotw%5gIWcED8{atEuIo~E z6IP{+&5bj*wkO+}UBfx|$8F(SZMWsM4cVEN_F&gjd!yZ)^ZUqNWQW^#>=bsmRRT*~ zXeW0qp&y&OsO&?;+|&HIirs~N?O@(}0pvN2tV6__a9M=xvLIe;CZ2v4y3(ec(@}WQ z;pF6*__N}~rsw{Gj#jK#DXUtOu)86|hdxr`>B8_2E6As2@;>|VacQl}{9b7?(z7xX zKfi*CXA0Tdar|f@cCImfz0qbnQ7B%#NBLcVI(Q#C>Zf3BA3$Oro$iJ7pmRH*H!D+54Gj+1Zw0i8>LKFx=DHu=cYx}x?P zhvuRhnD0`u^|w^9-N7S9ldIN)%h?QHUJ%v!A<)tuFhC2)Z8b`td!Rp~z`$RDPUPS& zjx=!W*Qi?;bA!k_>M#eiaw*oi2_|4OI2>Erelq%%anW?g-z&|#HW%A5Y zI;1;;KFkC`UqhwZh{|^#-{7X{%Z*pVcwY-F?h@G5LOS&F@x7lDrz+x~zwwRskvUwY zwkbp9^%^WDqnIp`%FR^yVc33c^5{3z{F%uYt}=h8FDG#Wwb22x$X@ajS!!SW&=BTi z%@RLRYyD(y-*2L1agmR`U&X6Sf**LvdZIkHKJ;cylQO}Y{z4E9`rL>Bd88Mb+$eU~ zoA?k-#XX<(^h~Q-05;N-`Mup(lU3|lZ*+7isea2*4SWZUPVK2obt@{e@2tZW@a+vK z?f+4Qyr)v=i%#W?zG=>aMR<^cONyDdta?T~K`+pS0_ftJ@%=l28GM8Z=|J6^A3P@m zXr+rP`6ztw0~B_NO#s-#JjHu{1=~2re@_MNegiXchWh|dc_xIQFRu+UGKwCD z^5TmgL)|@#NDv4v@)|Wn6Lv0u3ZObT!!MlyeCHe3YeC-S3$4C|@QIOln>{E7rh(V(VQ*tl zTwSA1y{XrOlI(yNGyHA|Q16{^cauTrle14j{Cxx1yk+1Y?^we?UYQVktqRvP6|A-x zD9dH6^#prSg=d)$l6fASekZnd41IJO@K0D}7@V!_=2Ms{fp)=zH8x@$N`Zbyf!XH9 zyY%AhJq2Nm1cNM$e)1r=fQM@)>g-Usl|31sqFt%^j35$NqR^JyQ+fbW`N_~(-#`k%;> zPf}l=qEbDN7ir7vlJ`t>dB@(D2Wi_0P7sbCxeZtMo<7?*FhV={t@Mu^n3+xg^&(pJoe|-hF@QagF6y!ZO9%(3u@_p38cj$4+ z3EP$cw6G}E$|Zd3c|Ot8gWx3@RJo1itKXE`JVs#>;LLi z@tpH~$9-S(^SQ3;zEk5qnwCtnzX$DNUpi5O{cS0G+6i%A!A^W=-=~q8<|^+lsq{!i zg4XaS#eKIdJ5bkN+(qV_vHCmxvjsnU!MA@QLx*v*Q`NZiVkohnHlv zV};c}gh=~$uJ{ApE5~wwp{8Jxr!0)8?}}v^!S-c^Y!4$pBVd1DvnUewgk1nc@>EI%KNHqvk zdbnVImgW&}GtO_BRRTj8j5Bx$PFch=wuIgE_0J5SPGv6pdv@L255}ZH%He&r1eN*o-gdGG z`+6^1e}!Es!fJ&*03Uja2K3==vK8Z_9@AOh7>-`Te6+)AF$T!dooEVy8Ox@(;#p(t zYH_Px5vqTRWqgj;sYpLUuImMMs~X8HNyjI-!a$MBc?fPld$1aw*nl**6`i!wnKjmD zCaCgWM^tchVRvR&--r0cHrBE>J2TJ@RJ4Bu{1dgNlS%MkOw^xj&l>i;B{naM_xYPY zjH`6`h3C8_f_TZ(hMfIIa&Zrx+wHw}u~IqU2PIg6Q9(DW$Z5st*k=w zg1E6a?XM#~`5}0c%_*!8$N3gw-RJp-Mp&mUbY~kbai~wn+uPS&={O7XFRg57l@`&D z6=Wrx`L-N;9)}fvOK!FYKL)kfhO8`Q3tY(;K{!RHiw>(}qU4-beldMd%e$QmLXLGV zZ7zdFI1yyBh6BWy2UM%HF|Vy2-R_04yc!%%{1wb+CkI)>+v*NZsnrX6r?PprfhwbF zLxhipSgt3~bu7X^;0a#g$+L)0GV)qw#XcD#H^f6TSbz~C#V1JDP2GVtbdA&{ll^(1 zcA~`o(D{RRH1HU||F!t_BY4a%E7=v=-PyF5=lR5gB9UwMEgj~!tl7vBy4-+Od(+!B z_eR&~|5qZvvtImJ`E{mx}qNDZCH;=J>gq@%WLloTP9VQqGh z0Vi9TB>RlL$-%G3`Jq0hek}GRMabF%FrHj6$0>gECsE=^QgnYC^|*=ytmBIJ z*r76Hy1G644M&jLzNN!7b>gXa1|!rNz3W@+c-)ZR9Z1ffu}h^%O5PCZrgM=Ta~%!&-bs$32G+sy9Vfs-puhGs>_IH&eP^gZ?_ijAuxk0}r>gZIoW0x1E> zs%lo%gK+c?Y~}Y!$O9SeC2IYL-B~YxEsMhmYep{H^WVwN8n*U%@%cEMypsdOa;a4+ ztXz{6z0 z6&KYPIz@IC#q_*r?(}|H?`pPv6!xc<^%zYDui(gXLZjY>2w#>J9)fO}gc}`Weskr> zTUfMN(b3WCFuvMRQ(dFkBPDQ5_ogh0RFC#GDf=iUdtdZ@F-QkfvpYuTMgENbfsel( zc7~hObTo1oJ}$de?hYZCY%cTv?~?#p}J2- zU~`X(f&P}Yy)Vjb=Vz-((LFx-+TRMWVrN7l2ki5Ud~mwR-w-ZmI>?Q>Va~oKV^6U? zxoOQ@mZ6+VwD-Kh=jw*qW5_aySRNuDY1ATJA%}-aX+3;_vjN204a_w8oaZdz4T|8X zXNVEIs7e~ngGF@5ccR^|>Ziz}+D7c@Czt(X2-&|WMx5xn;n|@lMJ;K?fuCBLzu2;B zo@bbhu!#!E``u|1BxebkeM)@19)_`xzON^%NozkwjJMry7qG8wS?HZABHrXLHjrGs znQCfg((tV6=0^Bzo_f5~rkSLcVK||{dL6aXwdrF|`OEo8z33bG_F?q+CTlZ>b-N}a zDGbLyE7Q&zN{Ixq1DFW<{)wbfXLgY@k&$k20opUaSh&maL8g8I;u->v*2 zl~lK(5kITUEyq9PksYs8CtR5(J9m@rUt=fh%LjIe4k-180sP6Y3 zFW^mcn84VPo$3$utcu^+K*PiNS#_fI&5B=be|DK!-pX|j@}p^@70swV1%dh1dFaoQ zqtSTxF62F&=3WNdn1?o{n7q^qrd3}o)6c)9d4vt3)=470a{949vZ71K(apeYF5l1W z{+-#sXK~#9>GgiOMmDtx>%=i*J@XOP@VYe|te&!E^u&22Eixi-|b)>SJFmPrJ)!fB4;u zFqF>DV9Mlq=E%jm@?DRJBfe$*P6w}$rZsk=fNbU#q$BL1NY76kl)VpPDJ%0$Js<`< zd9Okw_q3StIX>%RFk5z0o&IFx_x^;$Go0&XPy7(cf0jq8s}puS8yJD3oKSNUq35+_0j;1nZzfxj_U}c?W$)-{7BT7% z>aQz{r?RjZSJh_k466I)ZNB;<|2*4D=1kU>%=r ziM5XfKdE55EN@;S&iOpZXy#iuO`)s(=r51W8+k+o)QBF{hgBt212z!}Z4#~3pcT0z zCB@+rMK?!8-eJv6f}PIKE`CgsKNkz1lbuy3f0tkm4~i_?(Un@fLs!!zZpkbvs|df% z>NbpYr+pX5-P0JVT_k&&d@UPIY(i&RupSSIPrre{WK)Op8cj=qj)t|K5q!=C68sOl zwa)yJPEeBX$w%n3hKmx4MMttm?Z|RV8kt&L)*d=qT%<7>dsv7@ZQw!IVO$reX(;3K z-SqTkvX?rfVX~q&vc%hbb-?1iCQ_>lN#8*qQ$;^xugbXB06)#lS6^j&_F0=WtXelX zU_|`iP%UmPZ?cc}eC$lgo$6$3)3{r9?lwtyU+lP){KaJyZ?R4fi|A(ChnMKpFuBp$ zpsni8t>V?N7y0?*SRG_TgQT7NfVGa0(yh>~!gnlNGrF-+R+Y={_k_I`k&8d8%J>QL zR)j1&lao%RB_peNtXGp$;j9B>H&bK~-;teFu9}rhtx>05nv|Cj)s`T8gUQDx*~q`a zOwZCK=s-&zgb+0G&x=sHkAtB!q+fE3II12iI#VqDHH0v=()z9**r`Y72PB;7}*;qb2QvKUbmMLrUK|N4I?8b9^kb2=@gy>$+o!`1) zZ8y2nE*kzl=~%@U{BDijus4~+kNd1`8~*r$pSR^b-?{V5r+Mn3r0=YVWRf`N1l#jx zu+RE65*ruQXVJiVE*4!J5Dyh)pUF5W?`(cVF%kFIkb<)!m&@XlH)(Y#8q=CRYY#3H$XJ+u88WyqRovx)UsuW9UNl4_shT^XN}(A@h{rDdA{4ty7hLw zQYufc%OAqt^AvvkT{e3he7zbuxJ)BYxYl;Re^h*Wl`T8qPlrA6FxIdH4yC*5+CJX= z9`8|Boygqied6ON_9UEG{DFGcODYNPX7MJ&Rj1gs8tlLv48niVk&S#@0l9P>(JBMG-bDcko{*R;}LqZ4O$Wlobw}-IThsg`AU_>@AGW| z+@%CPKS2|#1Rt;sCuqT2I$ZjhrSlL?y}%#t(h2yiPOOq@lRLv$O3?RXiG%dK0X>O9 zUK_$d%0u-&Wp^$H`Q$MFsv%3GQvWXv;n$uN&(#Gs9j@_vrS0?zR;!J7tz{pJ zLPEZh(+=Y!FId~vu6#)3n^706xg`Gl4LiBbPu}zSOcRMxNA}Xdw_uYe$Wb-X%6;Un zx>XP7*&hmC5c%ILrpydWSnA}!!XmMjtZzCxTn7R(0h0KWO7Vm2@g}>{ihVy39P{_7 zo+B&i+V6hd&39WEbmf^(m{0eg9-C*B7Cx} zXrMaYW2GM3$vQyhV3!^jxxVhR`mWxW{rv`)GE9Ht3G;7SiH;_bjU8lSxd`B1y_j>o z=VqQ^zg?ZL-h7;O+957_LibZuyFDh*_hP5NR+&0p$5NCeu7I1p;<=XTQ@a+FWUcq} zM876>!4&k(X!U9CYA zJKoYNTvnSn1~XG!T=bk+@B`oc#OGzbT`rPXorEp0SC{;^Iaw`E{=3N7myq|jz0qNx zHBEN3zK7&one5MopaN;kNKW1f?qjF7`S~Dg7M_bUf(*9NeYi-!{j0jH{-=+8vmVM- z!5Q6`y>&!>n&_z;GI_E;-kzp z*cX4tk9UaA(VO>Ge6%wO$D3Dh-ZkpSUyHw}PpE-T>2vX{P9V>3@<56i zMt|uo+fDcO>x3@m8O&|hjh&gkbbwHfvVTqOP+7TIOWDeDZ`MPPT4`SRHofQw{oHT$ z&Y7XoF!{Qb_<#<2U%N6gIhQO|!F|=IIU)ZN(^(ibmG}kUZ);ti_FK!~9#zP~3-swO z67wnfO6n#&rr(g9CU$%GmrM-!FHtrakf>=MLo1!{@0o&r%V%@q!%R}Rtvj`^ZqInU ztan_m`>l&dTL^DmT2_Mf<+ce>3yD>*T`iS;e8E)T==@_To3++Gd^ZlM&3qYCItJd{AdYEjVFE zJ}pPG6wC52t#C?(j8G?y_~SJ)Qcy-!Sk66#-`#3$C-S_F_?bs>K2Ndn`E_Gf)2p3c zMD%d73Rw!d$t?W-M||puppKaT1fBQ}k6jH4*`8Hx&y#FbwUC|OucG(wTIbh%a>W{F z3a0t{e|BdSKdRb1*_L(`XAK*R@WXRGnz~vG@laJV9L8>!*x8xxvPchagp@za%Qs;g z)6j|2*6(Zk{sRqm=DO_YU+=&F4tnLB#~?%UsNGGN6|)tFo>~qRMn~n~eUFNcoMz8ISGZx+4Ul%GGyUIP?H}mLq^k*_RUr>>=5ii&uY`-`MB&hJV)PPZ7_i) zoNcvMTEpB{sK2$oH#v*F{=)o)&YokQ9JUWnc?(-Hl66eD|Mp;HatIqSl_V6?|7Kb! zdpX;c6TH(=yOE1*ofkP2;~(B(ai@vWpA-EzXDRB@mmlqK!ZSF-PZa;27^*9+e3_5j z>bL5!5^L$j59Hh#exkI-BKKd!q`#8muqX7kJ6B8Ir6+X}{QiO{=?ht8ID@fc@UW+(ys2w3-;r6l>>5Rt*x#8T6nHZJcE97s>Kx%5p?a_TyBrPArIzx6e<@7gDQH*$7uy z%Rk>Q{@YBp^!BhqpIXbMp7M8*$LrqrHPP&3wy2DFIUY=eL;Mlk@J^#dJM)u!tkixH z$LDafQFQJZzke*a9`u1I=7>B)2b-`656Yq1xkDD%f5WxdsG+L+zd zyV~1br2j|O<2bAKiHPZ-&+C!Qp(Ou45z#*+Hth9ziFaAaqMr~y&ZY^KpmWb-W2yxW zS&+&6%%^_((4APRAWN3Q-i`6DZLHgEvB_%t_qlug!XC9`x6ChOfisYVxjfuZdsD!U z4@f?wN@f+D-i)hc2mUL{_SUu1#l*c=#Xm(vX6s1G1Qs!$JZ2y7zSLcxuosogwhSj@ zyhoqk;#1FCzfFE`7D*RliN=q4)7!xyS~iuWcVMaRfvViY1Mf{_5RJ9)?T0+kIa70f zP23~0s;L|PeEiwO3vAX1(+gUg=Qb;`IsUroZ%tXA>+wz^g$z9A{KUNYViRK8nc&dN zjJ(C+JOz_a+OvSKn9%Sropyq;cUxm(Y*rqt4UN6wZW(D#UH9ti`5u@1PvW;m030D0~N*DeB; zxxz=zlJk_%V|QIFeuL-w2EI8P-gA?z{LWXdw2%8?8)|G|Xkl+vKN->sc*1B=et4HLawZj-#jjNdQ@(ckV|%Vf2TR(twxjnH|cRls7O1Qzy#l(N3O#fu}A3V z!8_LAcbv>H7V8FS->;83QzVV|Ebsom`n`hsOdsMoH^}V53dBjQ(P#9nuXnu(Z@pKH z^9SG5C1`CYpTeq*OE#k!waCO3@nS7H@tD|K?!t!`N`Aubk8r1@u2j>s%;)7?PmsZb zR&oLq?>To|2XA@U^W~TK&+$YfWE5d_NLxr$X{+?Hn7N*H-c90OOFrtUoHAwI+Q9sZ zMsmv^-{U*}6Tuu}1*+2bkAgAmO9A)XkZf*etI1NFh@Si(UmO?byg_oqNieP1&bFeD zFIbG$^y4Wjbc((#3HI=-OM*8<*W+2MxCm=}klGb%c>2XMfHOe}_HP;OyBd^=9H#Hx zWezU|!;&xB^N>0Hz-Rvpy2++<-O+%T;3>2C{OMM(8yi(WM1Ual_p{I2p_*NJoF`!d z+4S1Aqxp%T6#G_7uDx1hnt>NMEUxb>-zo3A&%&x+XLYUwwM7^iWzOdkyX2ZL_G4d4z$#2D58jjk%N%{y93Jy7KlZ9! zUSz$)xs9txK$=KDS<4K3%6zEBcrsmteawPyc$SnOcg@>0F1xzcne6#_62v-N^_R?z z4S#lvO}Q$FDmzh}}R)eN6^AI|(s z2thq7(Ug@9pS(8@SytxQ6#9?@MwuC6I##9{g)BV=!M|h=zx5=wS@%{@(c&y~Pq^bF zF#TI}?jiSV4o~<}1SV%Cci(#35CwhQlTD>*qge9|WbNL_nc#1q)PmA3Hn_J6BAC#9Jlj~- zWW4<-gs}>zM0E<9c$XL8`W@Md4nC{EZWK`OHp4S-3?kOAIaKKw8?gpcvW`s8p_>~q zMQ0(j@6k4X%Gn9w$*TR##1AJ^929@2#ck~(XMyNq5cK(8QNz8eBf@^}wxW&CU8k}W z4Sw|0Q^ons$>qy5{D#QuGZy}&C?vFz+Y*&j0PGFAie4AY>MTMYZH(1i;65|u*9YxQG5R_~T$JFQ zOOxng^gpMng7V^^5c#+sRMY)9l$4!@vxMB*HmeomTXxB{KjYcb$vs9}wXm}GH-2XV z-!j=Me9E8B;y==PyFCG3F?f>aJ)CS4q>aRB^%RyU_Z^I;BTXx6H}aCy5;U=&Jzp;? zD`=Nnv*9nw9LBNp&j$OZTeFc*yo&-W?|Rm+b}ia62cby)E~a^O1d@{Ihpp+CLD|J_U4U-3*s*w>G&%j@#6 z(vYBVs<$&bS=o8G%){`}yY)#WU z>qBm+((^K2?HwFJZd~9=vOCE9*#hdYySmpRcV3GZe^T{qK^Wh=eCZMl-)}6}KQv+; zz96fUx7O46KI$~nMqf}_`yr+|CHkr=oYdIDZ}da$!-cFM`4i69whm$I`I>MLdr9nZ`cQ$z9wVYJ%!{_5TTNvx3tcO{EGaE3`vW_; zO`MXQ1eZ7OwjDg}1f2|gqZ(=a5n@n;UClxwA5?#^7CYS9(V{RTCF`}OMtx(zq$ z%}-GsSY5?n-jq_&?JAg>=@)z_nlWWdv~je9UV%+23rDAv(K(ntn!uZVp!(!O^lId= zUfwr#sTE22Ix;Dy~RI%$j^Rja=|f7!A;*BC(9`&s_7&1UCk!_8DytPdqe^E zk-2ovDX2g%1J`QCQVql`9wT-Cu-V-)^G8(nEE26g;O-N7yaqVMv!bqwY{?_~si%6= zMr5XkTD<3@GmgU>}L`F?Ko-x;D+txhQ``4>O3Ol;QB%GHeAqWzEX3@=-O zT4eXIX&NERnuE7K!VfQH&x;THMi2c7HA}a=S*UB1Jm*%u>=X3meWEvI zsHYy{Z(Y)!8)3I zK)cStVZOJ^Z;7L`$bLqP2g2y3CF|1)v#}S>vP2%0icH;>89cQ(3e~F%)LTk5I$|?GrS7u&^>e}%1s^9p-ia4?T^s+wB@f;1TtOS09wutFM}@p|5+Tbn9SxZoIq7BH0X7x!Yy&gfM8eRW&B+up7CI?o1~;; zlW##g!;|Gc2!7{Nukm;{gU|f*OJ1=tpITd-707_o!dAD5P*Y_0>v)Ozyv`eTGcIn3 ziGjM()}HvwVotfgPWWpGYM>GWBP_%gNQx(7pNes|K6#v#9tZJMs`u@RR&M zwa@Ca&Xsu9IBuXg>lID_s)f0K)05SqOHbO_F%Y>k!Ex`fj{mIaIdb~eEn;O#xYG9`tP3RVhTQnC;C`PxK%akrQk|5KR7n;W`+iL}%wZKZEeLXomij}X zHb7N6iLve%1&L^|q~YwpvhMdG{HFma8At-tS({Eif6VIEBwZWH(pylM<1pJ1q-qmn zu!Xl@isOsQ5>ANaOe&MnHx@7N#bo~O9$U!b?%;GXf3OtlT8Q-v>-qmo))H?uwdboy zR()|{Evqss$fAm+sLcBx++0Je($~EPKp?v4vUyO9e>)LR%rK?8lvQ~gLRZ4X!rEpa z9!sRb6W?v-Xkpl5C1(=PHpjXtPN*E#X_<+)<)CeiWnV3W43LVy{bXBWkUXpmYTL(wmk?4h43f%2vVhYTmkNM^YF=`p?(#s|^4uRMWNvud*aJofH z^Gcgyu)a_H0*l*W#_)ZKtmdsg>XeFMiTNhtE``|LOymn{!0^(Tsk+qs)Fz1&ripJ( zd}H2bN8g)hYIFmfRwW$5@%UY4a5hY&#ue_5e;Xf+;~N~`884prDBi^c*-^f2UaRTn zLA+c%nm7>OjR~9|Uu?qm{qc-gj=bSnWu{O+nYi2A=X4hEn)p~V)k`H-#ao#|n~Xi= z8J_X1jZ8Pb$Fo$!ahx@4`Y+dP7%yp7`)z;vB=(uphgZgX$0s^bw_M^?Eaev_ua7mI z{a9>Lyi~l9nX^^Qr+hj-HGVd>I6leKMxc=`F`8RqIpQ5-L*vhzt=ukA(oZtRH^wgB zncm(G^YVS{v)E*BFc}ki-fwq~kBa>oTNJ-&&h7Mg{rI$4`FJn0m!C7uxuJi*j;DC4 zB__uI7poP|gO^F?-9L1{uHHRYVo@TMdAcj(tK&!F`AoQNh^cusJ~;7-HM^atY!Y+M z#N+W3@jcE_zF>}bdMh!}lRcc+VupS1#2e=Cw#Es)VmA3=tZg@QHoq_hwWyU^pD1J2 zZ`7`2w43wde`Arl!e$#Jez3~_i+7H9htEzj!#A}(>kP%s=M833H34D~N8fhMH_uyl@{;nyjU)%W~FsUCoZ*779Vuj(zFPgvp6yCK@Vu?TP zO1$Z*(~{O6iMgh-K5oMAEpoFOqddnPczXhO4xIiri}sXo;@txzC{?0Oe4F2XC;n<8 zfqy<5&u-3kMJwLkT=Tc%A1A(y{~Iq(%9i7{Cy}v{80>%JJ+WzD+WV=o=4ND%Gnsh4 zssE40bK1>A<{W<#OOsgWT;?0GO7WSoU9rxw9%SgJScrlfC+WifO1 zxQX2DW7%WXOjR!z`#Cn=i2}!O7mAIFZM|JAmNTA(Yz&G068kH5*6-aDpBL*HYZD(4 zTN67Mdm#QnY_78b_QZO`8pqbh=Et6i9gZy{clpc%Peopb#9oSbjrDQ=hW2V3_Ad+R zT@}9+>u2I^FPiX)e@DXLGsHi}6;>iI=j?EjCQOfSjURBjbR}|?*Sy@HefoI3ZX%C) z$2py0v(+@`Hl*weJa&ut^Q5FBw7<9YtQG&vI@ODRYU=rqrbB-h|1Z8?l|Ug9e^26e z{QLNgczx1W(~Q{X$>mq}uab$rOjY^o~D^LPBFDuMxc$S2GuKO8Sl z!gD!Mr>u&D_wDBVL>4Uj4f6Y$sl7W$?LOyWN6c0(Y@gf4e_&Y#kjZh0BlN!wxd@!! zkTn*&lbE!DOzy@3teC6THQ)L=n@=8ewmjCz`RrJ=B}5H5C%&=%_gjr?@uG>No}*TLs(YPR53woSfy80e6SwTjMc*4@ zS8v3#dBd4@^P+!BB#yDGJ4x*D#IvM0b>iFjOl$Zweg7xX+&d0VY;k5pK5N__8eTFO zV&3@(_I??goSB_yXywoO_AFE2t2)d5Drp#RjVH3F_t4yNP7X+u$i`~q#4K0yZvV#H zJE1*KVkvuGACFwsYX2H<=@h92EXp93XT5uTZ36JaJkl^LmBwyN#r>?{fj+RuzbDd~ zgPkpSmR)M89w%*ZCVqGDAUjoAg-L2>0sUi+b}83+ij<^RbJEv4U9}Il;|Jp}k*uZk z_$yZH>BJxLLw@VoAi?TAnHUfsNN #V&R!zxR8Bw;YU-+GTF}|ID=S;hcnC=AOSr z5;nvKm`__h_*xaq4X(s`_(R5y3vI8?e3~Lok1I*8?$tqDdw@Wx-Vn}gT?VVp)vPjlpgo*F6)pLr?o3kDIkaZ8wxftIUT}w4Civo zsuuOr;;_|qqPefI44bht^;Pt|A_qMVeJ!0F?J2&1rPmfCcNQ5om93l?!=A)sofkW9 z462F?!&6B%`CD4q>@sXtFRV;dE>PCf?DpgXFrmvJx2F2yjP_xwcH?JaFs2vOrX&+P z#jlMCb)A}k#9Df%p=w) z5F2(=ZxF-yl=eHdz2O2`%{1|2Il0rzIJwNC?fvko7S<)7&nG3T$g-#Ux4p>!R8SqS z@jTwwne-z0HtzGXi1icC)K?5%ReV2~Y!;S(41~1p3fkZjuZqF%lWlCW9-XaZF;{w4 z=5`7Wxda2_+}`9UcyBKi1WjcoVJ%T*k$>S}kNs$6Uyobo?{Vhc@nwJb|4mu{ayd;W zYuz65dCc>53bv>@8w4$=iC?Lo{Mz5|vCj`fMViZ1(qhyKVSEk+IUss>+vi&{fz76Gw*jt7@%pb~^ z%fj-TBzp%p?8H=Z*~wk+mRZg8NiO*!B#op!YVEQ@bRSQqCyRru@-r;KUM%-AJKQtq zg0XowsHplSue|UDzga*=o+$j>U$dkfxs zHp?T@;dx&y)|m%8SalZ!aOdp-~HtH6QPQ4+37{_reFMRnEyWD zEnblG7k1Z{s&O(|k1H_aGBEGYlXIaE&7kTf{MK!({u+$eaM#{0`!C^{(s#2asca}_s1=nRB*0;y}_dCKB1(!eS5S*tYGyezctn*A>A?SHUZ`Jhv0 z%qk6KKI-(o{*bX3^q7^khCjk|+T!1~smUKidOucW)Eom-9j7+RI+TS%*0VyxV3a3h zu;cCMRx;4rD(te`J)qhRq9t@SXNeYteS|zpPfwmopK4X#x!3c*YMRvxerulRect}m zuo@Y%#0w)Iwh5P)Nsriv_@ve5_N2f#b=8rK z&mbtjunt+tLO$=7UcX9f*xxBLxL$#Yh78DU`dxqp$N=FxYh~v{1Y4O?IfLchpaL)t zX1_etI*WZA%F`FH*NsVB0PB1e&N{_fR)Di!A~Sm+sCgi~n;^FNRlWDNn+uaCto+wrPZxDKDSd$zJWOtWVyW80AJ5RFx^%Ue z72ORrzG1Gy6}V*{_4v<`&qtuD74QmI>~Q7CV!CU_nLo9TWRJcAseIRyl=DQT&3-Hu zO{(wTL}O+}%i8aKI)#=*--`@0tEDc?^|#127T|FcUSEQkE_N#3i_wD7e;U zcae>s*o$E>?sx3LEt;6wjJZoXWBWkwGyCo3R^V^*Ro2t0)z&x%OLdIi^ww3B$6bbb zqHM6)q9iY14KG-O4)$p-WUnyP?x5e@?5zf37;9i4m(kqvu+Z&n@m^B|)X=d9eMnL` z>3TC>H@gW>Gp$G$*xH+96B8H4g**H)_}a!Xyo)<7;$r^fk$374LHv%N*4g zwiE`r2in%b8$H3lG`H8UlCu`j+5nkeaaEN6?wp9tSY|WHoDdv#Bc`IHeA(5c&K7JoYR=W zlP`M8q|#09@trQrl_qPgG~f7&EAG=}T_n0CI)UV6bDr<;$Ov-+%a~?-nRTBXy%Gsb zl&s+=nex6(Yjsk$sbqqhw)u@9P%dGbn9zqy6q5 zebni~_j-nd7~S5{wJhc#lQl0ya;AL44yEyaChmH}b`bVjxVqiZ1(EJ%%x<=FC-j9z zQ+|m|^Sve}Joh#w?t>`iCFMi2=VG3$t^V?}kwwun(Jyp?SD#~90>xAjTf z@ANcFHHUpHNSm6QA-U7cq<`S&@3NeqB{#74^Vp@Xcer2klyomYN9y~rfIZov$9?}_ zmaH}1ya)*ox!0U*WP-)4;Qhn5oa0Zq@RO@p_@1oX4D89VJG0n+<-zu{G()V?YD~tD zq_u9!w~?A=rtXbAW><#l+uCC?Tsl(OE^>EDBC;=h&57X`c{qTR`URRo1EO* zw87K%?0@!tuPL0hqXX_tQY>V)X0B9cqQgxE{5CqwG{Q$r3f_}4!bIExDLtYKXy-W- z270E9F%>o$shIMQGp@gk4l$8)lc~)yeDB8Saub>J-I>nV-h{?XDO;m|(T2_Rw{$c= zX{t!BJ4bV-lt@`)>h;s6efNqEjF!a-KjI0ivw|D3KJ%>oHD03xeRvB4{VPfDN50aL z+WVuGOb*CHf)aNVyOXi4{VoQIC^k8`wv#D>f z!6Qi5c~e3w;H#RN(%)Zf`6D0um3<2*bl+tXZSm+_5nej-znvHA$q(Pfhs@-+)~Mpy z=!w54?~kzQbJT3yOQ&X$;D>D zyZc1Judo4`qb5j2M_8qZPd~RRpQ&n!d$O(k$&1#n2c~g7@0vwioE@J$M!r!}FZeL4 zdn)-8wtummd&Djbu^(YA&oKUKm8hzwnw4Mhr)_Ym6}(+vxloGVY>F?spD+7e4pIWE zG)FG8MTf!(^}S!p(YnZQOZ8NS%u0c`nCSWyN!cw?BVU&sf+66+hPgNekSg;tfl!Uu~E9laV()fzk>bE@1Id@Q$ZH8;2oa2w)*#b*@}f?;o~Ni zWD(bHGAcGuo zv$vS07C$3is|t3gw(gj6*rpe0f$BE7cV<;xAFK5GM6NYPhWjr3;}P{r@5`C@sC9lo zUFv&qpl<3h=fg~1O175c-R0YV<9c$aD18T0Rl>^T^8^d6^3!^Fs>rE73dYJ<=gP>N zVWNu3Z!62BdAA83GZ3 zk$k7C>t$8xV_+BAU=6x>T<@vUj_ro#RaSW5QO7>7|H$W1DeWACwiW_s&b!Iad-w&6j2LW zNA3De2u%;Y7Gq#Lml7j`pHvQ>3rv9Wyx}CH*)XS{y=z3C+zqZ)-0qzAPJgIdUH~b3 zLZy4R0Cx)A*%N#NR~@NerK(;rSWD0eFHkRNi#6*A-O3YeN;ta_-jx%clq)C-gZ)US zOKRVK!8wLU5^bG@qN%-RnFw42{-Z9vN ztj>NP3#onsXZIrhtcri5D&t4ObsvV@E`g-B3VuqAb?r*Q6r9J`i7RmG`#jAQyxFG^ z&$7Nb)jhuPJC8uC2SJo4z&5YBcLtbwWuHue)#k?fq{1@wgnDO8)WucY=XARn5b1UP zeKbBzKV4a;(~W`BKkTf~znw;W8iITjYp^L+H=c~QkFSlTj(3OicZQR9iH~q9eQl?Q zriLr;!~1MrX-f*BTB&9{}(?FeP09@|IJ$F zN_-sOqko|lwrQ*D&W(4r|1ZJxU&j#aa$00VXH}+y!5@$JNu0v7oyT^41)IG~hr`>t z2&a?KY`Xtu=ofqlD*aph$)IuKSzUw8Fc_Vk^I9_T80qTl9*-tY+vlI--4b^>cdudM zJ&eHgctP^k4STlS6D`9C*2iYtbY}IRWaLCIhGyq!g+&#pvhnAaeUP?Tp+XGliyOhpoWw4EG)+;`dSQX z-Z{wPw8LIdt8Hp#S2?SFi29H@&b@1dIe8{=50wA)0DBbdaSvRu_k0hxF)on_V>tjj zcr5-pN&g(f^h^9O?x9qofp1=jSEm_CE8Z*dDK6<*PkYGU7LwX~60=BaJ34g&kGsQL zJrb{wNFSuLTK(dQcwt(T0sH&8Gjl_U929Skm$-!qIvGEK?fQs@oY6D4I-U)uHC!iL zF^p_R+|ko`+A<`31771&EGNF>OPcVlJ66KdbhDE;o%4MQGqc-MK9_h0GcgELo6DZr z8~YksfYMgA4*O8u^Jm3hEWy4Ez?0NT6bicHFW>VfO%nxm##9a##V6ypt2x_yEvqvG zLsAWSC;p|Y{Oz~?W0yWjd{6RbLioS5S9960 zpX}B~Jk3jfwn%TEQ)`{@`h{Mfg-CHNpRQ&mR2ha&v?aHlSgu8`HfJl(Ybx*U{Iomvt$ zwpBDcM>Wn|-#(!_t%Inh4@SU*ZJGNZRV6cn9T5MpZ#1_n=v`BblG(R*2fw=84PN(Y zH7+YL3JIQXir9JrEC}nsM=pUnhf@iAsFpe9iB7v$J{3-??BM#1L~b?JzBlxAYx&!v zranBdu{51J(3) zrR;10G3VcqnGNcsc3Q8WJ^2&X>Loo=&&YDOLBYqXBGNMe_xjXMoD)?(C{peOjrjw< zKTdS`nA+eGVyIlu(s%9NqjvO5weaOd(=+VAOVGj6^3MF~&coR$1>~w3RVF?KK?`Kk z?O}DBWJ~Eqrm5VeE>0m^SQi>JgOTNvIk$jIw3EFK7w@N0OT8m}3+Ut=`G0u6+GY`I zjGX6InR|s)JS!gyyQqJK%vFX`)`0?iEgRdW%KB?hT+Z8W!Df6a`z;UC+%1wX2}d&1 zj&y$y(|;H$Fh@pLNv@m`Z}E-#>M1g}filDvGVUv?=r=&`nnTg%z{od|`4#fKdu1Dg zp$jJ{^~b2q?Fni8KnB+e>hOZ=+#?(L2L{_4 zZhcsO`6q;OD-?B-Gw2qY)-k~KJ~u=BaB>$UesVOEtn4@(=*eigl%dfenKgP6no|r? zR4`gcmRK_Kw$FFTw)R4zvOr}1b>2bqXk`f1QK)n?HTt59=af4?rS7qb6AzB4 zq3##yEKe&3P0Q|li4ePaIOSsGB3$aN=z8aJG?t;Sh}4z0Uv~OYxs)mDu-iL(Ylf2@ zo=M38Q9I<+j493&xZi03=~Bu#52axA=V(o*ZnQ|*>sjVOc$&dj?{ls~rL=wP7o*Imo$btn4zB#UxBkvQRa2JAT4zPC znp0TY`W%q)KIJrnuc4|RM_%{sP7ubJOmx4x;k}WfDZ8PAB_U2Xn=zsqIcj#Q8CjlL4CXToG9NMZ&UMr!l8 z->?rYp^5#~Ms9>)Ws5F#*9mg_6nxI-(3kV>GT%-=VYNPlP~IE;-5CYNUEw$9f~BOK zk_#V)LoI|{w2sb#e6FPz?~{t&a`21(e-J*MCi*J$qZ6)S3}o}9jBqF{YLVLLe6BYH z5>(vNf9&tM;fo{T8P~m8k~ThSUEfjN*v|jM9Q_mWp2q)+`_@90zL{Z8%jx?~&;K3- zVV}E9#_Noiv;IzQ@AAY?dczFS_vuJ~nCp3{)~mkr1qrGXjmY^kzy^E5gI*-HWugzj zMXP!C-RgmVGLLe$?EmTLe@;nz5YD>FnG;p@4pfBt{A#y;jy_DD55v;lhHU-{G1_4b z5WA!!X}L72XG*CcGK2t+7w4$@*c+x5t|RzGb@IXk~6`Z$E-zexI&C!@P)??x*! z-}UB4`jWosCSd;0_e({??uHsBSti+pT$q=F(I4PvBfY^EXv!DtR&{c?4~7t6_gX>h zzGY>qMq5~$!cdY45ZXEL^o+2KeKPPA*wS$rQ=D97@@Z|@U_ES4bKmL4#uaCSI@*W1 zDy}=Tob~mke2QyXY!9wNDNN(W9`4m0H~@}d>Z6?)!nXFsPws>qt-z1XXZ^B39qYh1 z7xVEytGb=b7JUW1*}xZk16izz;rZ5;LI!pPk5C0?QX4j(n`+LF-_<2z2K%SWe1PGeTDnG0~A z@8yNc#puqj5VdXIw;X@`yq=ynxjiuG28A_I;UuFUUW=si*&S^#T1=E6?$#QM_Rh z*g^*JMQ?n^-T3G(Sm-x{Csn{^7Pkyd?$DL~x;!B%CR~oaUcQ;PsP>xM#Tw*#49I*1)eFpIPhrD&gbhy7dh

SOSYJfMsp~ z(QC(s4J6CovQDQ>-hGfO0r}b1h*_}`DQhn)Q3Aktq zPZFNP8Y5A!kgiFd_I3PT4ZC^JpB@&c+)Y={h;P3mu~qHyM3Kw8b~+|n_}0{g8)k~G zwUg<@V{iM(7vha2)@y}s?5bj|8uo2GR4<<$uc<~NryY3;w)6@d=5=^bR&3}oh|F)K z^%2p{y{c?}gw{MMhQ6dm>K`>a7*o5`OvE`y3|YptUbkaSRd1YwVx5Fc<+oR5M72(_ zQF~Uz(-kE#Z`wmAt&2AEm=o1dFZj*iY;ai(O-1^4K^**n?=FBAR&uYTICLgioTmaM z2Y$7%>kbV*R;_abBKHzhY9S2nU%i-5C2P3v32(5Uq&?zkGbGE)LK|OLm_ z+S~0CU8nIIRXqPAFvH$edv32WWIu=UC8?%`~R*I;Y!st$V9 zJLgne@{YG#2xa@io=yvDTkqkXw5!gJC#}~etNR_4^_m>*JHOq>dmWcEEV9$Dx_4J- z?2oY1(U4A4lhn_g(A^N8g7AG%2>b6G07&QoYca%oPLpfgR`c~DIgMcI|CN;;kO@5R zSsTia`jOAq@E4`@2ekK_>-=6;6*~v-bQ+~|9~lYxtRmmMqT;Tf3aOLHlGb6FF8Hwj zevS921C_3yob8=5(1`c_PKv#&X$6kS`<^EY;aLl}?C%G`e~{Nl{QX|*+M4XPq#Z>) zPiH=*r9O(gWNJIipUT6Fv>_8eT94as@t;}0FQBHGTyun4zoDvn+CVZ#$+JvQC12g# z=ZJdt5x!dwBKofkyORNjF$k9j3o$m^cs&zbLU`uBecNyeyf62s0=nH5O zT!$d;P(xcpRpBkT$bmZu!NJ5(lMMb;i#bCNK%K;7HPp9UZM!SK53Olt-rY_J>AQ(C z!L`IHzd7DFGCK|C9{K!-z7=u5DEzY*e0Yhgv{6I2NHAX#w=L}vVVj{_?sTw&9`)9;oi}eyAqR-+D>h!kJ)M8a65Y!*~%f$zsWx3p`Q&% zXCD&r7wqB#2y|}j#@!_6fA;vJpoUEU4{wkKo?VF67ll#$!iT*t@BJTX`AW6t@n9dD z(Ife-zda(K4d-u_p~(pv`?zaIJ@+bEaQizM`Ypea)?0<>-v`0V$z3%5-QWrGnU_ZV z?5fR!R-Wgfv-Zw-1y9ImjAgk~RE8Ex|zX^UY zf#h?uk?JH5lKCwv5!bT_+tq!pqx)sq<$BnRf%NzR$VJ@UkLdHimH0~~;zQQ!J!`U@ zwFsG%htw&?z3DZTs@otu=UjUV?r053*9j=5DX0f@4$Isj_?)iJRwepZaK`!_gBIj= zp95B40^Z|Y+Wwb!|DLtX$Ci9+9#2+JHqtc@c*+lX_;uuDjn1*_K`p#ULH=NWVyWuV zG|5V=;h6xF!`e?H2m3KF%RSv>?>)s6Zf9YR`co#1!D@GS#Ey4KrlPN3`ScB}$!R#{ z|Jb|HzSUY*ewuIWf+sp-$IhtKOF*Cg)Sq*R-R#YeJ?NR1v(vxw3gKjsG|B7kHQbud zV~MMXfwt>}dNKIT`qw5ixBYG<{__@Fx&bTk6UnHTR&~6)6S-KEH`$G(YUf$-&(5^GCnR-+ zitVNG>4~&KZjnKVs^^`y?T{V4B zN=0-iK4af(yBsyBhB4xRTD;i8*11QleAvjJ|-JaP3q< zUw5tPiubb&O|dOGVWAnkwbOH~buBpY55eNZauva)NN^#x;8R#(IK5}HoxDMRLKJ8t z>3t|Ujz-)UG-12f@r}29T8s?dO+%{Dx&HL#RWkiF9UVwg8U)YtXBSx4xE*ZBOKmg5 zqAu(6Ly!^X*4*@~dZNjDuyS40w9a;q`R+R-cmP`#rJ>_VQ4Mxyt*5I^M-IAb_}MZ~ z@ji|!pXe#hw@l=D&+;{G+$S}ZF-`JejNdAgn~sPs z6=X(Jyk9u$=~0o$SiW#Si_zFB)MXJKB&Qoy89zz>p3*P*H#yl#3XZ#fPEy{~-_Nst z=I464Wx;N%lajpQrwv)O(KM_&4_`F7ip;h0rdh-&+r=vj$<21Fznq6Dn@ks+qGvPR ztv5ci3BNQSv-z#(7*AWelIf-D(s%my!Nh~USC=)f?urRAx{CgtP^Y?rcD~B)bocF# z{J)F)-3R-dMq@@>`CrKVH2-~CJhWbK+Bz#cC7Fu_SR`KA=Nmt>FS~u~GtaS=JzdRn zE|n|Pq+J7PQxDAFXc}57`Ifk?3d_@hMvS*ghdf0K);(sw{$c^Q(b;|O&>(3#8yW6J zOLu$LZpr`1;7GjRY&&%epV7tlhVaH`6UUskxKEdAR{eK}#04{1jDRlhX89+G9zq6O znMd+0P1|Q5^Qm#}z&B0Owf87nIf50g$GT+>o`#@T5l39+^^ag3`?*4KvR#MG-cG`^ zz&%ZXc9*r@>x3(pNG{_~I@{xfC;Q6UPT_^t-0_*E$ay%cqYeKg!@VCzguP4$4!xDo%#R%-nb{ydnUOL3+f8?uI zV-mY2iU!x?t$Fqn@vZpBVeH3KxS!FwDRy9}3hL`<7T>I!qXg!0JzlYKJnS@?p;u~r z{1nFVL0r;Gzq{$H~WzEJ!8FhvO^d2RXm=oKn^y0!~FInJfEhtH-C)=-A?=)97u#G{FF}) z4{p+rt9LB&b}M<=`cAWduZcZ#iUC$z@jkrL5E>Lt!5_;S%oMLyO1{S%bq>ZRE{TUW z@wkKdt3PQ|+hhj&lU}ZrMPy$(Suwdt6#0bAVuJX8C8WF=S$LbKe|+cp>Og~g(A*Yx zuJ9>2Zoo^QCdKcOu^OU_bE1#DqKhp4G?^{AAU^y;MaFAv+pi>efAB7QR+-nyDXuNZ zCw5ZD@To}p44?EuvMmo$!@C{jV>39_Cr;vSvq0~Nd+$pA%&#wWmCOELFxibJ{t-;4 zVa@rbwZRVg_sim{Dx!*P;+8#p^aV#lY$+suJ zD?{0WELJM5b*ictW_(b9{mjam4&-^ZVFFv?F>CV|H{z=?LtX6pLNRAk67hFD2ad5E zHt`U4?gVBo?5t|3ALr+I6uY`n2ifns>w4q5Yb0*sQXfrxK)#;Q3-(_;vreJgyjkn` z#Ka4@$;{-aN4yCxu!P>IdoXI7+-sb_hb-zEY~l}?`{DSiH?Wsu#h`5ykC^2AvtHN2 zeCi(2@i1LSU+a?jKs?-pgpL&zA9L^i`jlS7Y~9c?bW11OblpKwtX>oB>ReZSna6G8 zx|c9=pU|9M?(-4OtYR=b5efblgXd;*3I-35>=UjyDsfQ%%_98T3OcZo7PJVyNVLYb zX2#4Nr{!04B0Z=(ET@03Ct_q^BFi?;4vf=}lAG*IB12EH)1Sy6&ym~BEXl^iO0qVd zEM*txwq{c+(2Sxolhf>XdeV?8`GP$z%BKA8-^;v6dY0sFanDj#a|ti;DrtF!E&7Gs z+UYLIL}~jRX1k5}k!{I6i8-v#)S$DN`E6dOxjQd)-NPc#i;4~oLFi2aI-SXPLy9+Z16@M#~hS`j|=GVj&fyZ;hY@Z zP!bLh=F8#i#u6;!OhD_M<(f66xgNWWY0T4ibJFjYFe z$OU|48L8a7#wCpT|;CKEtDl;x59^B}edIzhm?F>&fh5o)F6UYKPX~;WP z@-Cm;iQ%1=DZY)dQ>(po=*jF#8vWOaM?09_s^qUbMkZ)8tCQiF7wZ3=6W zPO(={LDyO*uUOlc@92FRUiV>7`94e2Q{0f%s-~9RR`vE>#IHCiGPKqH-zInYlhef* zEKM?iKr={zzW;Tp@m&!2&IX zCVXc%_sFTYh>ucx;&;gO5LRrK{ru71EMX@<_5Zo7pi?GEdJWcgs4S$0EB$C?vLy@n zo%cl`jgqxl$h~wSoZ&T*Z5w0%4|(RrEal7YHYnLqmS2e#94kurg?1cD=C^k{L>K3H zgWXnVr9G@77i^c@M(T56?mqGDBW!E7WJg{$kpGXeubE{N|IxU8?iJ3bxZCc&LC;PG zBa(wXeHFX2#{Vz6ZhIJW2T$E1@|(G6jiHfyWj=9l|0*2#lD|6{M*Zv(9_TM?Fadj) z3JSK&iro+Adjy;LrX799Do=$qPJ`JT!wVONdDMdJRZceY9^cU1XU(5jX)e%cb8b@P zVQYgq)~ONc9z<8$>;5o>9rj)r7TE=3-V8E58tz+C$3j^sP1rT@IE;RV-Fn-;HKv_e zA&NO7ui{v5%FbKpHp&1mebmnra=Kq3g2mzLnc)8oX zsR-@|&rhS)?$=RCx7&3n0fl>N=WAnSGYS`7XuicyWBat z1rKvZ-RMPgdwTiZrjYne`wPNt#7I2H{?NU~bp2NKKO%Uea6{PT3V=R2|m& z7)H8)mC3bT$pZe$ue{*mWg zY^Kx(*1Rn%u$cYtB2&x3GHj6%m-gnbdXDaDPQLh;;&h)-1@0{x@UT_*@(1#?Cq{Vqu-^&&)_)j^32P5M{=e! zYc1Kl9nQ8@ZyL@Bnq`JuQ4@dyIoP@68)T}x`B&+2Cy(NnI%Bp@Sk>Pl=1wCbXA|`G zoy1)^85j$hPRGPi688p<>ly5NTy^mZZ0Ba|O^Ikdi2fZ_*Sq_+Ee{8QR!6-@oz z(b0H|zUuggL_g3maS>B`SVePBGa8%gzDSQPUxW_1dsCuzPN8trEq#S6vj_Py59^h#FJv?lVToq z#n~{~)g$pg;dfLUA7Ncw7cY!UXpTC@QaScgpY2<_)z>Z z{2>;^d#NnO#mTWKhFCYQ!qm7V_KzR9cUhbsYQ_)4yYZROFE*#*Xy_aJLVG-o66G)T zp2)tTvu6&9x5XanvqBw`xKIsB=|$?t2{CJg*M!p)8f!32-ipQ4B@N=A;#;9={81|^ z$5WwiJP|f|ejaVfa(aSJacpcu71BNqjfcXLcpwaqZNrp!b^J@*7J8O@HS~;U!f#{6 zxJ9{h!o%KJxiOpy>*D-yMXVIR#&=l~u8noV87!F@=Gjx&B#YGH&+PKQ%*icbFD^rw zta@KjH7paEJY^QW!&}tB(Aa9t?aY(R#x7{ZYsj={hEy_2Yv^|-_^ca_-1Ao1HsL#b zxTi!#x9~W{yxikPb)HY^^FmuZ?d_x|UcfVABoLo8!CQ z*%{vA0Wai3Z)eY4wR0d}u!R>bcD0np7(#K9$II5^HFnZvO|d7)!{*qmwYeOa8_d1q ztYHE5)JVOR%iz`(zvE6itIg)|b#z;eLzYk3uPu++Tl5c$wEvi%tgHu~;+KE*-G=EF?{@^0-ytIZUV}$)V(wkshM;$C4@fydQ?ia%`U(m@!q=V4C)} zw!$l1KE$#h!f+TRa-R z^xcbMb2ir_-prT1!bbN{t~JAQs*jU0I?S|MzNUs}c(fub&k7@25NE_bp;bIT{*p9c zOV7k9p*zh}hd40QiCw9pPK7eHy@z7%J*=4pp$`Vjw(xfhsX~_2HTJ~4+7~9$SarsR zX%^S8neuTwb=RliF3hKY@$3b0TI_<+RW6QTL-k`Pe5nTUzF3S2GhK~0#OkqSEQ!07 zx14@!RJbYb!jpMEoYejhM}`VK!4WF4&(+{5ETo~KIV)@%Z;8jOzaJ-8TVXDWqgmH@ zyUl@^K7IV@fYno)Of^%+B**mAo$-6K^m?Vt_vAKwO_9DG=-xHjmLCrrfx0aDG&R+5 ztNLwWg&A3(<%P6nuV8oWwen7~D>z}?-U&aj-TSSdON?%5xL=<%h;LZo7V$qgH%2z6 z5?!182H&T@I(;6l42Se)F&@}(PhMcfna#Ev(`UY+9!L3ui8v$ejr?k=vVRzIr{!;<>i|P zgZ-_6cQw?NwOD5JaP1QRe=O|N=bfyqw`fD>AUiE~;MBO0;&Ws2t#%|@@=$n{KUwT= z#V~^%jWk}(*<@WDsQqQ-!xOw+Z=A-%&Jn*e0!w+3nMnxO2Jfro-Ae3errQ=%gv4= zs>A|wt0er)>RQFY`hLFIdX;%n$PV(bFIOZJ-PsMxYrCE0*<_iOZ>$=&!piF%dh%5d zV*VBS>_OvugVD~nAC4 zPYSo1I~#fQ+pSP1l7HI;UV{&}ia%XMN7~wWPs8WwO+R?rr}M&-POo!uAZxMMwz)mJ zoDuWfa|bS6U!J{$Ki}giUHSj2;bCWhLadzmcse3HNx~gU#zS<3j@i2aWp{TZ6u zSCWg&>|D<6RaUinW_ufZ_+ePcIVIp&nSckw7Fs#SRI?|TON-Y&dD(qC?IcUENk-!c z|J#XowNu^zJiI6E9k0N0KH&7W%4v9^^THl{_k~WArSy20Ds`|^aYcK(eK>Z#ox>h< zjvNj_dS4vwJZ!VMPJ}-=cfVIQ0*mZL_=4v^UM4tGZ^FLY?VL9oF4xjJdP44j|8~y1 z8vj|o9h%iT^!IO;O_u-jeF}in-YQyz8Caq=!=2&QLQGnC2m1ALMgIanDRbKU#GYn5 z=Gt5}9RRIpZI#N`Yd7!*J*`vU!J!VoN=ls@dpc#m>5d7`dB^ojf4LT#8pld{sjJ>? zL`!g!w`puu*JUDkmDIen>}A-+PfpXNPQUNrFgA74T5n)m_Ca}9pRN3elw z#3}kZhu>?Ar$M_G!|<9x{pLXt9)TeK4elmF22ouK7p(-d{7Ah0R~&)coV!`{YKeK^zuP??N*5E6jA>nkk0S3RrJW6+4pe-KEq;a0h##*_Iozl3&y_* z;?!NuibNidVM9z6uh{^*{R-B+Uu-G&=G89RHciCnM{(a`QG{F`)q9OVTRe<_8@9l# zt*Vw!LON?fGPhD}bt=oT87|kl$q?eZjO!BCJzM;EC%h;Zv&}IjhC{OJ=!;MEV>=eG z9a`K2IzAUpI2P|@hZx=CuvV8bB zG6%9X<>Vi84oB+esm10+bJpY?5=LzXEP6?{TUkEb{|uzOpSakqW$V;D_cr(aVhdIM zHbN;oJ@EqLxK)iOWt-}WuSHrv79F_TX}Pf;oX1vrsqZKHY7AxY2oaQ&Sav>~ueLjn z=z%dl$zl3miI|Vnrj*|=V1vb4vl2_7uU@I3t~RTJ{r7o6T!*k!>E6Q+Q+4O7^INW+zewrr`F0WT^*CSKGWD~4&Hl$S)37UE!1C%zbG8=uW3tSc zSBYQE#vN-U23KE3*DCa6<1!1xbxY++9WGW^DC6SGl#~lad#n5O%k)XVza-n=t#phH zWZFC~Yvh%gf%K%ay}hxP9DIjUYf?k0H4`}+|Cat)zQ*n{m=@D0wn!I>)pe2$vosRNk8|mpMSMcPW+Y0-C$a z8Na5_iVOaeu5YlX4N9M%IgnbLE)cbw;@y%vXi%%9e@VaVuJz)LyHcCdUFD+uCY_g= zNY6SXJu9`*(>tj1Ao(u$rM^$yk!~wTX&0)&QR%;=FH0YEr9rwRy+1WUmd$5VTT-di z52^O)?Wz3qx2YQRpdHeQsQBfSr{ncfzEVcfhi*;PN#B<$O4p?+Oy~?ZiG-J>x~7kN z&*fwpLPt>HHWkrams%!+;u$N@$ z?34LMw&K0GK!3&m8STxV|0}AwI&)*T3FY8Yb4#=s+b17;v5Wt2$UKl8Dqh+_U5coh zZ^3vRk{N+NG+xf&>UeHD%*ZO@vAH+0Hl@S^?Xr{f(jD== zp35d>zZJ7vBz{>W%JL{iMP v; + gu_split("Giada is cool", " ", &v); + REQUIRE(v.size() == 3); + REQUIRE(v.at(0) == "Giada"); + REQUIRE(v.at(1) == "is"); + REQUIRE(v.at(2) == "cool"); +} diff --git a/tests/wave.cpp b/tests/wave.cpp new file mode 100644 index 0000000..930e7fa --- /dev/null +++ b/tests/wave.cpp @@ -0,0 +1,45 @@ +#include "../src/core/wave.h" +#include "catch.hpp" + + +using std::string; + + +#define G_SAMPLE_RATE 44100 +#define G_BUFFER_SIZE 4096 + + +TEST_CASE("Test Wave class") +{ + Wave w1; + + SECTION("test read & write") + { + REQUIRE(w1.open("tests/resources/test.wav") == 1); + REQUIRE(w1.readData() == 1); + REQUIRE(w1.rate() == 44100); + REQUIRE(w1.channels() == 1); + REQUIRE(w1.basename() == "test"); + REQUIRE(w1.extension() == "wav"); + REQUIRE(w1.writeData("test-write.wav") == true); + } + + SECTION("test copy constructor") + { + Wave w2(w1); + REQUIRE(w2.size == w1.size); + REQUIRE(w2.isLogical == true); + //REQUIRE(w2.rate() == 44100); // WHAT THE FUCK??? + REQUIRE(w2.channels() == 1); + REQUIRE(w2.writeData("test-write.wav") == true); + } + + SECTION("test rec") + { + Wave w3; + REQUIRE(w3.allocEmpty(G_BUFFER_SIZE, G_SAMPLE_RATE) == 1); + REQUIRE(w3.rate() == G_SAMPLE_RATE); + REQUIRE(w3.channels() == 2); + REQUIRE(w3.writeData("test-write.wav") == true); + } +} -- 2.30.2