New upstream version 0.19.1+ds1
authorIOhannes m zmölnig (Debian/GNU) <umlaeute@debian.org>
Thu, 16 Dec 2021 15:48:47 +0000 (16:48 +0100)
committerIOhannes m zmölnig (Debian/GNU) <umlaeute@debian.org>
Thu, 16 Dec 2021 15:48:47 +0000 (16:48 +0100)
274 files changed:
CMakeLists.txt
ChangeLog
README.md
extras/com.giadamusic.Giada.desktop
src/core/action.h [deleted file]
src/core/actions/action.h [new file with mode: 0644]
src/core/actions/actionRecorder.cpp [new file with mode: 0644]
src/core/actions/actionRecorder.h [new file with mode: 0644]
src/core/actions/actions.cpp [new file with mode: 0644]
src/core/actions/actions.h [new file with mode: 0644]
src/core/audioBuffer.cpp [deleted file]
src/core/audioBuffer.h [deleted file]
src/core/channels/audioReceiver.cpp
src/core/channels/audioReceiver.h
src/core/channels/channel.cpp
src/core/channels/channel.h
src/core/channels/channelManager.cpp
src/core/channels/channelManager.h
src/core/channels/midiActionRecorder.cpp
src/core/channels/midiActionRecorder.h
src/core/channels/midiController.cpp
src/core/channels/midiController.h
src/core/channels/midiLearner.cpp
src/core/channels/midiLearner.h
src/core/channels/midiLighter.cpp
src/core/channels/midiLighter.h
src/core/channels/midiReceiver.cpp
src/core/channels/midiReceiver.h
src/core/channels/midiSender.cpp
src/core/channels/midiSender.h
src/core/channels/sampleActionRecorder.cpp
src/core/channels/sampleActionRecorder.h
src/core/channels/sampleAdvancer.cpp
src/core/channels/sampleAdvancer.h
src/core/channels/samplePlayer.cpp
src/core/channels/samplePlayer.h
src/core/channels/sampleReactor.cpp
src/core/channels/sampleReactor.h
src/core/channels/waveReader.cpp
src/core/channels/waveReader.h
src/core/clock.cpp [deleted file]
src/core/clock.h [deleted file]
src/core/conf.cpp
src/core/conf.h
src/core/const.h
src/core/engine.cpp [new file with mode: 0644]
src/core/engine.h [new file with mode: 0644]
src/core/eventDispatcher.cpp
src/core/eventDispatcher.h
src/core/graphics.cpp
src/core/graphics.h
src/core/idManager.cpp
src/core/idManager.h
src/core/init.cpp
src/core/init.h
src/core/jackTransport.cpp [new file with mode: 0644]
src/core/jackTransport.h [new file with mode: 0644]
src/core/kernelAudio.cpp
src/core/kernelAudio.h
src/core/kernelMidi.cpp
src/core/kernelMidi.h
src/core/metronome.cpp
src/core/metronome.h
src/core/midiDispatcher.cpp
src/core/midiDispatcher.h
src/core/midiEvent.cpp
src/core/midiEvent.h
src/core/midiMapConf.cpp [deleted file]
src/core/midiMapConf.h [deleted file]
src/core/midiMapper.cpp [new file with mode: 0644]
src/core/midiMapper.h [new file with mode: 0644]
src/core/mixer.cpp
src/core/mixer.h
src/core/mixerHandler.cpp
src/core/mixerHandler.h
src/core/model/mixer.cpp [new file with mode: 0644]
src/core/model/mixer.h [new file with mode: 0644]
src/core/model/model.cpp
src/core/model/model.h
src/core/model/recorder.cpp [new file with mode: 0644]
src/core/model/recorder.h [new file with mode: 0644]
src/core/model/sequencer.cpp [new file with mode: 0644]
src/core/model/sequencer.h [new file with mode: 0644]
src/core/model/storage.cpp
src/core/model/storage.h
src/core/patch.cpp
src/core/patch.h
src/core/plugins/plugin.cpp
src/core/plugins/plugin.h
src/core/plugins/pluginHost.cpp
src/core/plugins/pluginHost.h
src/core/plugins/pluginManager.cpp
src/core/plugins/pluginManager.h
src/core/quantizer.cpp
src/core/quantizer.h
src/core/queue.h
src/core/recManager.cpp [deleted file]
src/core/recManager.h [deleted file]
src/core/recorder.cpp
src/core/recorder.h
src/core/recorderHandler.cpp [deleted file]
src/core/recorderHandler.h [deleted file]
src/core/sequencer.cpp
src/core/sequencer.h
src/core/swapper.h [deleted file]
src/core/synchronizer.cpp [new file with mode: 0644]
src/core/synchronizer.h [new file with mode: 0644]
src/core/types.h
src/core/wave.cpp
src/core/wave.h
src/core/waveFx.cpp
src/core/waveFx.h
src/core/waveManager.cpp
src/core/waveManager.h
src/core/weakAtomic.h
src/glue/actionEditor.cpp
src/glue/actionEditor.h
src/glue/channel.cpp
src/glue/channel.h
src/glue/config.cpp
src/glue/config.h
src/glue/events.cpp
src/glue/events.h
src/glue/io.cpp
src/glue/io.h
src/glue/layout.cpp [new file with mode: 0644]
src/glue/layout.h [new file with mode: 0644]
src/glue/main.cpp
src/glue/main.h
src/glue/plugin.cpp
src/glue/plugin.h
src/glue/recorder.cpp
src/glue/recorder.h
src/glue/sampleEditor.cpp
src/glue/sampleEditor.h
src/glue/storage.cpp
src/glue/storage.h
src/gui/dialogs/actionEditor/baseActionEditor.cpp
src/gui/dialogs/actionEditor/baseActionEditor.h
src/gui/dialogs/actionEditor/midiActionEditor.cpp
src/gui/dialogs/actionEditor/midiActionEditor.h
src/gui/dialogs/actionEditor/sampleActionEditor.cpp
src/gui/dialogs/actionEditor/sampleActionEditor.h
src/gui/dialogs/beatsInput.cpp
src/gui/dialogs/beatsInput.h
src/gui/dialogs/bpmInput.cpp
src/gui/dialogs/browser/browserBase.cpp
src/gui/dialogs/browser/browserBase.h
src/gui/dialogs/browser/browserDir.cpp
src/gui/dialogs/browser/browserDir.h
src/gui/dialogs/browser/browserLoad.cpp
src/gui/dialogs/browser/browserLoad.h
src/gui/dialogs/browser/browserSave.cpp
src/gui/dialogs/browser/browserSave.h
src/gui/dialogs/config.cpp
src/gui/dialogs/config.h
src/gui/dialogs/keyGrabber.h
src/gui/dialogs/mainWindow.cpp
src/gui/dialogs/mainWindow.h
src/gui/dialogs/midiIO/midiInputBase.cpp
src/gui/dialogs/midiIO/midiInputBase.h
src/gui/dialogs/midiIO/midiInputChannel.cpp
src/gui/dialogs/midiIO/midiInputChannel.h
src/gui/dialogs/midiIO/midiInputMaster.cpp
src/gui/dialogs/midiIO/midiInputMaster.h
src/gui/dialogs/pluginChooser.cpp
src/gui/dialogs/pluginChooser.h
src/gui/dialogs/pluginList.cpp
src/gui/dialogs/pluginList.h
src/gui/dialogs/pluginWindow.cpp
src/gui/dialogs/pluginWindow.h
src/gui/dialogs/pluginWindowGUI.cpp
src/gui/dialogs/pluginWindowGUI.h
src/gui/dialogs/sampleEditor.cpp
src/gui/dialogs/sampleEditor.h
src/gui/dialogs/window.cpp
src/gui/dialogs/window.h
src/gui/dispatcher.cpp
src/gui/dispatcher.h
src/gui/elems/actionEditor/baseAction.h
src/gui/elems/actionEditor/baseActionEditor.cpp
src/gui/elems/actionEditor/baseActionEditor.h
src/gui/elems/actionEditor/envelopeEditor.cpp
src/gui/elems/actionEditor/envelopeEditor.h
src/gui/elems/actionEditor/envelopePoint.cpp
src/gui/elems/actionEditor/envelopePoint.h
src/gui/elems/actionEditor/gridTool.cpp
src/gui/elems/actionEditor/gridTool.h
src/gui/elems/actionEditor/noteEditor.cpp [deleted file]
src/gui/elems/actionEditor/noteEditor.h [deleted file]
src/gui/elems/actionEditor/pianoItem.cpp
src/gui/elems/actionEditor/pianoItem.h
src/gui/elems/actionEditor/pianoRoll.cpp
src/gui/elems/actionEditor/pianoRoll.h
src/gui/elems/actionEditor/sampleAction.cpp
src/gui/elems/actionEditor/sampleAction.h
src/gui/elems/actionEditor/sampleActionEditor.cpp
src/gui/elems/actionEditor/sampleActionEditor.h
src/gui/elems/actionEditor/splitScroll.cpp [new file with mode: 0644]
src/gui/elems/actionEditor/splitScroll.h [new file with mode: 0644]
src/gui/elems/actionEditor/velocityEditor.cpp
src/gui/elems/actionEditor/velocityEditor.h
src/gui/elems/basics/choice.cpp
src/gui/elems/basics/choice.h
src/gui/elems/basics/group.h
src/gui/elems/basics/liquidScroll.cpp
src/gui/elems/basics/liquidScroll.h
src/gui/elems/basics/pack.cpp
src/gui/elems/basics/pack.h
src/gui/elems/basics/resizerBar.cpp
src/gui/elems/basics/resizerBar.h
src/gui/elems/basics/scroll.cpp
src/gui/elems/basics/scroll.h
src/gui/elems/basics/split.cpp [new file with mode: 0644]
src/gui/elems/basics/split.h [new file with mode: 0644]
src/gui/elems/config/tabAudio.cpp
src/gui/elems/config/tabAudio.h
src/gui/elems/config/tabBehaviors.cpp
src/gui/elems/config/tabBehaviors.h
src/gui/elems/config/tabMidi.cpp
src/gui/elems/config/tabMidi.h
src/gui/elems/config/tabMisc.cpp
src/gui/elems/config/tabMisc.h
src/gui/elems/config/tabPlugins.cpp
src/gui/elems/config/tabPlugins.h
src/gui/elems/mainWindow/keyboard/channel.cpp
src/gui/elems/mainWindow/keyboard/channelButton.cpp
src/gui/elems/mainWindow/keyboard/channelMode.cpp
src/gui/elems/mainWindow/keyboard/channelMode.h
src/gui/elems/mainWindow/keyboard/channelStatus.cpp
src/gui/elems/mainWindow/keyboard/column.cpp
src/gui/elems/mainWindow/keyboard/column.h
src/gui/elems/mainWindow/keyboard/keyboard.cpp
src/gui/elems/mainWindow/keyboard/keyboard.h
src/gui/elems/mainWindow/keyboard/midiChannel.cpp
src/gui/elems/mainWindow/keyboard/sampleChannel.cpp
src/gui/elems/mainWindow/keyboard/sampleChannel.h
src/gui/elems/mainWindow/keyboard/sampleChannelButton.cpp
src/gui/elems/mainWindow/mainIO.cpp
src/gui/elems/mainWindow/mainIO.h
src/gui/elems/mainWindow/mainMenu.cpp
src/gui/elems/mainWindow/mainMenu.h
src/gui/elems/mainWindow/mainTimer.cpp
src/gui/elems/mainWindow/mainTransport.cpp
src/gui/elems/plugin/pluginBrowser.cpp
src/gui/elems/plugin/pluginBrowser.h
src/gui/elems/plugin/pluginElement.cpp
src/gui/elems/sampleEditor/pitchTool.cpp
src/gui/elems/sampleEditor/pitchTool.h
src/gui/elems/sampleEditor/waveTools.cpp
src/gui/elems/sampleEditor/waveTools.h
src/gui/elems/sampleEditor/waveform.cpp
src/gui/elems/sampleEditor/waveform.h
src/gui/elems/soundMeter.cpp
src/gui/elems/soundMeter.h
src/gui/model.cpp [deleted file]
src/gui/model.h [deleted file]
src/gui/types.h [new file with mode: 0644]
src/gui/ui.cpp [new file with mode: 0644]
src/gui/ui.h [new file with mode: 0644]
src/gui/updater.cpp
src/gui/updater.h
src/main.cpp
src/utils/gui.cpp
src/utils/gui.h
src/utils/math.h
src/utils/vector.h
tests/actionRecorder.cpp [new file with mode: 0644]
tests/audioBuffer.cpp [deleted file]
tests/main.cpp
tests/midiLighter.cpp [new file with mode: 0644]
tests/mocks/kernelMidiMock.h [new file with mode: 0644]
tests/recorder.cpp [deleted file]
tests/waveManager.cpp

index c1571c5815b0d2276a32a51206ed026026f52883..0c4427eee93fdec60dc802fa8b0a7904e2680ac6 100644 (file)
@@ -33,15 +33,16 @@ project(giada LANGUAGES CXX)
 
 list(APPEND SOURCES
        src/main.cpp
+       src/core/engine.cpp
        src/core/worker.cpp
        src/core/eventDispatcher.cpp
        src/core/midiDispatcher.cpp
-       src/core/midiMapConf.cpp
+       src/core/midiMapper.cpp
        src/core/midiEvent.cpp
-       src/core/audioBuffer.cpp
        src/core/quantizer.cpp
        src/core/conf.cpp
        src/core/kernelAudio.cpp
+       src/core/jackTransport.cpp
        src/core/mixerHandler.cpp
        src/core/sequencer.cpp
        src/core/metronome.cpp
@@ -51,12 +52,12 @@ list(APPEND SOURCES
        src/core/kernelMidi.cpp
        src/core/graphics.cpp
        src/core/patch.cpp
-       src/core/recorderHandler.cpp
-       src/core/recorder.cpp
+       src/core/actions/actionRecorder.cpp
+       src/core/actions/actions.cpp
        src/core/mixer.cpp
-       src/core/clock.cpp
+    src/core/synchronizer.cpp
        src/core/waveManager.cpp
-       src/core/recManager.cpp
+    src/core/recorder.cpp
        src/core/midiLearnParam.cpp
        src/core/resampler.cpp
        src/core/plugins/pluginHost.cpp
@@ -77,6 +78,9 @@ list(APPEND SOURCES
        src/core/channels/midiReceiver.cpp
        src/core/channels/channel.cpp
        src/core/channels/channelManager.cpp
+       src/core/model/sequencer.cpp
+       src/core/model/mixer.cpp
+       src/core/model/recorder.cpp
        src/core/model/model.cpp
        src/core/model/storage.cpp
        src/core/idManager.cpp
@@ -90,10 +94,11 @@ list(APPEND SOURCES
        src/glue/sampleEditor.cpp
        src/glue/actionEditor.cpp
        src/glue/config.cpp
+       src/glue/layout.cpp
+       src/gui/ui.cpp
        src/gui/dialogs/window.cpp
        src/gui/dispatcher.cpp
        src/gui/updater.cpp
-       src/gui/model.cpp
        src/gui/drawing.cpp
        src/gui/dialogs/keyGrabber.cpp
        src/gui/dialogs/about.cpp
@@ -142,11 +147,11 @@ list(APPEND SOURCES
        src/gui/elems/actionEditor/velocityEditor.cpp
        src/gui/elems/actionEditor/envelopePoint.cpp
        src/gui/elems/actionEditor/pianoRoll.cpp
-       src/gui/elems/actionEditor/noteEditor.cpp
        src/gui/elems/actionEditor/pianoItem.cpp
        src/gui/elems/actionEditor/sampleActionEditor.cpp
        src/gui/elems/actionEditor/sampleAction.cpp
        src/gui/elems/actionEditor/gridTool.cpp
+       src/gui/elems/actionEditor/splitScroll.cpp
        src/gui/elems/mainWindow/mainIO.cpp
        src/gui/elems/mainWindow/mainMenu.cpp
        src/gui/elems/mainWindow/mainTimer.cpp
@@ -183,6 +188,7 @@ list(APPEND SOURCES
        src/gui/elems/basics/slider.cpp
        src/gui/elems/basics/progress.cpp
        src/gui/elems/basics/check.cpp
+       src/gui/elems/basics/split.cpp
        src/utils/log.cpp
        src/utils/time.cpp
        src/utils/math.cpp
@@ -190,7 +196,8 @@ list(APPEND SOURCES
        src/utils/fs.cpp
        src/utils/ver.cpp
        src/utils/string.cpp
-       src/deps/rtaudio/RtAudio.cpp)
+       src/deps/rtaudio/RtAudio.cpp
+       src/deps/mcl-audio-buffer/src/audioBuffer.cpp)
 
 list(APPEND PREPROCESSOR_DEFS)
 list(APPEND INCLUDE_DIRS
@@ -222,7 +229,7 @@ endif()
 # ------------------------------------------------------------------------------
 
 if(DEFINED OS_WINDOWS)
-       list(APPEND COMPILER_OPTIONS /W4)
+       list(APPEND COMPILER_OPTIONS /W4 /bigobj /external:anglebrackets /external:W0)
 else()
        list(APPEND COMPILER_OPTIONS -Wall -Wextra -Wpedantic)
 endif()
@@ -257,8 +264,9 @@ endif()
 
 # Threads (system)
 
+set(THREADS_PREFER_PTHREAD_FLAG ON)
 find_package(Threads REQUIRED)
-list(APPEND LIBRARIES ${Threads_LIBRARY})
+list(APPEND LIBRARIES Threads::Threads)
 
 # pkg-config/pkgconf, required to find some external dependencies on some
 # platforms
@@ -287,7 +295,7 @@ if (NOT RtMidi_FOUND)
                message(FATAL_ERROR "Can't find RtMidi, aborting.")
        endif()
 
-       # RtMidi header path may vary accross OSes, so a fix is needed.
+       # RtMidi header path may vary across OSes, so a fix is needed.
        # TODO - Find a way to avoid this additional step
 
        find_path(LIBRARY_RTMIDI_INCLUDE_DIR RtMidi.h PATH_SUFFIXES rtmidi)
@@ -506,6 +514,8 @@ if(WITH_VST2 OR WITH_VST3)
 
        list(APPEND PREPROCESSOR_DEFS
                WITH_VST
+               JUCE_DEBUG=$<BOOL:$<CONFIG:Debug>>
+               JUCE_MODAL_LOOPS_PERMITTED=1
                JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1
                JUCE_MODULE_AVAILABLE_juce_gui_basics=1
                JUCE_STANDALONE_APPLICATION=1
index 3665e3b18cc3070dd0119a1c960bacd6564f9e8a..c7450556cee36ac631e742e464dadf013e13e2db 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
 --------------------------------------------------------------------------------
 
 
+0.19.1 --- 2021 . 12 . 15
+- Enable JUCE_DEBUG in Debug builds
+- New MidiLighter tests + compile-time dependency injection
+- Set limits to minimum zoom level in Action Editors (#425)
+- Refactoring and code cleanup for Channel class and other sub-components 
+- Update JUCE to version 6.1.2
+- Update RtAudio to version 5.2.0
+- Sanitize MIDI ports values (fixes #515)
+- MidiLighter improvements and cleanups (fixes #517)
+- Fix off-the-beat metronome (#522)
+- Fix number of plug-ins found not being updated after a scan (fix #523)
+- Fix PluginManager initialization
+- Fix pthread linking in CMake (#520)
+- Fix build info not being printed correctly on startup
+- [Linux] Fix X error messages on closing some plug-in editors
+- [Linux] Fix wrong icon file in XDG desktop file
+
+
+0.19.0 --- 2021 . 11 . 01
+- New "One-shot Pause" channel mode
+- Refactoring: new component-based architecture
+- Fix crash on startup if recording from mono input
+- Improved event handling for plug-ins GUIs 
+- Fix many compiler warnings on menu items initialization
+
+
+0.18.2 --- 2021 . 09 . 13
+- New stereo In/Out audio meters 
+- Revamped Action Editor: better UI, improved usability 
+- Show play head in Action Editor
+- Implement queue for MIDI events, fix issue #482
+- Simplified Event Dispatcher's Event type
+- Move JACK transport operations to new JackTransport class
+- Always pick sample rate from the first audio device when using JACK
+- Don't send MIDI events if MIDI channel is not playing (#499) or muted (#497)
+- Add AtomicSwapper as git submodule
+- Upgrade JUCE to version 6.1.0
+
+
 0.18.1 --- 2021 . 07 . 25
 - New resampler architecture: allows for changing quality also for live rendering (#288)
 - Gracefully shutdown UI on close to random crashes on quit on Windows 
 
 
 0.16.0 --- 2019 . 12 . 02
-- Fix columns' resizer bar height on verical window resize
+- Fix columns' resizer bar height on vertical window resize
 - Fix crash on MIDI learn global commands
 - Fix wrong channel routing when triggering MIDI learnt commands
 - Fix rewind button not rewinding sample channels in LOOP_* mode 
 - Update JUCE to version 5.1.2
 - UI-less plug-in window refinements
 - Update UI-less plug-in window on MIDI parameter's change
-- Strip .gptc/.gprj extention from patch name
+- Strip .gptc/.gprj extension from patch name
 - [Sample Editor] Fix non-working 'cut' operation
 - Fix missed MIDI events with more than 1 plug-in in the stack
 - Fix File Browser path widget drawn incorrectly in OS X
 - 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
+- Lots of security issues fixed while changing pitch dynamically
 - Enhanced sub-window system
 - Several minor bugs fixed
 
 - (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) The sample name is now truncated 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
index e0859b01a5617581875b2952c816fcf730ef497a..c63ef217d85bc49f501591f26eb39d41fb4eb0c7 100644 (file)
--- a/README.md
+++ b/README.md
@@ -45,7 +45,7 @@ Giada is an open source, minimalistic and hardcore music production tool. Design
 ## License
 
 Giada is available under the terms of the GNU General Public License.
-Take a look at the COPYING file for further informations.
+Take a look at the COPYING file for further information.
 
 ## Documentation
 
index 766bdfbd75fb54c7d29606c3dfbabb0e8c2e393f..b9d33b5039e786bbe5fa61489ddad9428b5aec5a 100644 (file)
@@ -7,6 +7,6 @@ GenericName=Drum machine and loop sequencer
 GenericName[es]=Caja de ritmos y secuenciador de loops
 Exec=giada %f
 Terminal=false
-Icon=giada-logo
+Icon=com.giadamusic.Giada
 Categories=Music;AudioVideo;Audio;Midi;X-Digital_Processing;X-Jack;X-MIDI;
-Keywords=Giada;
\ No newline at end of file
+Keywords=Giada;
diff --git a/src/core/action.h b/src/core/action.h
deleted file mode 100644 (file)
index 39384f8..0000000
+++ /dev/null
@@ -1,61 +0,0 @@
-/* -----------------------------------------------------------------------------
- *
- * Giada - Your Hardcore Loopmachine
- *
- * -----------------------------------------------------------------------------
- *
- * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
- *
- * This file is part of Giada - Your Hardcore Loopmachine.
- *
- * Giada - Your Hardcore Loopmachine is free software: you can
- * redistribute it and/or modify it under the terms of the GNU General
- * Public License as published by the Free Software Foundation, either
- * version 3 of the License, or (at your option) any later version.
- *
- * Giada - Your Hardcore Loopmachine is distributed in the hope that it
- * will be useful, but WITHOUT ANY WARRANTY; without even the implied
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Giada - Your Hardcore Loopmachine. If not, see
- * <http://www.gnu.org/licenses/>.
- *
- * -------------------------------------------------------------------------- */
-
-#ifndef G_ACTION_H
-#define G_ACTION_H
-
-#include "midiEvent.h"
-#include "types.h"
-
-namespace giada::m
-{
-struct Action
-{
-       ID        id = 0; // Invalid
-       ID        channelId;
-       Frame     frame;
-       MidiEvent event;
-       ID        pluginId    = -1;
-       int       pluginParam = -1;
-       ID        prevId      = 0;
-       ID        nextId      = 0;
-
-       const Action* prev = nullptr;
-       const Action* next = nullptr;
-
-       bool isValid() const
-       {
-               return id != 0;
-       }
-
-       bool isVolumeEnvelope() const
-       {
-               return event.getStatus() == MidiEvent::ENVELOPE && pluginId == -1;
-       }
-};
-} // namespace giada::m
-
-#endif
\ No newline at end of file
diff --git a/src/core/actions/action.h b/src/core/actions/action.h
new file mode 100644 (file)
index 0000000..5c3d2e6
--- /dev/null
@@ -0,0 +1,61 @@
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+#ifndef G_ACTION_H
+#define G_ACTION_H
+
+#include "src/core/midiEvent.h"
+#include "src/core/types.h"
+
+namespace giada::m
+{
+struct Action
+{
+       ID        id = 0; // Invalid
+       ID        channelId;
+       Frame     frame;
+       MidiEvent event;
+       ID        pluginId    = -1;
+       int       pluginParam = -1;
+       ID        prevId      = 0;
+       ID        nextId      = 0;
+
+       const Action* prev = nullptr;
+       const Action* next = nullptr;
+
+       bool isValid() const
+       {
+               return id != 0;
+       }
+
+       bool isVolumeEnvelope() const
+       {
+               return event.getStatus() == MidiEvent::ENVELOPE && pluginId == -1;
+       }
+};
+} // namespace giada::m
+
+#endif
\ No newline at end of file
diff --git a/src/core/actions/actionRecorder.cpp b/src/core/actions/actionRecorder.cpp
new file mode 100644 (file)
index 0000000..38f7b3f
--- /dev/null
@@ -0,0 +1,354 @@
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+#include "core/actions/actionRecorder.h"
+#include "core/actions/action.h"
+#include "core/actions/actions.h"
+#include "core/const.h"
+#include "core/model/model.h"
+#include "core/patch.h"
+#include "utils/log.h"
+#include "utils/ver.h"
+#include <algorithm>
+#include <cassert>
+#include <cmath>
+#include <unordered_map>
+
+namespace giada::m
+{
+namespace
+{
+constexpr int MAX_LIVE_RECS_CHUNK = 128;
+} // namespace
+
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+
+ActionRecorder::ActionRecorder(model::Model& m)
+: m_model(m)
+, m_actions(m)
+{
+       m_liveActions.reserve(MAX_LIVE_RECS_CHUNK);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void ActionRecorder::reset()
+{
+       m_liveActions.clear();
+       m_actions.reset();
+}
+
+/* -------------------------------------------------------------------------- */
+
+bool ActionRecorder::isBoundaryEnvelopeAction(const Action& a) const
+{
+       assert(a.prev != nullptr);
+       assert(a.next != nullptr);
+       return a.prev->frame > a.frame || a.next->frame < a.frame;
+}
+
+/* -------------------------------------------------------------------------- */
+
+void ActionRecorder::updateBpm(float ratio, int quantizerStep)
+{
+       if (ratio == 1.0f)
+               return;
+
+       m_actions.updateKeyFrames([=](Frame old) {
+               /* The division here cannot be precise. A new frame can be 44099 and the 
+               quantizer set to 44100. That would mean two recs completely useless. So we 
+               compute a reject value ('delta'): if it's lower than 6 frames the new frame 
+               is collapsed with a quantized frame. FIXME - maybe 6 frames are too low. */
+               Frame frame = static_cast<Frame>(old * ratio);
+               if (frame != 0)
+               {
+                       Frame delta = quantizerStep % frame;
+                       if (delta > 0 && delta <= 6)
+                               frame = frame + delta;
+               }
+               return frame;
+       });
+}
+
+/* -------------------------------------------------------------------------- */
+
+void ActionRecorder::updateSamplerate(int systemRate, int patchRate)
+{
+       if (systemRate == patchRate)
+               return;
+
+       float ratio = systemRate / (float)patchRate;
+
+       m_actions.updateKeyFrames([=](Frame old) { return floorf(old * ratio); });
+}
+
+/* -------------------------------------------------------------------------- */
+
+bool ActionRecorder::cloneActions(ID channelId, ID newChannelId)
+{
+       bool                       cloned = false;
+       std::vector<Action>        actions;
+       std::unordered_map<ID, ID> map; // Action ID mapper, old -> new
+
+       m_actions.forEachAction([&](const Action& a) {
+               if (a.channelId != channelId)
+                       return;
+
+               ID newActionId = m_actions.getNewActionId();
+
+               map.insert({a.id, newActionId});
+
+               Action clone(a);
+               clone.id        = newActionId;
+               clone.channelId = newChannelId;
+
+               actions.push_back(clone);
+               cloned = true;
+       });
+
+       /* Update nextId and prevId relationships given the new action ID. */
+
+       for (Action& a : actions)
+       {
+               if (a.prevId != 0)
+                       a.prevId = map.at(a.prevId);
+               if (a.nextId != 0)
+                       a.nextId = map.at(a.nextId);
+       }
+
+       m_actions.rec(actions);
+
+       return cloned;
+}
+
+/* -------------------------------------------------------------------------- */
+
+void ActionRecorder::liveRec(ID channelId, MidiEvent e, Frame globalFrame)
+{
+       assert(e.isNoteOnOff()); // Can't record any other kind of events for now
+
+       /* TODO - this might allocate on the MIDI thread */
+       if (m_liveActions.size() >= m_liveActions.capacity())
+               m_liveActions.reserve(m_liveActions.size() + MAX_LIVE_RECS_CHUNK);
+
+       m_liveActions.push_back(m_actions.makeAction(m_actions.getNewActionId(), channelId, globalFrame, e));
+}
+
+/* -------------------------------------------------------------------------- */
+
+std::unordered_set<ID> ActionRecorder::consolidate()
+{
+       for (auto it = m_liveActions.begin(); it != m_liveActions.end(); ++it)
+               consolidate(*it, it - m_liveActions.begin()); // Pass current index
+
+       m_actions.rec(m_liveActions);
+
+       std::unordered_set<ID> out;
+       for (const Action& action : m_liveActions)
+               out.insert(action.channelId);
+
+       m_liveActions.clear();
+       return out;
+}
+
+/* -------------------------------------------------------------------------- */
+
+void ActionRecorder::clearAllActions()
+{
+       for (Channel& ch : m_model.get().channels)
+               ch.hasActions = false;
+       m_model.swap(model::SwapType::HARD);
+
+       m_actions.clearAll();
+}
+
+/* -------------------------------------------------------------------------- */
+
+Actions::Map ActionRecorder::deserializeActions(const std::vector<Patch::Action>& pactions)
+{
+       Actions::Map out;
+
+       /* First pass: add actions with no relationship, that is with no prev/next
+       pointers filled in. */
+
+       for (const Patch::Action& paction : pactions)
+               out[paction.frame].push_back(m_actions.makeAction(paction));
+
+       /* Second pass: fill in previous and next actions, if any. Is this the
+       fastest/smartest way to do it? Maybe not. Optimizations are welcome. */
+
+       for (const Patch::Action& paction : pactions)
+       {
+               if (paction.nextId == 0 && paction.prevId == 0)
+                       continue;
+               Action* curr = const_cast<Action*>(getActionPtrById(paction.id, out));
+               assert(curr != nullptr);
+               if (paction.nextId != 0)
+               {
+                       curr->next = getActionPtrById(paction.nextId, out);
+                       assert(curr->next != nullptr);
+               }
+               if (paction.prevId != 0)
+               {
+                       curr->prev = getActionPtrById(paction.prevId, out);
+                       assert(curr->prev != nullptr);
+               }
+       }
+
+       return out;
+}
+
+/* -------------------------------------------------------------------------- */
+
+std::vector<Patch::Action> ActionRecorder::serializeActions(const Actions::Map& actions)
+{
+       std::vector<Patch::Action> out;
+       for (const auto& kv : actions)
+       {
+               for (const Action& a : kv.second)
+               {
+                       out.push_back({
+                           a.id,
+                           a.channelId,
+                           a.frame,
+                           a.event.getRaw(),
+                           a.prevId,
+                           a.nextId,
+                       });
+               }
+       }
+       return out;
+}
+
+/* -------------------------------------------------------------------------- */
+
+const Action* ActionRecorder::getActionPtrById(int id, const Actions::Map& source)
+{
+       for (const auto& [_, actions] : source)
+               for (const Action& action : actions)
+                       if (action.id == id)
+                               return &action;
+       return nullptr;
+}
+
+/* -------------------------------------------------------------------------- */
+
+bool ActionRecorder::areComposite(const Action& a1, const Action& a2) const
+{
+       return a1.event.getStatus() == MidiEvent::NOTE_ON &&
+              a2.event.getStatus() == MidiEvent::NOTE_OFF &&
+              a1.event.getNote() == a2.event.getNote() &&
+              a1.channelId == a2.channelId;
+}
+
+/* -------------------------------------------------------------------------- */
+
+void ActionRecorder::consolidate(const Action& a1, std::size_t i)
+{
+       /* This algorithm must start searching from the element next to 'a1': since 
+       live actions are recorded in linear sequence, the potential partner of 'a1' 
+       always lies beyond a1 itself. Without this trick (i.e. if it loops from 
+       vector.begin() each time) the algorithm would end up matching wrong partners. */
+
+       for (auto it = m_liveActions.begin() + i; it != m_liveActions.end(); ++it)
+       {
+
+               const Action& a2 = *it;
+
+               if (!areComposite(a1, a2))
+                       continue;
+
+               const_cast<Action&>(a1).nextId = a2.id;
+               const_cast<Action&>(a2).prevId = a1.id;
+
+               break;
+       }
+}
+
+/* -------------------------------------------------------------------------- */
+
+const std::vector<Action>* ActionRecorder::getActionsOnFrame(Frame f) const
+{
+       return m_actions.getActionsOnFrame(f);
+}
+
+bool ActionRecorder::hasActions(ID channelId, int type) const
+{
+       return m_actions.hasActions(channelId, type);
+}
+
+Action ActionRecorder::getClosestAction(ID channelId, Frame f, int type) const
+{
+       return m_actions.getClosestAction(channelId, f, type);
+}
+
+std::vector<Action> ActionRecorder::getActionsOnChannel(ID channelId) const
+{
+       return m_actions.getActionsOnChannel(channelId);
+}
+
+void ActionRecorder::clearChannel(ID channelId)
+{
+       m_actions.clearChannel(channelId);
+}
+
+void ActionRecorder::clearActions(ID channelId, int type)
+{
+       m_actions.clearActions(channelId, type);
+}
+
+Action ActionRecorder::rec(ID channelId, Frame frame, MidiEvent e)
+{
+       return m_actions.rec(channelId, frame, e);
+}
+
+void ActionRecorder::rec(ID channelId, Frame f1, Frame f2, MidiEvent e1, MidiEvent e2)
+{
+       return m_actions.rec(channelId, f1, f2, e1, e2);
+}
+
+void ActionRecorder::updateSiblings(ID id, ID prevId, ID nextId)
+{
+       m_actions.updateSiblings(id, prevId, nextId);
+}
+
+void ActionRecorder::deleteAction(ID id)
+{
+       m_actions.deleteAction(id);
+}
+
+void ActionRecorder::deleteAction(ID currId, ID nextId)
+{
+       m_actions.deleteAction(currId, nextId);
+}
+
+void ActionRecorder::updateEvent(ID id, MidiEvent e)
+{
+       m_actions.updateEvent(id, e);
+}
+} // namespace giada::m
\ No newline at end of file
diff --git a/src/core/actions/actionRecorder.h b/src/core/actions/actionRecorder.h
new file mode 100644 (file)
index 0000000..c0ee704
--- /dev/null
@@ -0,0 +1,130 @@
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+#ifndef G_ACTION_RECORDER_H
+#define G_ACTION_RECORDER_H
+
+#include "core/actions/actions.h"
+#include "core/midiEvent.h"
+#include "core/types.h"
+#include <unordered_set>
+
+namespace giada::m::patch
+{
+struct Action;
+}
+
+namespace giada::m
+{
+struct Action;
+class ActionRecorder
+{
+public:
+       ActionRecorder(model::Model&);
+
+       /* reset
+       Brings everything back to the initial state. */
+
+       void reset();
+
+       bool isBoundaryEnvelopeAction(const Action& a) const;
+
+       /* updateBpm
+    Changes actions position by calculating the new bpm value. */
+
+       void updateBpm(float ratio, int quantizerStep);
+
+       /* updateSamplerate
+    Changes actions position by taking in account the new samplerate. If 
+    f_system == f_patch nothing will change, otherwise the conversion is 
+    mandatory. */
+
+       void updateSamplerate(int systemRate, int patchRate);
+
+       /* cloneActions
+    Clones actions in channel 'channelId', giving them a new channel ID. Returns
+    whether any action has been cloned. */
+
+       bool cloneActions(ID channelId, ID newChannelId);
+
+       /* liveRec
+    Records a user-generated action. NOTE_ON or NOTE_OFF only for now. */
+
+       void liveRec(ID channelId, MidiEvent e, Frame global);
+
+       /* consolidate
+    Records all live actions. Returns a set of channels IDs that have been 
+    recorded. */
+
+       std::unordered_set<ID> consolidate();
+
+       /* clearAllActions
+    Deletes all recorded actions. */
+
+       void clearAllActions();
+
+       /* (de)serializeActions
+    Creates new Actions given the patch raw data and vice versa. */
+
+       Actions::Map               deserializeActions(const std::vector<Patch::Action>& as);
+       std::vector<Patch::Action> serializeActions(const Actions::Map& as);
+
+       /* Pass-thru functions. See Actions.h */
+
+       const std::vector<Action>* getActionsOnFrame(Frame f) const;
+       bool                       hasActions(ID channelId, int type = 0) const;
+       Action                     getClosestAction(ID channelId, Frame f, int type) const;
+       std::vector<Action>        getActionsOnChannel(ID channelId) const;
+       void                       clearChannel(ID channelId);
+       void                       clearActions(ID channelId, int type);
+       Action                     rec(ID channelId, Frame frame, MidiEvent e);
+       void                       rec(ID channelId, Frame f1, Frame f2, MidiEvent e1, MidiEvent e2);
+       void                       updateSiblings(ID id, ID prevId, ID nextId);
+       void                       deleteAction(ID id);
+       void                       deleteAction(ID currId, ID nextId);
+       void                       updateEvent(ID id, MidiEvent e);
+
+private:
+       /* areComposite
+    Composite: NOTE_ON + NOTE_OFF on the same note. */
+
+       bool areComposite(const Action& a1, const Action& a2) const;
+
+       const Action* getActionPtrById(int id, const Actions::Map& source);
+
+       /* consolidate
+    Given an action 'a1' tries to find the matching NOTE_OFF and updates the
+    action accordingly. */
+
+       void consolidate(const Action& a1, std::size_t i);
+
+       model::Model&       m_model;
+       Actions             m_actions;
+       std::vector<Action> m_liveActions;
+};
+} // namespace giada::m
+
+#endif
diff --git a/src/core/actions/actions.cpp b/src/core/actions/actions.cpp
new file mode 100644 (file)
index 0000000..dc3bdb0
--- /dev/null
@@ -0,0 +1,352 @@
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+#include "actions.h"
+#include "action.h"
+#include "core/idManager.h"
+#include "core/model/model.h"
+#include "utils/log.h"
+#include <algorithm>
+#include <cassert>
+#include <memory>
+
+namespace giada::m
+{
+Actions::Actions(model::Model& model)
+: m_model(model)
+{
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Actions::reset()
+{
+       m_actionId = IdManager();
+       clearAll();
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Actions::clearAll()
+{
+       model::DataLock lock = m_model.lockData();
+       m_model.getAllShared<Map>().clear();
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Actions::clearChannel(ID channelId)
+{
+       removeIf([=](const Action& a) { return a.channelId == channelId; });
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Actions::clearActions(ID channelId, int type)
+{
+       removeIf([=](const Action& a) {
+               return a.channelId == channelId && a.event.getStatus() == type;
+       });
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Actions::deleteAction(ID id)
+{
+       removeIf([=](const Action& a) { return a.id == id; });
+}
+
+void Actions::deleteAction(ID currId, ID nextId)
+{
+       removeIf([=](const Action& a) { return a.id == currId || a.id == nextId; });
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Actions::updateKeyFrames(std::function<Frame(Frame old)> f)
+{
+       Map temp;
+
+       /* Copy all existing actions in local map by cloning them, with just a
+       difference: they have a new frame value. */
+
+       for (const auto& [oldFrame, actions] : m_model.getAllShared<Map>())
+       {
+               Frame newFrame = f(oldFrame);
+               for (const Action& a : actions)
+               {
+                       Action copy = a;
+                       copy.frame  = newFrame;
+                       temp[newFrame].push_back(copy);
+               }
+               G_DEBUG(oldFrame << " -> " << newFrame);
+       }
+
+       updateMapPointers(temp);
+
+       model::DataLock lock  = m_model.lockData();
+       m_model.getAllShared<Map>() = std::move(temp);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Actions::updateEvent(ID id, MidiEvent e)
+{
+       model::DataLock lock = m_model.lockData();
+
+       findAction(m_model.getAllShared<Map>(), id)->event = e;
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Actions::updateSiblings(ID id, ID prevId, ID nextId)
+{
+       model::DataLock lock = m_model.lockData();
+
+       Action* pcurr = findAction(m_model.getAllShared<Map>(), id);
+       Action* pprev = findAction(m_model.getAllShared<Map>(), prevId);
+       Action* pnext = findAction(m_model.getAllShared<Map>(), nextId);
+
+       pcurr->prev   = pprev;
+       pcurr->prevId = pprev->id;
+       pcurr->next   = pnext;
+       pcurr->nextId = pnext->id;
+
+       if (pprev != nullptr)
+       {
+               pprev->next   = pcurr;
+               pprev->nextId = pcurr->id;
+       }
+       if (pnext != nullptr)
+       {
+               pnext->prev   = pcurr;
+               pnext->prevId = pcurr->id;
+       }
+}
+
+/* -------------------------------------------------------------------------- */
+
+bool Actions::hasActions(ID channelId, int type) const
+{
+       for (const auto& [frame, actions] : m_model.getAllShared<Map>())
+               for (const Action& a : actions)
+                       if (a.channelId == channelId && (type == 0 || type == a.event.getStatus()))
+                               return true;
+       return false;
+}
+
+/* -------------------------------------------------------------------------- */
+
+Action Actions::makeAction(ID id, ID channelId, Frame frame, MidiEvent e)
+{
+       Action out{m_actionId.generate(id), channelId, frame, e, -1, -1};
+       m_actionId.set(id);
+       return out;
+}
+
+Action Actions::makeAction(const Patch::Action& a)
+{
+       m_actionId.set(a.id);
+       return Action{a.id, a.channelId, a.frame, a.event, -1, -1, a.prevId,
+           a.nextId};
+}
+
+/* -------------------------------------------------------------------------- */
+
+Action Actions::rec(ID channelId, Frame frame, MidiEvent event)
+{
+       /* Skip duplicates. */
+
+       if (exists(channelId, frame, event))
+               return {};
+
+       Action a = makeAction(0, channelId, frame, event);
+
+       /* If key frame doesn't exist yet, the [] operator in std::map is smart 
+       enough to insert a new item first. No plug-in data for now. */
+
+       model::DataLock lock = m_model.lockData();
+
+       m_model.getAllShared<Map>()[frame].push_back(a);
+       updateMapPointers(m_model.getAllShared<Map>());
+
+       return a;
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Actions::rec(std::vector<Action>& actions)
+{
+       if (actions.size() == 0)
+               return;
+
+       model::DataLock lock = m_model.lockData();
+
+       Map& map = m_model.getAllShared<Map>();
+
+       for (const Action& a : actions)
+               if (!exists(a.channelId, a.frame, a.event, map))
+                       map[a.frame].push_back(a);
+       updateMapPointers(map);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Actions::rec(ID channelId, Frame f1, Frame f2, MidiEvent e1, MidiEvent e2)
+{
+       model::DataLock lock = m_model.lockData();
+
+       Map& map = m_model.getAllShared<Map>();
+
+       map[f1].push_back(makeAction(0, channelId, f1, e1));
+       map[f2].push_back(makeAction(0, channelId, f2, e2));
+
+       Action* a1 = findAction(map, map[f1].back().id);
+       Action* a2 = findAction(map, map[f2].back().id);
+       a1->nextId = a2->id;
+       a2->prevId = a1->id;
+
+       updateMapPointers(map);
+}
+
+/* -------------------------------------------------------------------------- */
+
+const std::vector<Action>* Actions::getActionsOnFrame(Frame frame) const
+{
+       if (m_model.getAllShared<Map>().count(frame) == 0)
+               return nullptr;
+       return &m_model.getAllShared<Map>().at(frame);
+}
+
+/* -------------------------------------------------------------------------- */
+
+Action Actions::getClosestAction(ID channelId, Frame f, int type) const
+{
+       Action out = {};
+       forEachAction([&](const Action& a) {
+               if (a.event.getStatus() != type || a.channelId != channelId)
+                       return;
+               if (!out.isValid() || (a.frame <= f && a.frame > out.frame))
+                       out = a;
+       });
+       return out;
+}
+
+/* -------------------------------------------------------------------------- */
+
+std::vector<Action> Actions::getActionsOnChannel(ID channelId) const
+{
+       std::vector<Action> out;
+       forEachAction([&](const Action& a) {
+               if (a.channelId == channelId)
+                       out.push_back(a);
+       });
+       return out;
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Actions::forEachAction(std::function<void(const Action&)> f) const
+{
+       for (auto& [_, actions] : m_model.getAllShared<Map>())
+               for (const Action& action : actions)
+                       f(action);
+}
+
+/* -------------------------------------------------------------------------- */
+
+ID Actions::getNewActionId()
+{
+       return m_actionId.generate();
+}
+
+/* -------------------------------------------------------------------------- */
+
+Action* Actions::findAction(Map& src, ID id)
+{
+       for (auto& [frame, actions] : src)
+               for (Action& a : actions)
+                       if (a.id == id)
+                               return &a;
+       assert(false);
+       return nullptr;
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Actions::updateMapPointers(Map& src)
+{
+       for (auto& kv : src)
+       {
+               for (Action& action : kv.second)
+               {
+                       if (action.nextId != 0)
+                               action.next = findAction(src, action.nextId);
+                       if (action.prevId != 0)
+                               action.prev = findAction(src, action.prevId);
+               }
+       }
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Actions::optimize(Map& map)
+{
+       for (auto it = map.cbegin(); it != map.cend();)
+               it->second.size() == 0 ? it = map.erase(it) : ++it;
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Actions::removeIf(std::function<bool(const Action&)> f)
+{
+       model::DataLock lock = m_model.lockData();
+
+       Map& map = m_model.getAllShared<Map>();
+       for (auto& [frame, actions] : map)
+               actions.erase(std::remove_if(actions.begin(), actions.end(), f), actions.end());
+       optimize(map);
+       updateMapPointers(map);
+}
+
+/* -------------------------------------------------------------------------- */
+
+bool Actions::exists(ID channelId, Frame frame, const MidiEvent& event, const Map& target) const
+{
+       for (const auto& [_, actions] : target)
+               for (const Action& a : actions)
+                       if (a.channelId == channelId && a.frame == frame && a.event.getRaw() == event.getRaw())
+                               return true;
+       return false;
+}
+
+/* -------------------------------------------------------------------------- */
+
+bool Actions::exists(ID channelId, Frame frame, const MidiEvent& event) const
+{
+       return exists(channelId, frame, event, m_model.getAllShared<Map>());
+}
+} // namespace giada::m
diff --git a/src/core/actions/actions.h b/src/core/actions/actions.h
new file mode 100644 (file)
index 0000000..12c224b
--- /dev/null
@@ -0,0 +1,185 @@
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+#ifndef G_ACTIONS_H
+#define G_ACTIONS_H
+
+#include "action.h"
+#include "core/idManager.h"
+#include "core/midiEvent.h"
+#include "core/patch.h"
+#include "core/types.h"
+#include <functional>
+#include <map>
+#include <memory>
+#include <vector>
+
+namespace giada::m::model
+{
+class Model;
+}
+
+namespace giada::m
+{
+class Actions
+{
+public:
+       using Map = std::map<Frame, std::vector<Action>>;
+
+       Actions(model::Model& model);
+
+       /* forEachAction
+    Applies a read-only callback on each action recorded. NEVER do anything
+    inside the callback that might alter the ActionMap. */
+
+       void forEachAction(std::function<void(const Action&)> f) const;
+
+       /* getActionsOnChannel
+    Returns a vector of actions belonging to channel 'ch'. */
+
+       std::vector<Action> getActionsOnChannel(ID channelId) const;
+
+       /* getClosestAction
+    Given a frame 'f' returns the closest action. */
+
+       Action getClosestAction(ID channelId, Frame f, int type) const;
+
+       /* getActionsOnFrame
+    Returns a pointer to a vector of actions recorded on frame 'f', or nullptr 
+    if the frame has no actions. */
+
+       const std::vector<Action>* getActionsOnFrame(Frame f) const;
+
+       /* hasActions
+    Checks if the channel has at least one action recorded. */
+
+       bool hasActions(ID channelId, int type = 0) const;
+
+       /* makeAction
+    Makes a new action given some data. */
+       //TODO - move to actionManager
+
+       Action makeAction(ID id, ID channelId, Frame frame, MidiEvent e);
+       Action makeAction(const Patch::Action& a);
+
+       /* reset
+       Brings everything back to the initial state. */
+
+       void reset();
+
+       /* clearAll
+    Deletes all recorded actions. */
+
+       void clearAll();
+
+       /* clearChannel
+    Clears all actions from a channel. */
+
+       void clearChannel(ID channelId);
+
+       /* clearActions
+    Clears the actions by type from a channel. */
+
+       void clearActions(ID channelId, int type);
+
+       /* deleteAction (1)
+    Deletes a specific action. */
+
+       void deleteAction(ID id);
+
+       /* deleteAction (2)
+    Deletes a specific pair of actions. Useful for composite stuff (i.e. MIDI). */
+
+       void deleteAction(ID currId, ID nextId);
+
+       /* updateKeyFrames
+    Update all the key frames in the internal map of actions, according to a 
+    lambda function 'f'. */
+
+       void updateKeyFrames(std::function<Frame(Frame old)> f);
+
+       /* updateEvent
+    Changes the event in action 'a'. */
+
+       void updateEvent(ID id, MidiEvent e);
+
+       /* updateSiblings
+    Changes previous and next actions in action with id 'id'. Mostly used for
+    chained actions such as envelopes. */
+
+       void updateSiblings(ID id, ID prevId, ID nextId);
+
+       /* rec (1)
+    Records an action and returns it. Used by the Action Editor. */
+
+       Action rec(ID channelId, Frame frame, MidiEvent e);
+
+       /* rec (2)
+    Transfer a vector of actions into the current ActionMap. This is called by 
+    recordHandler when a live session is over and consolidation is required. */
+
+       void rec(std::vector<Action>& actions);
+
+       /* rec (3)
+    Records two actions on channel 'channel'. Useful when recording composite 
+    actions in the Action Editor. */
+
+       void rec(ID channelId, Frame f1, Frame f2, MidiEvent e1, MidiEvent e2);
+
+       /* getNewActionId
+    Returns a new action ID, internally generated. */
+       //TODO - move to actionManager
+
+       ID getNewActionId();
+
+private:
+       bool exists(ID channelId, Frame frame, const MidiEvent& event, const Map& target) const;
+       bool exists(ID channelId, Frame frame, const MidiEvent& event) const;
+
+       Action* findAction(Map& src, ID id);
+
+       /* updateMapPointers
+    Updates all prev/next actions pointers into the action map. This is required
+    after an action has been recorded, since pushing back new actions in a Action 
+    vector makes it reallocating the existing ones. */
+
+       void updateMapPointers(Map& src);
+
+       /* optimize
+    Removes frames without actions. */
+
+       void optimize(Map& map);
+
+       void removeIf(std::function<bool(const Action&)> f);
+
+       model::Model& m_model;
+
+       //TODO - move to actionManager
+       IdManager m_actionId;
+};
+} // namespace giada::m
+
+#endif
diff --git a/src/core/audioBuffer.cpp b/src/core/audioBuffer.cpp
deleted file mode 100644 (file)
index c117c58..0000000
+++ /dev/null
@@ -1,271 +0,0 @@
-/* -----------------------------------------------------------------------------
- *
- * Giada - Your Hardcore Loopmachine
- *
- * -----------------------------------------------------------------------------
- *
- * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
- *
- * This file is part of Giada - Your Hardcore Loopmachine.
- *
- * Giada - Your Hardcore Loopmachine is free software: you can
- * redistribute it and/or modify it under the terms of the GNU General
- * Public License as published by the Free Software Foundation, either
- * version 3 of the License, or (at your option) any later version.
- *
- * Giada - Your Hardcore Loopmachine is distributed in the hope that it
- * will be useful, but WITHOUT ANY WARRANTY; without even the implied
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Giada - Your Hardcore Loopmachine. If not, see
- * <http://www.gnu.org/licenses/>.
- *
- * -------------------------------------------------------------------------- */
-
-#include "audioBuffer.h"
-#include <algorithm>
-#include <cassert>
-
-namespace giada::m
-{
-AudioBuffer::AudioBuffer()
-: m_data(nullptr)
-, m_size(0)
-, m_channels(0)
-, m_viewing(false)
-{
-}
-
-/* -------------------------------------------------------------------------- */
-
-AudioBuffer::AudioBuffer(Frame size, int channels)
-: AudioBuffer()
-{
-       alloc(size, channels);
-}
-
-/* -------------------------------------------------------------------------- */
-
-AudioBuffer::AudioBuffer(float* data, Frame size, int channels)
-: m_data(data)
-, m_size(size)
-, m_channels(channels)
-, m_viewing(true)
-{
-       assert(channels <= NUM_CHANS);
-}
-
-/* -------------------------------------------------------------------------- */
-
-AudioBuffer::AudioBuffer(const AudioBuffer& o)
-{
-       copy(o);
-}
-
-/* -------------------------------------------------------------------------- */
-
-AudioBuffer::AudioBuffer(AudioBuffer&& o)
-{
-       move(std::move(o));
-}
-
-/* -------------------------------------------------------------------------- */
-
-AudioBuffer::~AudioBuffer()
-{
-       if (!m_viewing)
-               free();
-}
-
-/* -------------------------------------------------------------------------- */
-
-AudioBuffer& AudioBuffer::operator=(const AudioBuffer& o)
-{
-       if (this == &o)
-               return *this;
-       copy(o);
-       return *this;
-}
-
-/* -------------------------------------------------------------------------- */
-
-AudioBuffer& AudioBuffer::operator=(AudioBuffer&& o)
-{
-       if (this == &o)
-               return *this;
-       move(std::move(o));
-       return *this;
-}
-
-/* -------------------------------------------------------------------------- */
-
-float* AudioBuffer::operator[](Frame offset) const
-{
-       assert(m_data != nullptr);
-       assert(offset < m_size);
-       return m_data + (offset * m_channels);
-}
-
-/* -------------------------------------------------------------------------- */
-
-void AudioBuffer::clear(Frame a, Frame b)
-{
-       if (m_data == nullptr)
-               return;
-       if (b == -1)
-               b = m_size;
-       std::fill_n(m_data + (a * m_channels), (b - a) * m_channels, 0.0);
-}
-
-/* -------------------------------------------------------------------------- */
-
-Frame AudioBuffer::countFrames() const { return m_size; }
-int   AudioBuffer::countSamples() const { return m_size * m_channels; }
-int   AudioBuffer::countChannels() const { return m_channels; }
-bool  AudioBuffer::isAllocd() const { return m_data != nullptr; }
-
-/* -------------------------------------------------------------------------- */
-
-float AudioBuffer::getPeak() const
-{
-       float peak = 0.0f;
-       for (int i = 0; i < countSamples(); i++)
-               peak = std::max(peak, m_data[i]);
-       return peak;
-}
-
-/* -------------------------------------------------------------------------- */
-
-void AudioBuffer::alloc(Frame size, int channels)
-{
-       assert(channels <= NUM_CHANS);
-
-       free();
-       m_size     = size;
-       m_channels = channels;
-       m_data     = new float[m_size * m_channels];
-       clear();
-}
-
-/* -------------------------------------------------------------------------- */
-
-void AudioBuffer::free()
-{
-       if (m_data == nullptr)
-               return;
-       delete[] m_data;
-       m_data     = nullptr;
-       m_size     = 0;
-       m_channels = 0;
-       m_viewing  = false;
-}
-
-/* -------------------------------------------------------------------------- */
-
-void AudioBuffer::sum(const AudioBuffer& b, Frame framesToCopy, Frame srcOffset,
-    Frame destOffset, float gain, Pan pan)
-{
-       copyData<Operation::SUM>(b, framesToCopy, srcOffset, destOffset, gain, pan);
-}
-
-void AudioBuffer::set(const AudioBuffer& b, Frame framesToCopy, Frame srcOffset,
-    Frame destOffset, float gain, Pan pan)
-{
-       copyData<Operation::SET>(b, framesToCopy, srcOffset, destOffset, gain, pan);
-}
-
-void AudioBuffer::sum(const AudioBuffer& b, float gain, Pan pan)
-{
-       copyData<Operation::SUM>(b, -1, 0, 0, gain, pan);
-}
-
-void AudioBuffer::set(const AudioBuffer& b, float gain, Pan pan)
-{
-       copyData<Operation::SET>(b, -1, 0, 0, gain, pan);
-}
-
-/* -------------------------------------------------------------------------- */
-
-template <AudioBuffer::Operation O>
-void AudioBuffer::copyData(const AudioBuffer& b, Frame framesToCopy,
-    Frame srcOffset, Frame destOffset, float gain, Pan pan)
-{
-       const int  srcChannels  = b.countChannels();
-       const int  destChannels = countChannels();
-       const bool sameChannels = srcChannels == destChannels;
-
-       assert(m_data != nullptr);
-       assert(destOffset >= 0 && destOffset < m_size);
-       assert(srcChannels <= destChannels);
-
-       /* Make sure the amount of frames to copy lies within the current buffer 
-       size. */
-
-       framesToCopy = framesToCopy == -1 ? b.countFrames() : framesToCopy;
-       framesToCopy = std::min(framesToCopy, m_size - destOffset);
-
-       /* Case 1) source has less channels than this one: brutally spread source's
-       channel 0 over this one (TODO - maybe mixdown source channels first?)
-          Case 2) source has same amount of channels: copy them 1:1. */
-
-       for (int destF = 0, srcF = srcOffset; destF < framesToCopy && destF < b.countFrames(); destF++, srcF++)
-       {
-               for (int ch = 0; ch < destChannels; ch++)
-               {
-                       if constexpr (O == Operation::SUM)
-                               sum(destF + destOffset, ch, b[srcF][sameChannels ? ch : 0] * gain * pan[ch]);
-                       else
-                               set(destF + destOffset, ch, b[srcF][sameChannels ? ch : 0] * gain * pan[ch]);
-               }
-       }
-}
-
-/* -------------------------------------------------------------------------- */
-
-void AudioBuffer::applyGain(float g)
-{
-       for (int i = 0; i < countSamples(); i++)
-               m_data[i] *= g;
-}
-
-/* -------------------------------------------------------------------------- */
-
-void AudioBuffer::sum(Frame f, int channel, float val) { (*this)[f][channel] += val; }
-void AudioBuffer::set(Frame f, int channel, float val) { (*this)[f][channel] = val; }
-
-/* -------------------------------------------------------------------------- */
-
-void AudioBuffer::move(AudioBuffer&& o)
-{
-       assert(o.countChannels() <= NUM_CHANS);
-
-       m_data     = o.m_data;
-       m_size     = o.m_size;
-       m_channels = o.m_channels;
-       m_viewing  = o.m_viewing;
-
-       o.m_data     = nullptr;
-       o.m_size     = 0;
-       o.m_channels = 0;
-       o.m_viewing  = false;
-}
-
-/* -------------------------------------------------------------------------- */
-
-void AudioBuffer::copy(const AudioBuffer& o)
-{
-       m_data     = new float[o.m_size * o.m_channels];
-       m_size     = o.m_size;
-       m_channels = o.m_channels;
-       m_viewing  = o.m_viewing;
-
-       std::copy(o.m_data, o.m_data + (o.m_size * o.m_channels), m_data);
-}
-
-/* -------------------------------------------------------------------------- */
-
-template void AudioBuffer::copyData<AudioBuffer::Operation::SUM>(const AudioBuffer&, Frame, Frame, Frame, float, Pan);
-template void AudioBuffer::copyData<AudioBuffer::Operation::SET>(const AudioBuffer&, Frame, Frame, Frame, float, Pan);
-} // namespace giada::m
\ No newline at end of file
diff --git a/src/core/audioBuffer.h b/src/core/audioBuffer.h
deleted file mode 100644 (file)
index 3ff25ec..0000000
+++ /dev/null
@@ -1,164 +0,0 @@
-/* -----------------------------------------------------------------------------
- *
- * Giada - Your Hardcore Loopmachine
- *
- * -----------------------------------------------------------------------------
- *
- * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
- *
- * This file is part of Giada - Your Hardcore Loopmachine.
- *
- * Giada - Your Hardcore Loopmachine is free software: you can
- * redistribute it and/or modify it under the terms of the GNU General
- * Public License as published by the Free Software Foundation, either
- * version 3 of the License, or (at your option) any later version.
- *
- * Giada - Your Hardcore Loopmachine is distributed in the hope that it
- * will be useful, but WITHOUT ANY WARRANTY; without even the implied
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Giada - Your Hardcore Loopmachine. If not, see
- * <http://www.gnu.org/licenses/>.
- *
- * -------------------------------------------------------------------------- */
-
-#ifndef G_AUDIO_BUFFER_H
-#define G_AUDIO_BUFFER_H
-
-#include "core/types.h"
-#include <array>
-
-namespace giada::m
-{
-/* AudioBuffer
-A class that holds a buffer filled with audio data. NOTE: currently it only
-supports 2 channels (stereo). Give it a mono stream and it will convert it to
-stereo. Give it a multichannel stream and it will throw an assertion. */
-
-class AudioBuffer
-{
-public:
-       static constexpr int NUM_CHANS = 2;
-
-       using Pan = std::array<float, NUM_CHANS>;
-
-       /* AudioBuffer (1)
-       Creates an empty (and invalid) audio buffer. */
-
-       AudioBuffer();
-
-       /* AudioBuffer (2)
-       Creates an audio buffer and allocates memory for size * channels frames. */
-
-       AudioBuffer(Frame size, int channels);
-
-       /* AudioBuffer (3)
-       Creates an audio buffer out of a raw pointer. AudioBuffer created this way
-       is instructed not to free the owned data on destruction. */
-
-       AudioBuffer(float* data, Frame size, int channels);
-
-       /* AudioBuffer(const AudioBuffer&)
-       Copy constructor. */
-
-       AudioBuffer(const AudioBuffer& o);
-
-       /* AudioBuffer(AudioBuffer&&)
-       Move constructor. */
-
-       AudioBuffer(AudioBuffer&& o);
-
-       /* ~AudioBuffer
-       Destructor. */
-
-       ~AudioBuffer();
-
-       /* operator = (const AudioBuffer& o)
-       Copy assignment operator. */
-
-       AudioBuffer& operator=(const AudioBuffer& o);
-
-       /* operator = (AudioBuffer&& o)
-       Move assignment operator. */
-
-       AudioBuffer& operator=(AudioBuffer&& o);
-
-       /* operator []
-       Given a frame 'offset', returns a pointer to it. This is useful for digging 
-       inside a frame, i.e. parsing each channel. How to use it:
-
-               for (int k=0; k<buffer->countFrames(), k++)
-                       for (int i=0; i<buffer->countChannels(); i++)
-                               ... buffer[k][i] ...
-
-       Also note that buffer[0] will give you a pointer to the whole internal data
-       array. */
-
-       float* operator[](int offset) const;
-
-       Frame countFrames() const;
-       int   countSamples() const;
-       int   countChannels() const;
-       bool  isAllocd() const;
-
-       /* getPeak
-       Returns the highest value from any channel. */
-
-       float getPeak() const;
-
-       void alloc(Frame size, int channels);
-       void free();
-
-       /* sum, set (1)
-       Merges (sum) or copies (set) 'framesToCopy' frames of buffer 'b' onto this 
-       one. If 'framesToCopy' is -1 the whole buffer will be copied. If 'b' has 
-       less channels than this one, they will be spread over the current ones. 
-       Buffer 'b' MUST NOT contain more channels than this one. */
-
-       void sum(const AudioBuffer& b, Frame framesToCopy = -1, Frame srcOffset = 0,
-           Frame destOffset = 0, float gain = 1.0f, Pan pan = {1.0f, 1.0f});
-       void set(const AudioBuffer& b, Frame framesToCopy = -1, Frame srcOffset = 0,
-           Frame destOffset = 0, float gain = 1.0f, Pan pan = {1.0f, 1.0f});
-
-       /* sum, set (2)
-       Same as sum, set (1) without boundaries or offsets: it just copies as much
-       as possibile. */
-
-       void sum(const AudioBuffer& b, float gain = 1.0f, Pan pan = {1.0f, 1.0f});
-       void set(const AudioBuffer& b, float gain = 1.0f, Pan pan = {1.0f, 1.0f});
-
-       /* clear
-       Clears the internal data by setting all bytes to 0.0f. Optional parameters
-       'a' and 'b' set the range. */
-
-       void clear(Frame a = 0, Frame b = -1);
-
-       void applyGain(float g);
-
-private:
-       enum class Operation
-       {
-               SUM,
-               SET
-       };
-
-       template <Operation O = Operation::SET>
-       void copyData(const AudioBuffer& b, Frame framesToCopy = -1,
-           Frame srcOffset = 0, Frame destOffset = 0, float gain = 1.0f,
-           Pan pan = {1.0f, 1.0f});
-
-       void move(AudioBuffer&& o);
-       void copy(const AudioBuffer& o);
-       void sum(Frame f, int channel, float val);
-       void set(Frame f, int channel, float val);
-
-       float* m_data;
-       Frame  m_size;
-       int    m_channels;
-       bool   m_viewing;
-};
-} // namespace giada::m
-
-#endif
\ No newline at end of file
index dc785f60ce0d57704931d0e79276e07b0865fedb..c0957a7c5942847df72dcd408cfd8b0186e1aa38 100644 (file)
 
 #include "audioReceiver.h"
 #include "core/channels/channel.h"
+#include "deps/mcl-audio-buffer/src/audioBuffer.hpp"
 
-namespace giada::m::audioReceiver
+namespace giada::m
 {
-Data::Data(const patch::Channel& p)
+AudioReceiver::AudioReceiver(const Patch::Channel& p)
 : inputMonitor(p.inputMonitor)
 , overdubProtection(p.overdubProtection)
 {
@@ -37,14 +38,14 @@ Data::Data(const patch::Channel& p)
 
 /* -------------------------------------------------------------------------- */
 
-void render(const channel::Data& ch, const AudioBuffer& in)
+void AudioReceiver::render(const Channel& ch, const mcl::AudioBuffer& in) const
 {
        /* If armed and input monitor is on, copy input buffer to channel buffer: 
        this enables the input monitoring. The channel buffer will be overwritten 
        later on by pluginHost::processStack, so that you would record "clean" audio 
        (i.e. not plugin-processed). */
 
-       if (ch.armed && ch.audioReceiver->inputMonitor)
-               ch.buffer->audio.set(in, /*gain=*/1.0f); // add, don't overwrite
+       if (ch.armed && inputMonitor)
+               ch.shared->audioBuffer.set(in, /*gain=*/1.0f); // add, don't overwrite
 }
-} // namespace giada::m::audioReceiver
\ No newline at end of file
+} // namespace giada::m
index f04a1247d62946ddfed3545f6a770decdfc6f9f4..99e4e9d42f6f062e52ef3f78f43324a42526da3e 100644 (file)
 #ifndef G_CHANNEL_AUDIO_RECEIVER_H
 #define G_CHANNEL_AUDIO_RECEIVER_H
 
-namespace giada::m
+#include "core/patch.h"
+
+namespace mcl
 {
 class AudioBuffer;
 }
-namespace giada::m::channel
-{
-struct Data;
-}
-namespace giada::m::patch
-{
-struct Channel;
-}
-namespace giada::m::audioReceiver
+
+namespace giada::m
 {
-struct Data
+class Channel;
+class AudioReceiver final
 {
-       Data() = default;
-       Data(const patch::Channel& p);
-       Data(const Data& o) = default;
+public:
+       AudioReceiver() = default;
+       AudioReceiver(const Patch::Channel& p);
+       AudioReceiver(const AudioReceiver& o) = default;
+
+       void render(const Channel& ch, const mcl::AudioBuffer& in) const;
 
        bool inputMonitor;
        bool overdubProtection;
 };
-
-void render(const channel::Data& ch, const AudioBuffer& in);
-} // namespace giada::m::audioReceiver
+} // namespace giada::m
 
 #endif
index 2d90efcd87a6b8a133e012d065f65f9bff4927a6..5aa00ab763607b199601f46b2ed325d24140f496 100644 (file)
  *
  * -------------------------------------------------------------------------- */
 
-#include "channel.h"
+#include "core/channels/channel.h"
+#include "core/actions/actionRecorder.h"
+#include "core/channels/sampleAdvancer.h"
+#include "core/conf.h"
+#include "core/engine.h"
+#include "core/midiMapper.h"
 #include "core/mixerHandler.h"
+#include "core/model/model.h"
 #include "core/plugins/pluginHost.h"
 #include "core/plugins/pluginManager.h"
+#include "core/recorder.h"
 #include <cassert>
 
-namespace giada::m::channel
+extern giada::m::Engine g_engine;
+
+namespace giada::m
 {
 namespace
 {
-AudioBuffer::Pan calcPanning_(float pan)
+mcl::AudioBuffer::Pan calcPanning_(float pan)
 {
        /* TODO - precompute the AudioBuffer::Pan when pan value changes instead of
        building it on the fly. */
@@ -45,133 +54,54 @@ AudioBuffer::Pan calcPanning_(float pan)
                return {1.0f, 1.0f};
        return {1.0f - pan, pan};
 }
-
-/* -------------------------------------------------------------------------- */
-
-void react_(Data& d, const eventDispatcher::Event& e)
-{
-       switch (e.type)
-       {
-       case eventDispatcher::EventType::CHANNEL_VOLUME:
-               d.volume = std::get<float>(e.data);
-               break;
-
-       case eventDispatcher::EventType::CHANNEL_PAN:
-               d.pan = std::get<float>(e.data);
-               break;
-
-       case eventDispatcher::EventType::CHANNEL_MUTE:
-               d.mute = !d.mute;
-               break;
-
-       case eventDispatcher::EventType::CHANNEL_TOGGLE_ARM:
-               d.armed = !d.armed;
-               break;
-
-       case eventDispatcher::EventType::CHANNEL_SOLO:
-               d.solo = !d.solo;
-               m::mh::updateSoloCount();
-               break;
-
-       default:
-               break;
-       }
-}
-
-/* -------------------------------------------------------------------------- */
-
-void renderMasterOut_(const Data& d, AudioBuffer& out)
-{
-       d.buffer->audio.set(out, /*gain=*/1.0f);
-#ifdef WITH_VST
-       if (d.plugins.size() > 0)
-               pluginHost::processStack(d.buffer->audio, d.plugins, nullptr);
-#endif
-       out.set(d.buffer->audio, d.volume);
-}
-
-/* -------------------------------------------------------------------------- */
-
-void renderMasterIn_(const Data& d, AudioBuffer& in)
-{
-#ifdef WITH_VST
-       if (d.plugins.size() > 0)
-               pluginHost::processStack(in, d.plugins, nullptr);
-#endif
-}
-
-/* -------------------------------------------------------------------------- */
-
-void renderChannel_(const Data& d, AudioBuffer& out, AudioBuffer& in, bool audible)
-{
-       d.buffer->audio.clear();
-
-       if (d.samplePlayer)
-               samplePlayer::render(d);
-       if (d.audioReceiver)
-               audioReceiver::render(d, in);
-
-               /* If MidiReceiver exists, let it process the plug-in stack, as it can 
-       contain plug-ins that take MIDI events (i.e. synths). Otherwise process the
-       plug-in stack internally with no MIDI events. */
-
-#ifdef WITH_VST
-       if (d.midiReceiver)
-               midiReceiver::render(d);
-       else if (d.plugins.size() > 0)
-               pluginHost::processStack(d.buffer->audio, d.plugins, nullptr);
-#endif
-
-       if (audible)
-               out.sum(d.buffer->audio, d.volume * d.volume_i, calcPanning_(d.pan));
-}
 } // namespace
 
 /* -------------------------------------------------------------------------- */
 /* -------------------------------------------------------------------------- */
 /* -------------------------------------------------------------------------- */
 
-Buffer::Buffer(Frame bufferSize)
-: audio(bufferSize, G_MAX_IO_CHANS)
+Channel::Shared::Shared(Frame bufferSize)
+: audioBuffer(bufferSize, G_MAX_IO_CHANS)
 {
 }
 
 /* -------------------------------------------------------------------------- */
 
-Data::Data(ChannelType type, ID id, ID columnId, State& state, Buffer& buffer)
-: state(&state)
-, buffer(&buffer)
+Channel::Channel(ChannelType type, ID id, ID columnId, Shared& s)
+: shared(&s)
 , id(id)
 , type(type)
 , columnId(columnId)
 , volume(G_DEFAULT_VOL)
 , volume_i(G_DEFAULT_VOL)
 , pan(G_DEFAULT_PAN)
-, mute(false)
-, solo(false)
 , armed(false)
 , key(0)
 , hasActions(false)
 , height(G_GUI_UNIT)
+, midiLighter(g_engine.midiMapper)
+, m_mute(false)
+, m_solo(false)
 {
        switch (type)
        {
        case ChannelType::SAMPLE:
-               samplePlayer.emplace(&state.resampler.value());
-               sampleReactor.emplace(id);
+               samplePlayer.emplace(&(shared->resampler.value()));
+               sampleAdvancer.emplace();
+               sampleReactor.emplace(id, g_engine.sequencer, g_engine.model);
                audioReceiver.emplace();
-               sampleActionRecorder.emplace();
+               sampleActionRecorder.emplace(g_engine.actionRecorder, g_engine.sequencer);
                break;
 
        case ChannelType::PREVIEW:
-               samplePlayer.emplace(&state.resampler.value());
-               sampleReactor.emplace(id);
+               samplePlayer.emplace(&(shared->resampler.value()));
+               sampleReactor.emplace(id, g_engine.sequencer, g_engine.model);
                break;
 
        case ChannelType::MIDI:
                midiController.emplace();
-               midiSender.emplace();
-               midiActionRecorder.emplace();
+               midiSender.emplace(g_engine.kernelMidi);
+               midiActionRecorder.emplace(g_engine.actionRecorder, g_engine.sequencer);
 #ifdef WITH_VST
                midiReceiver.emplace();
 #endif
@@ -180,52 +110,55 @@ Data::Data(ChannelType type, ID id, ID columnId, State& state, Buffer& buffer)
        default:
                break;
        }
+
+       initCallbacks();
 }
 
 /* -------------------------------------------------------------------------- */
 
-Data::Data(const patch::Channel& p, State& state, Buffer& buffer, float samplerateRatio)
-: state(&state)
-, buffer(&buffer)
+Channel::Channel(const Patch::Channel& p, Shared& s, float samplerateRatio, Wave* wave)
+: shared(&s)
 , id(p.id)
 , type(p.type)
 , columnId(p.columnId)
 , volume(p.volume)
 , volume_i(G_DEFAULT_VOL)
 , pan(p.pan)
-, mute(p.mute)
-, solo(p.solo)
 , armed(p.armed)
 , key(p.key)
 , hasActions(p.hasActions)
 , name(p.name)
 , height(p.height)
 #ifdef WITH_VST
-, plugins(pluginManager::hydratePlugins(p.pluginIds))
+, plugins(g_engine.pluginManager.hydratePlugins(p.pluginIds, g_engine.model)) // TODO move outside, as constructor parameter
 #endif
 , midiLearner(p)
+, midiLighter(g_engine.midiMapper, p)
+, m_mute(p.mute)
+, m_solo(p.solo)
 {
-       state.readActions.store(p.readActions);
-       state.recStatus.store(p.readActions ? ChannelStatus::PLAY : ChannelStatus::OFF);
+       shared->readActions.store(p.readActions);
+       shared->recStatus.store(p.readActions ? ChannelStatus::PLAY : ChannelStatus::OFF);
 
        switch (type)
        {
        case ChannelType::SAMPLE:
-               samplePlayer.emplace(p, samplerateRatio, &state.resampler.value());
-               sampleReactor.emplace(id);
+               samplePlayer.emplace(p, samplerateRatio, &(shared->resampler.value()), wave);
+               sampleAdvancer.emplace();
+               sampleReactor.emplace(id, g_engine.sequencer, g_engine.model);
                audioReceiver.emplace(p);
-               sampleActionRecorder.emplace();
+               sampleActionRecorder.emplace(g_engine.actionRecorder, g_engine.sequencer);
                break;
 
        case ChannelType::PREVIEW:
-               samplePlayer.emplace(p, samplerateRatio, &state.resampler.value());
-               sampleReactor.emplace(id);
+               samplePlayer.emplace(p, samplerateRatio, &(shared->resampler.value()), nullptr);
+               sampleReactor.emplace(id, g_engine.sequencer, g_engine.model);
                break;
 
        case ChannelType::MIDI:
                midiController.emplace();
-               midiSender.emplace(p);
-               midiActionRecorder.emplace();
+               midiSender.emplace(p, g_engine.kernelMidi);
+               midiActionRecorder.emplace(g_engine.actionRecorder, g_engine.sequencer);
 #ifdef WITH_VST
                midiReceiver.emplace();
 #endif
@@ -234,29 +167,88 @@ Data::Data(const patch::Channel& p, State& state, Buffer& buffer, float samplera
        default:
                break;
        }
+
+       initCallbacks();
 }
 
 /* -------------------------------------------------------------------------- */
 
-bool Data::operator==(const Data& other)
+Channel::Channel(const Channel& other)
+: midiLighter(g_engine.midiMapper)
+{
+       *this = other;
+}
+
+/* -------------------------------------------------------------------------- */
+
+Channel& Channel::operator=(const Channel& other)
+{
+       if (this == &other)
+               return *this;
+
+       shared     = other.shared;
+       id         = other.id;
+       type       = other.type;
+       columnId   = other.columnId;
+       volume     = other.volume;
+       volume_i   = other.volume_i;
+       pan        = other.pan;
+       m_mute     = other.m_mute;
+       m_solo     = other.m_solo;
+       armed      = other.armed;
+       key        = other.key;
+       hasActions = other.hasActions;
+       name       = other.name;
+       height     = other.height;
+#ifdef WITH_VST
+       plugins = other.plugins;
+#endif
+
+       midiLearner    = other.midiLearner;
+       midiLighter    = other.midiLighter;
+       samplePlayer   = other.samplePlayer;
+       sampleAdvancer = other.sampleAdvancer;
+       sampleReactor  = other.sampleReactor;
+       audioReceiver  = other.audioReceiver;
+       midiController = other.midiController;
+#ifdef WITH_VST
+       midiReceiver = other.midiReceiver;
+#endif
+       midiSender           = other.midiSender;
+       sampleActionRecorder = other.sampleActionRecorder;
+       midiActionRecorder   = other.midiActionRecorder;
+
+       initCallbacks();
+
+       return *this;
+}
+
+/* -------------------------------------------------------------------------- */
+
+bool Channel::operator==(const Channel& other)
 {
        return id == other.id;
 }
 
 /* -------------------------------------------------------------------------- */
 
-bool Data::isInternal() const
+bool Channel::isInternal() const
 {
        return type == ChannelType::MASTER || type == ChannelType::PREVIEW;
 }
 
-bool Data::isMuted() const
+bool Channel::isMuted() const
 {
        /* Internals can't be muted. */
-       return !isInternal() && mute;
+       return !isInternal() && m_mute;
 }
 
-bool Data::canInputRec() const
+bool Channel::isSoloed() const
+{
+       return m_solo;
+}
+
+bool Channel::canInputRec() const
 {
        if (type != ChannelType::SAMPLE)
                return false;
@@ -268,87 +260,202 @@ bool Data::canInputRec() const
        return armed && canOverdub;
 }
 
-bool Data::canActionRec() const
+bool Channel::canActionRec() const
 {
        return hasWave() && !samplePlayer->isAnyLoopMode();
 }
 
-bool Data::hasWave() const
+bool Channel::hasWave() const
 {
        return samplePlayer && samplePlayer->hasWave();
 }
 
-bool Data::isPlaying() const
+bool Channel::isPlaying() const
 {
-       ChannelStatus s = state->playStatus.load();
+       ChannelStatus s = shared->playStatus.load();
        return s == ChannelStatus::PLAY || s == ChannelStatus::ENDING;
 }
 
-bool Data::isReadingActions() const
+bool Channel::isReadingActions() const
 {
-       ChannelStatus s = state->recStatus.load();
+       ChannelStatus s = shared->recStatus.load();
        return s == ChannelStatus::PLAY || s == ChannelStatus::ENDING;
 }
 
 /* -------------------------------------------------------------------------- */
 
-void advance(const Data& d, const sequencer::EventBuffer& events)
+void Channel::setMute(bool v)
+{
+       if (m_mute != v)
+               midiLighter.sendMute(v);
+       m_mute = v;
+}
+
+void Channel::setSolo(bool v)
+{
+       if (m_solo != v)
+               midiLighter.sendSolo(v);
+       m_solo = v;
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Channel::initCallbacks()
 {
-       for (const sequencer::Event& e : events)
+       shared->playStatus.onChange = [this](ChannelStatus status) {
+               midiLighter.sendStatus(status, g_engine.mixer.isChannelAudible(*this));
+       };
+
+       if (samplePlayer)
        {
-               if (d.midiController)
-                       midiController::advance(d, e);
-               if (d.samplePlayer)
-                       samplePlayer::advance(d, e);
-               if (d.midiSender)
-                       midiSender::advance(d, e);
+               samplePlayer->onLastFrame = [this]() {
+                       sampleAdvancer->onLastFrame(*this, g_engine.sequencer.isRunning());
+               };
+       }
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Channel::advance(const Sequencer::EventBuffer& events) const
+{
+       for (const Sequencer::Event& e : events)
+       {
+               if (midiController)
+                       midiController->advance(*this, e);
+               if (samplePlayer)
+                       sampleAdvancer->advance(*this, e);
+               if (midiSender)
+                       midiSender->advance(*this, e);
 #ifdef WITH_VST
-               if (d.midiReceiver)
-                       midiReceiver::advance(d, e);
+               if (midiReceiver)
+                       midiReceiver->advance(*this, e);
 #endif
        }
 }
 
 /* -------------------------------------------------------------------------- */
 
-void react(Data& d, const eventDispatcher::EventBuffer& events, bool audible)
+void Channel::react(const EventDispatcher::EventBuffer& events)
 {
-       for (const eventDispatcher::Event& e : events)
+       for (const EventDispatcher::Event& e : events)
        {
-               if (e.channelId > 0 && e.channelId != d.id)
+               if (e.channelId > 0 && e.channelId != id)
                        continue;
 
-               react_(d, e);
-               midiLighter::react(d, e, audible);
-
-               if (d.midiController)
-                       midiController::react(d, e);
-               if (d.midiSender)
-                       midiSender::react(d, e);
-               if (d.samplePlayer)
-                       samplePlayer::react(d, e);
-               if (d.midiActionRecorder)
-                       midiActionRecorder::react(d, e);
-               if (d.sampleActionRecorder)
-                       sampleActionRecorder::react(d, e);
-               if (d.sampleReactor)
-                       sampleReactor::react(d, e);
+               react(e);
+               if (midiController)
+                       midiController->react(*this, e);
+               if (midiSender)
+                       midiSender->react(*this, e);
+               if (samplePlayer)
+                       samplePlayer->react(e);
+               if (midiActionRecorder)
+                       midiActionRecorder->react(*this, e, g_engine.recorder.canRecordActions());
+               if (sampleActionRecorder)
+                       sampleActionRecorder->react(*this, e, g_engine.conf.data.treatRecsAsLoops,
+                           g_engine.sequencer.isRunning(), g_engine.recorder.canRecordActions());
+               if (sampleReactor)
+                       sampleReactor->react(*this, e, g_engine.sequencer, g_engine.conf.data);
 #ifdef WITH_VST
-               if (d.midiReceiver)
-                       midiReceiver::react(d, e);
+               if (midiReceiver)
+                       midiReceiver->react(*this, e);
 #endif
        }
 }
 
 /* -------------------------------------------------------------------------- */
 
-void render(const Data& d, AudioBuffer* out, AudioBuffer* in, bool audible)
+void Channel::react(const EventDispatcher::Event& e)
+{
+       switch (e.type)
+       {
+       case EventDispatcher::EventType::CHANNEL_VOLUME:
+               volume = std::get<float>(e.data);
+               break;
+
+       case EventDispatcher::EventType::CHANNEL_PAN:
+               pan = std::get<float>(e.data);
+               break;
+
+       case EventDispatcher::EventType::CHANNEL_MUTE:
+               setMute(!isMuted());
+               break;
+
+       case EventDispatcher::EventType::CHANNEL_TOGGLE_ARM:
+               armed = !armed;
+               break;
+
+       case EventDispatcher::EventType::CHANNEL_SOLO:
+               setSolo(!isSoloed());
+               g_engine.mixerHandler.updateSoloCount();
+               break;
+
+       default:
+               break;
+       }
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Channel::render(mcl::AudioBuffer* out, mcl::AudioBuffer* in, bool audible) const
 {
-       if (d.id == mixer::MASTER_OUT_CHANNEL_ID)
-               renderMasterOut_(d, *out);
-       else if (d.id == mixer::MASTER_IN_CHANNEL_ID)
-               renderMasterIn_(d, *in);
+       if (id == Mixer::MASTER_OUT_CHANNEL_ID)
+               renderMasterOut(*out);
+#ifdef WITH_VST
+       else if (id == Mixer::MASTER_IN_CHANNEL_ID)
+               renderMasterIn(*in);
+#endif
        else
-               renderChannel_(d, *out, *in, audible);
+               renderChannel(*out, *in, audible);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Channel::renderMasterOut(mcl::AudioBuffer& out) const
+{
+       shared->audioBuffer.set(out, /*gain=*/1.0f);
+#ifdef WITH_VST
+       if (plugins.size() > 0)
+               g_engine.pluginHost.processStack(shared->audioBuffer, plugins, nullptr);
+#endif
+       out.set(shared->audioBuffer, volume);
+}
+
+/* -------------------------------------------------------------------------- */
+
+#ifdef WITH_VST
+
+void Channel::renderMasterIn(mcl::AudioBuffer& in) const
+{
+       if (plugins.size() > 0)
+               g_engine.pluginHost.processStack(in, plugins, nullptr);
+}
+
+#endif
+
+/* -------------------------------------------------------------------------- */
+
+void Channel::renderChannel(mcl::AudioBuffer& out, mcl::AudioBuffer& in, bool audible) const
+{
+       shared->audioBuffer.clear();
+
+       if (samplePlayer)
+               samplePlayer->render(*this);
+       if (audioReceiver)
+               audioReceiver->render(*this, in);
+
+               /* If MidiReceiver exists, let it process the plug-in stack, as it can 
+       contain plug-ins that take MIDI events (i.e. synths). Otherwise process the
+       plug-in stack internally with no MIDI events. */
+
+#ifdef WITH_VST
+       if (midiReceiver)
+               midiReceiver->render(*this, g_engine.pluginHost);
+       else if (plugins.size() > 0)
+               g_engine.pluginHost.processStack(shared->audioBuffer, plugins, nullptr);
+#endif
+
+       if (audible)
+               out.sum(shared->audioBuffer, volume * volume_i, calcPanning_(pan));
 }
-} // namespace giada::m::channel
\ No newline at end of file
+} // namespace giada::m
\ No newline at end of file
index 1273e00cebb00765b27861fc016abf34225fd1d1..1d1d81c0efdc32b4996a3c5959aac67f937a96dc 100644 (file)
@@ -31,7 +31,6 @@
 #ifdef WITH_VST
 #include "deps/juce-config.h"
 #endif
-#include "core/audioBuffer.h"
 #include "core/channels/audioReceiver.h"
 #include "core/channels/midiActionRecorder.h"
 #include "core/channels/midiController.h"
 #include "core/channels/midiLighter.h"
 #include "core/channels/midiSender.h"
 #include "core/channels/sampleActionRecorder.h"
+#include "core/channels/sampleAdvancer.h"
 #include "core/channels/samplePlayer.h"
+#include "core/channels/sampleReactor.h"
 #include "core/const.h"
 #include "core/eventDispatcher.h"
+#include "core/midiEvent.h"
 #include "core/mixer.h"
+#include "core/patch.h"
+#include "core/queue.h"
 #include "core/resampler.h"
 #include "core/sequencer.h"
+#include "deps/mcl-audio-buffer/src/audioBuffer.hpp"
 #ifdef WITH_VST
 #include "core/channels/midiReceiver.h"
 #endif
 namespace giada::m
 {
 class Plugin;
-}
-namespace giada::m::channel
+class Channel final
 {
-struct State
-{
-       WeakAtomic<Frame>         tracker     = 0;
-       WeakAtomic<ChannelStatus> playStatus  = ChannelStatus::OFF;
-       WeakAtomic<ChannelStatus> recStatus   = ChannelStatus::OFF;
-       WeakAtomic<bool>          readActions = false;
-       bool                      rewinding   = false;
-       Frame                     offset      = 0;
-
-       /* Optional resampler for sample-based channels. Unfortunately a Resampler
+public:
+       struct Shared
+       {
+               Shared(Frame bufferSize);
+
+               mcl::AudioBuffer audioBuffer;
+#ifdef WITH_VST
+               juce::MidiBuffer     midiBuffer;
+               Queue<MidiEvent, 32> midiQueue;
+#endif
+
+               WeakAtomic<Frame>         tracker     = 0;
+               WeakAtomic<ChannelStatus> playStatus  = ChannelStatus::OFF;
+               WeakAtomic<ChannelStatus> recStatus   = ChannelStatus::OFF;
+               WeakAtomic<bool>          readActions = false;
+               bool                      rewinding   = false;
+               Frame                     offset      = 0;
+
+               /* Optional resampler for sample-based channels. Unfortunately a Resampler
        object (based on libsamplerate) doesn't like to get copied while rendering
        audio, so can't live inside WaveReader object (which is copied on model 
        changes by the Swapper mechanism). Let's put it in the shared state here. */
 
-       std::optional<Resampler> resampler = {};
-};
+               std::optional<Resampler> resampler = {};
+       };
 
-struct Buffer
-{
-       Buffer(Frame bufferSize);
+       Channel(ChannelType t, ID id, ID columnId, Shared&);
+       Channel(const Patch::Channel& p, Shared&, float samplerateRatio, Wave* w);
+       Channel(const Channel& o);
+       Channel(Channel&& o) = default;
 
-       AudioBuffer audio;
-#ifdef WITH_VST
-       juce::MidiBuffer midi;
-#endif
-};
+       Channel& operator=(const Channel&);
+       Channel& operator=(Channel&&) = default;
+       bool     operator==(const Channel&);
 
-struct Data
-{
-       Data(ChannelType t, ID id, ID columnId, State& state, Buffer& buffer);
-       Data(const patch::Channel& p, State& state, Buffer& buffer, float samplerateRatio);
-       Data(const Data& o) = default;
-       Data(Data&& o)      = default;
-       Data& operator=(const Data&) = default;
-       Data& operator=(Data&&) = default;
+       /* advance
+       Advances internal state by processing static events (e.g. pre-recorded 
+       actions or sequencer events) in the current block. */
+
+       void advance(const Sequencer::EventBuffer& e) const;
+
+       /* render
+       Renders audio data to I/O buffers. */
 
-       bool operator==(const Data&);
+       void render(mcl::AudioBuffer* out, mcl::AudioBuffer* in, bool audible) const;
+
+       /* react
+       Reacts to live events coming from the EventDispatcher (human events) and
+       updates itself accordingly. */
+
+       void react(const EventDispatcher::EventBuffer& e);
 
        bool isPlaying() const;
        bool isReadingActions() const;
        bool isInternal() const;
        bool isMuted() const;
+       bool isSoloed() const;
        bool canInputRec() const;
        bool canActionRec() const;
        bool hasWave() const;
 
-       State*      state;
-       Buffer*     buffer;
+       void setMute(bool);
+       void setSolo(bool);
+
+       Shared*     shared;
        ID          id;
        ChannelType type;
        ID          columnId;
        float       volume;
        float       volume_i; // Internal volume used for velocity-drives-volume mode on Sample Channels
        float       pan;
-       bool        mute;
-       bool        solo;
        bool        armed;
        int         key;
        bool        hasActions;
@@ -120,37 +139,32 @@ struct Data
        std::vector<Plugin*> plugins;
 #endif
 
-       midiLearner::Data midiLearner;
-       midiLighter::Data midiLighter;
+       MidiLearner             midiLearner;
+       MidiLighter<KernelMidi> midiLighter;
 
-       std::optional<samplePlayer::Data>   samplePlayer;
-       std::optional<sampleReactor::Data>  sampleReactor;
-       std::optional<audioReceiver::Data>  audioReceiver;
-       std::optional<midiController::Data> midiController;
+       std::optional<SamplePlayer>   samplePlayer;
+       std::optional<SampleAdvancer> sampleAdvancer;
+       std::optional<SampleReactor>  sampleReactor;
+       std::optional<AudioReceiver>  audioReceiver;
+       std::optional<MidiController> midiController;
 #ifdef WITH_VST
-       std::optional<midiReceiver::Data> midiReceiver;
+       std::optional<MidiReceiver> midiReceiver;
 #endif
-       std::optional<midiSender::Data>           midiSender;
-       std::optional<sampleActionRecorder::Data> sampleActionRecorder;
-       std::optional<midiActionRecorder::Data>   midiActionRecorder;
-};
-
-/* advance
-Advances internal state by processing static events (e.g. pre-recorded 
-actions or sequencer events) in the current block. */
-
-void advance(const Data& d, const sequencer::EventBuffer& e);
+       std::optional<MidiSender>           midiSender;
+       std::optional<SampleActionRecorder> sampleActionRecorder;
+       std::optional<MidiActionRecorder>   midiActionRecorder;
 
-/* react
-Reacts to live events coming from the EventDispatcher (human events) and
-updates itself accordingly. */
+private:
+       void renderMasterOut(mcl::AudioBuffer&) const;
+       void renderMasterIn(mcl::AudioBuffer&) const;
+       void renderChannel(mcl::AudioBuffer& out, mcl::AudioBuffer& in, bool audible) const;
 
-void react(Data& d, const eventDispatcher::EventBuffer& e, bool audible);
+       void initCallbacks();
+       void react(const EventDispatcher::Event&);
 
-/* render
-Renders audio data to I/O buffers. */
-
-void render(const Data& d, AudioBuffer* out, AudioBuffer* in, bool audible);
-} // namespace giada::m::channel
+       bool m_mute;
+       bool m_solo;
+};
+} // namespace giada::m
 
 #endif
index 489106f19c1641d42f66bac6b216eb8604efef49..da24d726ab1625e24d480ee2dc46bb62ee9f4f61 100644 (file)
  *
  * -------------------------------------------------------------------------- */
 
-#include "channelManager.h"
-#include "core/action.h"
+#include "core/channels/channelManager.h"
 #include "core/channels/channel.h"
 #include "core/channels/samplePlayer.h"
 #include "core/conf.h"
 #include "core/const.h"
-#include "core/idManager.h"
-#include "core/kernelAudio.h"
-#include "core/mixer.h"
 #include "core/model/model.h"
 #include "core/patch.h"
 #include "core/plugins/plugin.h"
 #include "core/plugins/pluginHost.h"
-#include "core/plugins/pluginManager.h"
-#include "core/recorderHandler.h"
 #include "core/wave.h"
-#include "core/waveManager.h"
-#include "utils/fs.h"
 #include <cassert>
 
-namespace giada::m::channelManager
+namespace giada::m
 {
-namespace
+ChannelManager::ChannelManager(const Conf::Data& c, model::Model& m)
+: m_conf(c)
+, m_model(m)
 {
-IdManager channelId_;
-
-/* -------------------------------------------------------------------------- */
-
-channel::State& makeState_(ChannelType type)
-{
-       std::unique_ptr<channel::State> state = std::make_unique<channel::State>();
-
-       if (type == ChannelType::SAMPLE || type == ChannelType::PREVIEW)
-               state->resampler = Resampler(static_cast<Resampler::Quality>(conf::conf.rsmpQuality), G_MAX_IO_CHANS);
-
-       model::add(std::move(state));
-       return model::back<channel::State>();
 }
 
 /* -------------------------------------------------------------------------- */
 
-channel::Buffer& makeBuffer_()
+ID ChannelManager::getNextId() const
 {
-       model::add(std::make_unique<channel::Buffer>(kernelAudio::getRealBufSize()));
-       return model::back<channel::Buffer>();
+       return m_channelId.getNext();
 }
-} // namespace
 
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
 /* -------------------------------------------------------------------------- */
 
-void init()
+void ChannelManager::reset()
 {
-       channelId_ = IdManager();
+       m_channelId = IdManager();
 }
 
 /* -------------------------------------------------------------------------- */
 
-channel::Data create(ID channelId, ChannelType type, ID columnId)
+Channel ChannelManager::create(ID channelId, ChannelType type, ID columnId, int bufferSize)
 {
-       channel::Data out = channel::Data(type, channelId_.generate(channelId),
-           columnId, makeState_(type), makeBuffer_());
+       Channel out = Channel(type, m_channelId.generate(channelId), columnId,
+           makeShared(type, bufferSize));
 
        if (out.audioReceiver)
-               out.audioReceiver->overdubProtection = conf::conf.overdubProtectionDefaultOn;
+               out.audioReceiver->overdubProtection = m_conf.overdubProtectionDefaultOn;
 
        return out;
 }
 
 /* -------------------------------------------------------------------------- */
 
-channel::Data create(const channel::Data& o)
+Channel ChannelManager::create(const Channel& o, int bufferSize)
 {
-       channel::Data out = channel::Data(o);
+       Channel out = Channel(o);
 
-       out.id     = channelId_.generate();
-       out.state  = &makeState_(o.type);
-       out.buffer = &makeBuffer_();
+       out.id     = m_channelId.generate();
+       out.shared = &makeShared(o.type, bufferSize);
 
        return out;
 }
 
 /* -------------------------------------------------------------------------- */
 
-channel::Data deserializeChannel(const patch::Channel& pch, float samplerateRatio)
+Channel ChannelManager::deserializeChannel(const Patch::Channel& pch, float samplerateRatio, int bufferSize)
 {
-       channelId_.set(pch.id);
-       return channel::Data(pch, makeState_(pch.type), makeBuffer_(), samplerateRatio);
+       m_channelId.set(pch.id);
+       return Channel(pch, makeShared(pch.type, bufferSize), samplerateRatio, m_model.findShared<Wave>(pch.waveId));
 }
 
 /* -------------------------------------------------------------------------- */
 
-const patch::Channel serializeChannel(const channel::Data& c)
+const Patch::Channel ChannelManager::serializeChannel(const Channel& c)
 {
-       patch::Channel pc;
+       Patch::Channel pc;
 
 #ifdef WITH_VST
        for (const Plugin* p : c.plugins)
@@ -132,12 +108,12 @@ const patch::Channel serializeChannel(const channel::Data& c)
        pc.height            = c.height;
        pc.name              = c.name;
        pc.key               = c.key;
-       pc.mute              = c.mute;
-       pc.solo              = c.solo;
+       pc.mute              = c.isMuted();
+       pc.solo              = c.isSoloed();
        pc.volume            = c.volume;
        pc.pan               = c.pan;
        pc.hasActions        = c.hasActions;
-       pc.readActions       = c.state->readActions.load();
+       pc.readActions       = c.shared->readActions.load();
        pc.armed             = c.armed;
        pc.midiIn            = c.midiLearner.enabled;
        pc.midiInFilter      = c.midiLearner.filter;
@@ -175,4 +151,17 @@ const patch::Channel serializeChannel(const channel::Data& c)
 
        return pc;
 }
-} // namespace giada::m::channelManager
+
+/* -------------------------------------------------------------------------- */
+
+Channel::Shared& ChannelManager::makeShared(ChannelType type, int bufferSize)
+{
+       std::unique_ptr<Channel::Shared> shared = std::make_unique<Channel::Shared>(bufferSize);
+
+       if (type == ChannelType::SAMPLE || type == ChannelType::PREVIEW)
+               shared->resampler = Resampler(static_cast<Resampler::Quality>(m_conf.rsmpQuality), G_MAX_IO_CHANS);
+
+       m_model.addShared(std::move(shared));
+       return m_model.backShared<Channel::Shared>();
+}
+} // namespace giada::m
index fd8598a625cae6e5cb8c9814741f83f775860432..0e32e04edc3fa69269f6c9e5eee8ce2f7637ef2c 100644 (file)
 #ifndef G_CHANNEL_MANAGER_H
 #define G_CHANNEL_MANAGER_H
 
+#include "core/channels/channel.h"
+#include "core/conf.h"
+#include "core/idManager.h"
+#include "core/patch.h"
 #include "core/types.h"
 
-namespace giada::m::channel
+namespace giada::m::model
 {
-struct Data;
+class Model;
 }
-namespace giada::m::conf
+
+namespace giada::m
 {
-struct Conf;
-}
-namespace giada::m::patch
+class KernelAudio;
+class ChannelManager final
 {
-struct Channel;
-}
-namespace giada::m::channelManager
-{
-/* init
-Initializes internal data. */
+public:
+       ChannelManager(const Conf::Data&, model::Model&);
+
+       /* getNextId
+       Returns the next channel ID that will be assigned to a new channel. */
+
+       ID getNextId() const;
+
+       /* reset
+    Resets internal ID generator. */
+
+       void reset();
+
+       /* create (1)
+    Creates a new channel. If channelId == 0 generates a new ID, reuse the one 
+    passed in otherwise. */
+
+       Channel create(ID channelId, ChannelType type, ID columnId, int bufferSize);
 
-void init();
+       /* create (2)
+    Creates a new channel given an existing one (i.e. clone). */
 
-/* create (1)
-Creates a new channel. If channelId == 0 generates a new ID, reuse the one 
-passed in otherwise. */
+       Channel create(const Channel& ch, int bufferSize);
 
-channel::Data create(ID channelId, ChannelType type, ID columnId);
+       /* (de)serializeWave
+    Creates a new channel given the patch raw data and vice versa. */
 
-/* create (2)
-Creates a new channel given an existing one (i.e. clone). */
+       Channel              deserializeChannel(const Patch::Channel& c, float samplerateRatio, int bufferSize);
+       const Patch::Channel serializeChannel(const Channel& c);
 
-channel::Data create(const channel::Data& ch);
+private:
+       Channel::Shared& makeShared(ChannelType type, int bufferSize);
 
-/* (de)serializeWave
-Creates a new channel given the patch raw data and vice versa. */
+       IdManager m_channelId;
 
-channel::Data        deserializeChannel(const patch::Channel& c, float samplerateRatio);
-const patch::Channel serializeChannel(const channel::Data& c);
-} // namespace giada::m::channelManager
+       const Conf::Data& m_conf;
+       model::Model&     m_model;
+};
+} // namespace giada::m
 
 #endif
index a759159f4821acf3fb1512999b500c157cacd14e..61d29250918675c31b9d28e6ff6e091a85548347 100644 (file)
  *
  * -------------------------------------------------------------------------- */
 
-#include "midiActionRecorder.h"
-#include "core/action.h"
+#include "core/channels/midiActionRecorder.h"
 #include "core/channels/channel.h"
-#include "core/clock.h"
 #include "core/conf.h"
 #include "core/eventDispatcher.h"
-#include "core/mixer.h"
-#include "core/recManager.h"
-#include "core/recorderHandler.h"
-#include <cassert>
+#include "core/sequencer.h"
+#include "src/core/actions/action.h"
+#include "src/core/actions/actionRecorder.h"
 
-namespace giada::m::midiActionRecorder
+namespace giada::m
 {
-namespace
+MidiActionRecorder::MidiActionRecorder(ActionRecorder& a, Sequencer& s)
+: m_actionRecorder(&a)
+, m_sequencer(&s)
 {
-void record_(channel::Data& ch, const MidiEvent& e)
-{
-       MidiEvent flat(e);
-       flat.setChannel(0);
-       recorderHandler::liveRec(ch.id, flat, clock::quantize(clock::getCurrentFrame()));
-       ch.hasActions = true;
-}
-
-/* -------------------------------------------------------------------------- */
-
-bool canRecord_()
-{
-       return recManager::isRecordingAction() &&
-              clock::isRunning() &&
-              !recManager::isRecordingInput();
 }
-} // namespace
 
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
 /* -------------------------------------------------------------------------- */
 
-void react(channel::Data& ch, const eventDispatcher::Event& e)
+void MidiActionRecorder::react(Channel& ch, const EventDispatcher::Event& e, bool canRecordActions)
 {
-       if (e.type == eventDispatcher::EventType::MIDI && canRecord_())
-               record_(ch, std::get<Action>(e.data).event);
+       if (e.type == EventDispatcher::EventType::MIDI && canRecordActions)
+       {
+               MidiEvent flat(std::get<Action>(e.data).event);
+               flat.setChannel(0);
+               m_actionRecorder->liveRec(ch.id, flat, m_sequencer->getCurrentFrameQuantized());
+               ch.hasActions = true;
+       }
 }
-} // namespace giada::m::midiActionRecorder
\ No newline at end of file
+} // namespace giada::m
\ No newline at end of file
index 3e08c9a83d86c4f3cb62aba2b270259f9566c47c..8abc229f2449c3b6bbbbb769ddc780282b03284d 100644 (file)
 #ifndef G_CHANNEL_MIDI_ACTION_RECORDER_H
 #define G_CHANNEL_MIDI_ACTION_RECORDER_H
 
-namespace giada::m::channel
-{
-struct Data;
-}
-namespace giada::m::eventDispatcher
-{
-struct Event;
-}
-namespace giada::m::midiActionRecorder
+#include "core/eventDispatcher.h"
+
+namespace giada::m
 {
-struct Data
+class ActionRecorder;
+class Sequencer;
+class Channel;
+class MidiActionRecorder final
 {
+public:
+       MidiActionRecorder(ActionRecorder&, Sequencer&);
+
+       void react(Channel&, const EventDispatcher::Event&, bool canRecordActions);
+
+private:
+       ActionRecorder* m_actionRecorder;
+       Sequencer*      m_sequencer;
 };
 
-void react(channel::Data& ch, const eventDispatcher::Event& e);
-} // namespace giada::m::midiActionRecorder
+} // namespace giada::m
 
 #endif
index 3403eadbcdd74609ce53d7ccebc3c68a8f4e5244..ffc86a5c358028ad41d0059ff7cf468af921344b 100644 (file)
 #include "core/conf.h"
 #include <cassert>
 
-namespace giada::m::midiController
+namespace giada::m
 {
-namespace
+void MidiController::react(Channel& ch, const EventDispatcher::Event& e) const
 {
-ChannelStatus onFirstBeat_(const channel::Data& ch)
+       switch (e.type)
+       {
+       case EventDispatcher::EventType::KEY_PRESS:
+               ch.shared->playStatus.store(press(ch));
+               break;
+
+       case EventDispatcher::EventType::KEY_KILL:
+       case EventDispatcher::EventType::SEQUENCER_STOP:
+               ch.shared->playStatus.store(ChannelStatus::OFF);
+               break;
+
+       case EventDispatcher::EventType::SEQUENCER_REWIND:
+               ch.shared->playStatus.store(onFirstBeat(ch));
+
+       default:
+               break;
+       }
+}
+
+/* -------------------------------------------------------------------------- */
+
+void MidiController::advance(const Channel& ch, const Sequencer::Event& e) const
+{
+       if (e.type == Sequencer::EventType::FIRST_BEAT)
+               ch.shared->playStatus.store(onFirstBeat(ch));
+}
+
+/* -------------------------------------------------------------------------- */
+
+ChannelStatus MidiController::onFirstBeat(const Channel& ch) const
 {
-       ChannelStatus playStatus = ch.state->playStatus.load();
+       ChannelStatus playStatus = ch.shared->playStatus.load();
 
        if (playStatus == ChannelStatus::ENDING)
                playStatus = ChannelStatus::OFF;
@@ -47,9 +76,9 @@ ChannelStatus onFirstBeat_(const channel::Data& ch)
 
 /* -------------------------------------------------------------------------- */
 
-ChannelStatus press_(const channel::Data& ch)
+ChannelStatus MidiController::press(const Channel& ch) const
 {
-       ChannelStatus playStatus = ch.state->playStatus.load();
+       ChannelStatus playStatus = ch.shared->playStatus.load();
 
        switch (playStatus)
        {
@@ -72,39 +101,4 @@ ChannelStatus press_(const channel::Data& ch)
 
        return playStatus;
 }
-} // namespace
-
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-
-void react(channel::Data& ch, const eventDispatcher::Event& e)
-{
-       switch (e.type)
-       {
-
-       case eventDispatcher::EventType::KEY_PRESS:
-               ch.state->playStatus.store(press_(ch));
-               break;
-
-       case eventDispatcher::EventType::KEY_KILL:
-       case eventDispatcher::EventType::SEQUENCER_STOP:
-               ch.state->playStatus.store(ChannelStatus::OFF);
-               break;
-
-       case eventDispatcher::EventType::SEQUENCER_REWIND:
-               ch.state->playStatus.store(onFirstBeat_(ch));
-
-       default:
-               break;
-       }
-}
-
-/* -------------------------------------------------------------------------- */
-
-void advance(const channel::Data& ch, const sequencer::Event& e)
-{
-       if (e.type == sequencer::EventType::FIRST_BEAT)
-               ch.state->playStatus.store(onFirstBeat_(ch));
-}
-} // namespace giada::m::midiController
+} // namespace giada::m
index 8f73435325eeb3554b9c0a284542f22a2d6513a5..48b1a7975a0a7f6a1dcf883144550c81aa0bea5b 100644 (file)
 #ifndef G_CHANNEL_MIDI_CONTROLLER_H
 #define G_CHANNEL_MIDI_CONTROLLER_H
 
-namespace giada::m::channel
-{
-struct Data;
-}
-namespace giada::m::eventDispatcher
-{
-struct Event;
-}
-namespace giada::m::sequencer
-{
-struct Event;
-}
-namespace giada::m::midiController
+#include "core/sequencer.h"
+
+namespace giada::m
 {
-struct Data
+class Channel;
+class MidiController final
 {
-};
+public:
+       void advance(const Channel& ch, const Sequencer::Event& e) const;
+       void react(Channel& ch, const EventDispatcher::Event& e) const;
 
-void react(channel::Data& ch, const eventDispatcher::Event& e);
-void advance(const channel::Data& ch, const sequencer::Event& e);
-} // namespace giada::m::midiController
+private:
+       ChannelStatus onFirstBeat(const Channel& ch) const;
+       ChannelStatus press(const Channel& ch) const;
+};
+} // namespace giada::m
 
 #endif
index 48e2a9ff6a1dbb22dbd96f0b97dd76bc36578ed0..bb5492d242844fd96d9c7dcbc6aef2524b4a54ce 100644 (file)
 #include "midiLearner.h"
 #include "core/patch.h"
 
-namespace giada::m::midiLearner
+namespace giada::m
 {
-Data::Data(const patch::Channel& p)
+MidiLearner::MidiLearner()
+: enabled(false)
+, filter(-1)
+{
+}
+
+/* -------------------------------------------------------------------------- */
+
+MidiLearner::MidiLearner(const Patch::Channel& p)
 : enabled(p.midiIn)
 , filter(p.midiInFilter)
 , keyPress(p.midiInKeyPress)
@@ -46,8 +54,8 @@ Data::Data(const patch::Channel& p)
 
 /* -------------------------------------------------------------------------- */
 
-bool Data::isAllowed(int c) const
+bool MidiLearner::isAllowed(int c) const
 {
        return enabled && (filter == -1 || filter == c);
 }
-} // namespace giada::m::midiLearner
\ No newline at end of file
+} // namespace giada::m
\ No newline at end of file
index e1e95c28ed5c71cc545ad36020550c5ab91adcc0..286b3cc4b9560d7149f1e64d8f2955332edd3c65 100644 (file)
 #define G_CHANNEL_MIDI_LEARNER_H
 
 #include "core/midiLearnParam.h"
+#include "core/patch.h"
 
-namespace giada::m::patch
+namespace giada::m
 {
-struct Channel;
-}
-namespace giada::m::midiLearner
+class MidiLearner final
 {
-struct Data
-{
-       Data() = default;
-       Data(const patch::Channel&);
-       Data(const Data&) = default;
+public:
+       MidiLearner();
+       MidiLearner(const Patch::Channel&);
+       MidiLearner(const MidiLearner&) = default;
 
        /* isAllowed
     Tells whether the MIDI channel 'c' is enabled to receive MIDI data. */
@@ -69,6 +67,6 @@ struct Data
        MidiLearnParam readActions; // Sample Channels only
        MidiLearnParam pitch;       // Sample Channels only
 };
-} // namespace giada::m::midiLearner
+} // namespace giada::m
 
 #endif
index c6f2ca438192cedf2f8d6ca14352b7859b0a4b22..687c059765942ea46d1a33cdfef41514a9cf9276 100644 (file)
  *
  * -------------------------------------------------------------------------- */
 
-#include "midiLighter.h"
-#include "core/channels/channel.h"
+#include "core/channels/midiLighter.h"
 #include "core/kernelMidi.h"
-#include "core/midiMapConf.h"
-#include "core/mixer.h"
+#include "core/midiMapper.h"
 
-namespace giada::m::midiLighter
+namespace giada::m
 {
-namespace
+template <typename KernelMidiI>
+MidiLighter<KernelMidiI>::MidiLighter(MidiMapper<KernelMidiI>& m)
+: enabled(false)
+, m_midiMapper(&m)
 {
-void sendMute_(channel::Data& ch, uint32_t l_mute)
-{
-       if (ch.mute)
-               kernelMidi::sendMidiLightning(l_mute, midimap::midimap.muteOn);
-       else
-               kernelMidi::sendMidiLightning(l_mute, midimap::midimap.muteOff);
 }
 
 /* -------------------------------------------------------------------------- */
 
-void sendSolo_(channel::Data& ch, uint32_t l_solo)
+template <typename KernelMidiI>
+MidiLighter<KernelMidiI>::MidiLighter(MidiMapper<KernelMidiI>& m, const Patch::Channel& p)
+: enabled(p.midiOutL)
+, playing(p.midiOutLplaying)
+, mute(p.midiOutLmute)
+, solo(p.midiOutLsolo)
+, m_midiMapper(&m)
 {
-       if (ch.solo)
-               kernelMidi::sendMidiLightning(l_solo, midimap::midimap.soloOn);
-       else
-               kernelMidi::sendMidiLightning(l_solo, midimap::midimap.soloOff);
 }
 
 /* -------------------------------------------------------------------------- */
 
-void sendStatus_(channel::Data& ch, uint32_t l_playing, bool audible)
+template <typename KernelMidiI>
+void MidiLighter<KernelMidiI>::sendStatus(ChannelStatus status, bool audible)
 {
-       switch (ch.state->playStatus.load())
-       {
+       const MidiMap& midiMap   = m_midiMapper->currentMap;
+       const uint32_t l_playing = playing.getValue();
+
+       if (l_playing == 0x0)
+               return;
 
+       switch (status)
+       {
        case ChannelStatus::OFF:
-               kernelMidi::sendMidiLightning(l_playing, midimap::midimap.stopped);
+               m_midiMapper->sendMidiLightning(l_playing, midiMap.stopped);
                break;
 
        case ChannelStatus::WAIT:
-               kernelMidi::sendMidiLightning(l_playing, midimap::midimap.waiting);
+               m_midiMapper->sendMidiLightning(l_playing, midiMap.waiting);
                break;
 
        case ChannelStatus::ENDING:
-               kernelMidi::sendMidiLightning(l_playing, midimap::midimap.stopping);
+               m_midiMapper->sendMidiLightning(l_playing, midiMap.stopping);
                break;
 
        case ChannelStatus::PLAY:
-               kernelMidi::sendMidiLightning(l_playing, audible ? midimap::midimap.playing : midimap::midimap.playingInaudible);
+               m_midiMapper->sendMidiLightning(l_playing, audible ? midiMap.playing : midiMap.playingInaudible);
                break;
 
        default:
                break;
        }
 }
-} // namespace
 
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
 /* -------------------------------------------------------------------------- */
 
-Data::Data(const patch::Channel& p)
-: enabled(p.midiOutL)
-, playing(p.midiOutLplaying)
-, mute(p.midiOutLmute)
-, solo(p.midiOutLsolo)
+template <typename KernelMidiI>
+void MidiLighter<KernelMidiI>::sendMute(bool isMuted)
 {
+       const MidiMap& midiMap = m_midiMapper->currentMap;
+       const uint32_t l_mute  = mute.getValue();
+
+       if (l_mute != 0x0)
+               m_midiMapper->sendMidiLightning(l_mute, isMuted ? midiMap.muteOn : midiMap.muteOff);
 }
 
 /* -------------------------------------------------------------------------- */
 
-void react(channel::Data& ch, const eventDispatcher::Event& e, bool audible)
+template <typename KernelMidiI>
+void MidiLighter<KernelMidiI>::sendSolo(bool isSoloed)
 {
-       if (!ch.midiLighter.enabled)
-               return;
-
-       uint32_t l_playing = ch.midiLighter.playing.getValue();
-       uint32_t l_mute    = ch.midiLighter.mute.getValue();
-       uint32_t l_solo    = ch.midiLighter.solo.getValue();
-
-       switch (e.type)
-       {
-
-       case eventDispatcher::EventType::KEY_PRESS:
-       case eventDispatcher::EventType::KEY_RELEASE:
-       case eventDispatcher::EventType::KEY_KILL:
-       case eventDispatcher::EventType::SEQUENCER_STOP:
-               if (l_playing != 0x0)
-                       sendStatus_(ch, l_playing, audible);
-               break;
+       const MidiMap& midiMap = m_midiMapper->currentMap;
+       const uint32_t l_solo  = solo.getValue();
 
-       case eventDispatcher::EventType::CHANNEL_MUTE:
-               if (l_mute != 0x0)
-                       sendMute_(ch, l_mute);
-               break;
+       if (l_solo != 0x0)
+               m_midiMapper->sendMidiLightning(l_solo, isSoloed ? midiMap.soloOn : midiMap.soloOff);
+}
 
-       case eventDispatcher::EventType::CHANNEL_SOLO:
-               if (l_solo != 0x0)
-                       sendSolo_(ch, l_solo);
-               break;
+/* -------------------------------------------------------------------------- */
 
-       default:
-               break;
-       }
-}
-} // namespace giada::m::midiLighter
\ No newline at end of file
+template struct MidiLighter<KernelMidi>;
+#ifdef WITH_TESTS
+template struct MidiLighter<KernelMidiMock>;
+#endif
+} // namespace giada::m
\ No newline at end of file
index 483d21e8b08cb2fae6129d5a37aad5f54abdc013..b34dbc0a20ea191ce40889be422e06b37ee5a915 100644 (file)
 #ifndef G_CHANNEL_MIDI_LIGHTER_H
 #define G_CHANNEL_MIDI_LIGHTER_H
 
+#include "core/eventDispatcher.h"
 #include "core/midiLearnParam.h"
+#include "core/midiMapper.h"
+#include "core/patch.h"
 
-namespace giada::m::channel
+namespace giada::m
 {
-struct Data;
-}
-namespace giada::m::patch
+template <typename KernelMidiI>
+class MidiLighter final
 {
-struct Channel;
-}
-namespace giada::m::eventDispatcher
-{
-struct Event;
-}
-namespace giada::m::midiLighter
-{
-struct Data
-{
-       Data() = default;
-       Data(const patch::Channel& p);
-       Data(const Data& o) = default;
+public:
+       MidiLighter(MidiMapper<KernelMidiI>&);
+       MidiLighter(MidiMapper<KernelMidiI>&, const Patch::Channel&);
+       MidiLighter(const MidiLighter& o) = default;
+
+       void sendStatus(ChannelStatus, bool audible);
+       void sendMute(bool isMuted);
+       void sendSolo(bool isSoloed);
 
        /* enabled
-    Tells whether MIDI ligthing is enabled or not. */
+    Tells whether MIDI lighting is enabled or not. */
 
        bool enabled;
 
-       /* MIDI learning fields for MIDI ligthing. */
+       /* MIDI learning fields for MIDI lighting. */
 
        MidiLearnParam playing;
        MidiLearnParam mute;
        MidiLearnParam solo;
+
+private:
+       MidiMapper<KernelMidiI>* m_midiMapper;
 };
 
-void react(channel::Data& ch, const eventDispatcher::Event& e, bool audible);
-} // namespace giada::m::midiLighter
+extern template struct MidiLighter<KernelMidi>;
+#ifdef WITH_TESTS
+extern template struct MidiLighter<KernelMidiMock>;
+#endif
+} // namespace giada::m
 
 #endif
index 595d682724a1c7ea5b602c7853acd07589640f18..ada713a3c97b637fffe7fcefffe8a365c85dbe82 100644 (file)
 #include "core/mixer.h"
 #include "core/plugins/pluginHost.h"
 
-namespace giada::m::midiReceiver
+namespace giada::m
 {
-namespace
+void MidiReceiver::react(const Channel& ch, const EventDispatcher::Event& e) const
 {
-void sendToPlugins_(const channel::Data& ch, const MidiEvent& e, Frame localFrame)
-{
-       juce::MidiMessage message = juce::MidiMessage(
-           e.getStatus(),
-           e.getNote(),
-           e.getVelocity());
-       ch.buffer->midi.addEvent(message, localFrame);
+       switch (e.type)
+       {
+       case EventDispatcher::EventType::MIDI:
+               parseMidi(ch, std::get<Action>(e.data).event);
+               break;
+
+       case EventDispatcher::EventType::KEY_KILL:
+       case EventDispatcher::EventType::SEQUENCER_STOP:
+       case EventDispatcher::EventType::SEQUENCER_REWIND:
+               sendToPlugins(ch, MidiEvent(G_MIDI_ALL_NOTES_OFF), 0);
+               break;
+
+       default:
+               break;
+       }
 }
 
 /* -------------------------------------------------------------------------- */
 
-void parseMidi_(const channel::Data& ch, const MidiEvent& e)
+void MidiReceiver::advance(const Channel& ch, const Sequencer::Event& e) const
 {
-       /* Now all messages are turned into Channel-0 messages. Giada doesn't care 
-       about holding MIDI channel information. Moreover, having all internal 
-       messages on channel 0 is way easier. Then send it to plug-ins. */
-
-       MidiEvent flat(e);
-       flat.setChannel(0);
-       sendToPlugins_(ch, flat, /*delta=*/0);
+       if (e.type == Sequencer::EventType::ACTIONS && ch.isPlaying())
+               for (const Action& action : *e.actions)
+                       if (action.channelId == ch.id)
+                               sendToPlugins(ch, action.event, e.delta);
 }
-} // namespace
 
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
 /* -------------------------------------------------------------------------- */
 
-void react(const channel::Data& ch, const eventDispatcher::Event& e)
+void MidiReceiver::render(const Channel& ch, PluginHost& pluginHost) const
 {
-       switch (e.type)
-       {
-
-       case eventDispatcher::EventType::MIDI:
-               parseMidi_(ch, std::get<Action>(e.data).event);
-               break;
+       ch.shared->midiBuffer.clear();
 
-       case eventDispatcher::EventType::KEY_KILL:
-       case eventDispatcher::EventType::SEQUENCER_STOP:
-       case eventDispatcher::EventType::SEQUENCER_REWIND:
-               sendToPlugins_(ch, MidiEvent(G_MIDI_ALL_NOTES_OFF), 0);
-               break;
-
-       default:
-               break;
+       MidiEvent e;
+       while (ch.shared->midiQueue.pop(e))
+       {
+               juce::MidiMessage message = juce::MidiMessage(
+                   e.getStatus(),
+                   e.getNote(),
+                   e.getVelocity());
+               ch.shared->midiBuffer.addEvent(message, e.getDelta());
        }
+
+       pluginHost.processStack(ch.shared->audioBuffer, ch.plugins, &ch.shared->midiBuffer);
 }
 
 /* -------------------------------------------------------------------------- */
 
-void advance(const channel::Data& ch, const sequencer::Event& e)
+void MidiReceiver::sendToPlugins(const Channel& ch, const MidiEvent& e, Frame localFrame) const
 {
-       if (e.type == sequencer::EventType::ACTIONS && ch.isPlaying())
-               for (const Action& action : *e.actions)
-                       if (action.channelId == ch.id)
-                               sendToPlugins_(ch, action.event, e.delta);
+       ch.shared->midiQueue.push(MidiEvent(e.getRaw(), localFrame));
 }
 
 /* -------------------------------------------------------------------------- */
 
-void render(const channel::Data& ch)
+void MidiReceiver::parseMidi(const Channel& ch, const MidiEvent& e) const
 {
-       pluginHost::processStack(ch.buffer->audio, ch.plugins, &ch.buffer->midi);
-       ch.buffer->midi.clear();
+       /* Now all messages are turned into Channel-0 messages. Giada doesn't care 
+       about holding MIDI channel information. Moreover, having all internal 
+       messages on channel 0 is way easier. Then send it to plug-ins. */
+
+       MidiEvent flat(e);
+       flat.setChannel(0);
+       sendToPlugins(ch, flat, /*delta=*/0);
 }
-} // namespace giada::m::midiReceiver
+} // namespace giada::m
 
 #endif // WITH_VST
index b2f7aa828f9620f65d57d44bb141f84cc5890019..050bc4f773ccff0957ab9231460bea28211ae045 100644 (file)
 
 #ifdef WITH_VST
 
-namespace giada::m::channel
-{
-struct Data;
-}
-namespace giada::m::eventDispatcher
-{
-struct Event;
-}
-namespace giada::m::sequencer
-{
-struct Event;
-}
-namespace giada::m::midiReceiver
+#include "core/sequencer.h"
+
+namespace giada::m
 {
-struct Data
+class PluginHost;
+class Channel;
+class MidiReceiver final
 {
-};
+public:
+       void react(const Channel& ch, const EventDispatcher::Event& e) const;
+       void advance(const Channel& ch, const Sequencer::Event& e) const;
+       void render(const Channel& ch, PluginHost& plugiHost) const;
 
-void react(const channel::Data& ch, const eventDispatcher::Event& e);
-void advance(const channel::Data& ch, const sequencer::Event& e);
-void render(const channel::Data& ch);
-} // namespace giada::m::midiReceiver
+private:
+       void sendToPlugins(const Channel& ch, const MidiEvent& e, Frame localFrame) const;
+       void parseMidi(const Channel& ch, const MidiEvent& e) const;
+};
+} // namespace giada::m
 
 #endif // WITH_VST
 
index e4994ca2f318c0b3a22b8f5a53344622b42025dd..b50fc9ab1e8ebf1a2fc18ade960781e0edfbf076 100644 (file)
  *
  * -------------------------------------------------------------------------- */
 
-#include "midiSender.h"
+#include "core/channels/midiSender.h"
 #include "core/channels/channel.h"
 #include "core/kernelMidi.h"
 #include "core/mixer.h"
 
-namespace giada::m::midiSender
+namespace giada::m
 {
-namespace
+MidiSender::MidiSender(KernelMidi& k)
+: kernelMidi(&k)
+, enabled(false)
+, filter(0)
 {
-void send_(const channel::Data& ch, MidiEvent e)
-{
-       e.setChannel(ch.midiSender->filter);
-       kernelMidi::send(e.getRaw());
 }
 
 /* -------------------------------------------------------------------------- */
 
-void parseActions_(const channel::Data& ch, const std::vector<Action>& as)
+MidiSender::MidiSender(const Patch::Channel& p, KernelMidi& k)
+: kernelMidi(&k)
+, enabled(p.midiOut)
+, filter(p.midiOutChan)
 {
-       for (const Action& a : as)
-               if (a.channelId == ch.id)
-                       send_(ch, a.event);
 }
-} // namespace
 
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
 /* -------------------------------------------------------------------------- */
 
-Data::Data(const patch::Channel& p)
-: enabled(p.midiOut)
-, filter(p.midiOutChan)
+void MidiSender::react(const Channel& ch, const EventDispatcher::Event& e)
 {
+       if (!ch.isPlaying() || !enabled || ch.isMuted())
+               return;
+
+       if (e.type == EventDispatcher::EventType::KEY_KILL ||
+           e.type == EventDispatcher::EventType::SEQUENCER_STOP)
+               send(MidiEvent(G_MIDI_ALL_NOTES_OFF));
 }
 
 /* -------------------------------------------------------------------------- */
 
-void react(const channel::Data& ch, const eventDispatcher::Event& e)
+void MidiSender::advance(const Channel& ch, const Sequencer::Event& e) const
 {
-       if (!ch.isPlaying() || !ch.midiSender->enabled)
+       if (!ch.isPlaying() || !enabled || ch.isMuted())
                return;
+       if (e.type == Sequencer::EventType::ACTIONS)
+               parseActions(ch, *e.actions);
+}
+
+/* -------------------------------------------------------------------------- */
 
-       if (e.type == eventDispatcher::EventType::KEY_KILL ||
-           e.type == eventDispatcher::EventType::SEQUENCER_STOP)
-               send_(ch, MidiEvent(G_MIDI_ALL_NOTES_OFF));
+void MidiSender::send(MidiEvent e) const
+{
+       e.setChannel(filter);
+       kernelMidi->send(e.getRaw());
 }
 
 /* -------------------------------------------------------------------------- */
 
-void advance(const channel::Data& ch, const sequencer::Event& e)
+void MidiSender::parseActions(const Channel& ch, const std::vector<Action>& as) const
 {
-       if (!ch.midiSender->enabled)
-               return;
-       if (e.type == sequencer::EventType::ACTIONS)
-               parseActions_(ch, *e.actions);
+       for (const Action& a : as)
+               if (a.channelId == ch.id)
+                       send(a.event);
 }
-} // namespace giada::m::midiSender
+} // namespace giada::m
\ No newline at end of file
index dbecca398e71a6b930bcf51ccad5d68b31576e86..a405aff4987b6615d4c7c6e27df8718a5edfe5ba 100644 (file)
 #ifndef G_CHANNEL_MIDI_SENDER_H
 #define G_CHANNEL_MIDI_SENDER_H
 
-namespace giada::m::channel
-{
-struct Data;
-}
-namespace giada::m::patch
-{
-struct Channel;
-}
-namespace giada::m::eventDispatcher
-{
-struct Event;
-}
-namespace giada::m::sequencer
-{
-struct Event;
-}
-namespace giada::m::midiSender
+#include "core/patch.h"
+#include "core/sequencer.h"
+
+namespace giada::m
 {
-struct Data
+class KernelMidi;
+class Channel;
+class MidiSender final
 {
-       Data() = default;
-       Data(const patch::Channel& p);
-       Data(const Data& o) = default;
+public:
+       MidiSender(KernelMidi&);
+       MidiSender(const Patch::Channel& p, KernelMidi&);
+       MidiSender(const MidiSender& o) = default;
+
+       void react(const Channel& ch, const EventDispatcher::Event& e);
+       void advance(const Channel& ch, const Sequencer::Event& e) const;
+
+       KernelMidi* kernelMidi;
 
        /* enabled
     Tells whether MIDI output is enabled or not. */
@@ -60,10 +55,11 @@ struct Data
     Which MIDI channel data should be sent to. */
 
        int filter;
-};
 
-void react(const channel::Data& ch, const eventDispatcher::Event& e);
-void advance(const channel::Data& ch, const sequencer::Event& e);
-} // namespace giada::m::midiSender
+private:
+       void send(MidiEvent e) const;
+       void parseActions(const Channel& ch, const std::vector<Action>& as) const;
+};
+} // namespace giada::m
 
 #endif
index cbfd7bd1a7d78039589935cc550b851cb433b5cf..cb0445cdbb92dc3833e1bd90fa0cae56fde84332 100644 (file)
  *
  * -------------------------------------------------------------------------- */
 
-#include "sampleActionRecorder.h"
-#include "core/action.h"
+#include "core/channels/sampleActionRecorder.h"
 #include "core/channels/channel.h"
-#include "core/clock.h"
-#include "core/conf.h"
 #include "core/eventDispatcher.h"
-#include "core/mixer.h"
-#include "core/recManager.h"
-#include "core/recorderHandler.h"
+#include "src/core/actions/action.h"
+#include "src/core/actions/actionRecorder.h"
 #include <cassert>
 
-namespace giada::m::sampleActionRecorder
+namespace giada::m
 {
-namespace
+SampleActionRecorder::SampleActionRecorder(ActionRecorder& a, Sequencer& s)
+: m_actionRecorder(&a)
+, m_sequencer(&s)
 {
-void record_(channel::Data& ch, int note);
-void onKeyPress_(channel::Data& ch);
-void toggleReadActions_(channel::Data& ch);
-void startReadActions_(channel::Data& ch);
-void stopReadActions_(channel::Data& ch, ChannelStatus curRecStatus);
-void killReadActions_(channel::Data& ch);
-bool canRecord_(const channel::Data& ch);
-
-/* -------------------------------------------------------------------------- */
-
-bool canRecord_(const channel::Data& ch)
-{
-       return recManager::isRecordingAction() &&
-              clock::isRunning() &&
-              !recManager::isRecordingInput() &&
-              !ch.samplePlayer->isAnyLoopMode();
 }
 
 /* -------------------------------------------------------------------------- */
 
-void onKeyPress_(channel::Data& ch)
+void SampleActionRecorder::react(Channel& ch, const EventDispatcher::Event& e, bool treatRecsAsLoops,
+    bool seqIsRunning, bool canRecordActions) const
 {
-       if (!canRecord_(ch))
+       if (!ch.hasWave())
                return;
-       record_(ch, MidiEvent::NOTE_ON);
 
-       /* Skip reading actions when recording on ChannelMode::SINGLE_PRESS to 
-       prevent existing actions to interfere with the keypress/keyrel combo. */
+       canRecordActions = canRecordActions && !ch.samplePlayer->isAnyLoopMode();
 
-       if (ch.samplePlayer->mode == SamplePlayerMode::SINGLE_PRESS)
-               ch.state->readActions.store(false);
+       switch (e.type)
+       {
+       case EventDispatcher::EventType::KEY_PRESS:
+               if (canRecordActions)
+                       onKeyPress(ch);
+               break;
+
+       case EventDispatcher::EventType::KEY_RELEASE:
+               /* Record a stop event only if channel is SINGLE_PRESS. For any other 
+               mode the key release event is meaningless. */
+               if (canRecordActions && ch.samplePlayer->mode == SamplePlayerMode::SINGLE_PRESS)
+                       record(ch, MidiEvent::NOTE_OFF);
+               break;
+
+       case EventDispatcher::EventType::KEY_KILL:
+               if (canRecordActions)
+                       record(ch, MidiEvent::NOTE_KILL);
+               break;
+
+       case EventDispatcher::EventType::CHANNEL_TOGGLE_READ_ACTIONS:
+               if (ch.hasActions)
+                       toggleReadActions(ch, treatRecsAsLoops, seqIsRunning);
+               break;
+
+       case EventDispatcher::EventType::CHANNEL_KILL_READ_ACTIONS:
+               /* Killing Read Actions, i.e. shift + click on 'R' button is meaningful 
+               only when the conf::treatRecsAsLoops is true. */
+               if (treatRecsAsLoops)
+                       killReadActions(ch);
+               break;
+
+       default:
+               break;
+       }
 }
 
 /* -------------------------------------------------------------------------- */
 
-void record_(channel::Data& ch, int note)
+void SampleActionRecorder::record(Channel& ch, int note) const
 {
-       recorderHandler::liveRec(ch.id, MidiEvent(note, 0, 0),
-           clock::quantize(clock::getCurrentFrame()));
-
+       m_actionRecorder->liveRec(ch.id, MidiEvent(note, 0, 0), m_sequencer->getCurrentFrameQuantized());
        ch.hasActions = true;
 }
 
 /* -------------------------------------------------------------------------- */
 
-void toggleReadActions_(channel::Data& ch)
+void SampleActionRecorder::onKeyPress(Channel& ch) const
 {
-       /* When you start reading actions while conf::treatRecsAsLoops is true, the
-       value ch.state->readActions actually is not set to true immediately, because
-       the channel is in wait mode (REC_WAITING). readActions will become true on
-       the next first beat. So a 'stop rec' command should occur also when
-       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. */
+       record(ch, MidiEvent::NOTE_ON);
 
-       if (!ch.hasActions)
-               return;
-
-       const bool          readActions = ch.state->readActions.load();
-       const ChannelStatus recStatus   = ch.state->recStatus.load();
+       /* Skip reading actions when recording on ChannelMode::SINGLE_PRESS to 
+       prevent existing actions to interfere with the keypress/keyrel combo. */
 
-       if (readActions || (!readActions && recStatus == ChannelStatus::WAIT))
-               stopReadActions_(ch, recStatus);
-       else
-               startReadActions_(ch);
+       if (ch.samplePlayer->mode == SamplePlayerMode::SINGLE_PRESS)
+               ch.shared->readActions.store(false);
 }
 
 /* -------------------------------------------------------------------------- */
 
-void startReadActions_(channel::Data& ch)
+void SampleActionRecorder::startReadActions(Channel& ch, bool treatRecsAsLoops) const
 {
-       if (conf::conf.treatRecsAsLoops)
-               ch.state->recStatus.store(ChannelStatus::WAIT);
+       if (treatRecsAsLoops)
+               ch.shared->recStatus.store(ChannelStatus::WAIT);
        else
        {
-               ch.state->recStatus.store(ChannelStatus::PLAY);
-               ch.state->readActions.store(true);
+               ch.shared->recStatus.store(ChannelStatus::PLAY);
+               ch.shared->readActions.store(true);
        }
 }
 
 /* -------------------------------------------------------------------------- */
 
-void stopReadActions_(channel::Data& ch, ChannelStatus curRecStatus)
+void SampleActionRecorder::stopReadActions(Channel& ch, ChannelStatus curRecStatus,
+    bool treatRecsAsLoops, bool seqIsRunning) const
 {
-       /* First of all, if the clock is not running or treatRecsAsLoops is off, 
+       /* First of all, if the sequencer is not running or treatRecsAsLoops is off, 
        just stop and disable everything. Otherwise make sure a channel with actions
        behave like a dynamic one. */
 
-       if (!clock::isRunning() || !conf::conf.treatRecsAsLoops)
+       if (!seqIsRunning || !treatRecsAsLoops)
        {
-               ch.state->recStatus.store(ChannelStatus::OFF);
-               ch.state->readActions.store(false);
+               ch.shared->recStatus.store(ChannelStatus::OFF);
+               ch.shared->readActions.store(false);
        }
        else if (curRecStatus == ChannelStatus::WAIT)
-               ch.state->recStatus.store(ChannelStatus::OFF);
+               ch.shared->recStatus.store(ChannelStatus::OFF);
        else if (curRecStatus == ChannelStatus::ENDING)
-               ch.state->recStatus.store(ChannelStatus::PLAY);
+               ch.shared->recStatus.store(ChannelStatus::PLAY);
        else
-               ch.state->recStatus.store(ChannelStatus::ENDING);
+               ch.shared->recStatus.store(ChannelStatus::ENDING);
 }
 
 /* -------------------------------------------------------------------------- */
 
-void killReadActions_(channel::Data& ch)
+void SampleActionRecorder::toggleReadActions(Channel& ch, bool treatRecsAsLoops, bool seqIsRunning) const
 {
-       /* Killing Read Actions, i.e. shift + click on 'R' button is meaninful only 
-       when the conf::treatRecsAsLoops is true. */
+       /* When you start reading actions while conf::treatRecsAsLoops is true, the
+       value ch.shared->readActions actually is not set to true immediately, because
+       the channel is in wait mode (REC_WAITING). readActions will become true on
+       the next first beat. So a 'stop rec' command should occur also when
+       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 (!conf::conf.treatRecsAsLoops)
-               return;
-       ch.state->recStatus.store(ChannelStatus::OFF);
-       ch.state->readActions.store(false);
+       const bool          readActions = ch.shared->readActions.load();
+       const ChannelStatus recStatus   = ch.shared->recStatus.load();
+
+       if (readActions || (!readActions && recStatus == ChannelStatus::WAIT))
+               stopReadActions(ch, recStatus, treatRecsAsLoops, seqIsRunning);
+       else
+               startReadActions(ch, treatRecsAsLoops);
 }
-} // namespace
 
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
 /* -------------------------------------------------------------------------- */
 
-void react(channel::Data& ch, const eventDispatcher::Event& e)
+void SampleActionRecorder::killReadActions(Channel& ch) const
 {
-       if (!ch.hasWave())
-               return;
-
-       switch (e.type)
-       {
-
-       case eventDispatcher::EventType::KEY_PRESS:
-               onKeyPress_(ch);
-               break;
-
-               /* Record a stop event only if channel is SINGLE_PRESS. For any other 
-               mode the key release event is meaningless. */
-
-       case eventDispatcher::EventType::KEY_RELEASE:
-               if (canRecord_(ch) && ch.samplePlayer->mode == SamplePlayerMode::SINGLE_PRESS)
-                       record_(ch, MidiEvent::NOTE_OFF);
-               break;
-
-       case eventDispatcher::EventType::KEY_KILL:
-               if (canRecord_(ch))
-                       record_(ch, MidiEvent::NOTE_KILL);
-               break;
-
-       case eventDispatcher::EventType::CHANNEL_TOGGLE_READ_ACTIONS:
-               toggleReadActions_(ch);
-               break;
-
-       case eventDispatcher::EventType::CHANNEL_KILL_READ_ACTIONS:
-               killReadActions_(ch);
-               break;
-
-       default:
-               break;
-       }
+       ch.shared->recStatus.store(ChannelStatus::OFF);
+       ch.shared->readActions.store(false);
 }
-} // namespace giada::m::sampleActionRecorder
\ No newline at end of file
+} // namespace giada::m
\ No newline at end of file
index 4ecac81eff03ea7078618e41fa0476b0f6bfc0f2..7f86b099315933feb0d2c83efc50e1dc786f9274 100644 (file)
 #ifndef G_CHANNEL_SAMPLE_ACTION_RECORDER_H
 #define G_CHANNEL_SAMPLE_ACTION_RECORDER_H
 
-namespace giada::m::channel
-{
-struct Data;
-}
-namespace giada::m::eventDispatcher
-{
-struct Event;
-}
-namespace giada::m::sampleActionRecorder
+#include "core/eventDispatcher.h"
+
+namespace giada::m
 {
-struct Data
+class ActionRecorder;
+class Sequencer;
+class Channel;
+class SampleActionRecorder final
 {
+public:
+       SampleActionRecorder(ActionRecorder&, Sequencer&);
+
+       void react(Channel&, const EventDispatcher::Event&, bool treatRecsAsLoops,
+           bool seqIsRunning, bool canRecordActions) const;
+
+private:
+       void record(Channel&, int note) const;
+       void onKeyPress(Channel&) const;
+       void startReadActions(Channel&, bool treatRecsAsLoops) const;
+       void stopReadActions(Channel&, ChannelStatus, bool treatRecsAsLoops, bool seqIsRunning) const;
+       void toggleReadActions(Channel&, bool treatRecsAsLoops, bool seqIsRunning) const;
+       void killReadActions(Channel& ch) const;
+
+       ActionRecorder* m_actionRecorder;
+       Sequencer*      m_sequencer;
 };
 
-void react(channel::Data& ch, const eventDispatcher::Event& e);
-} // namespace giada::m::sampleActionRecorder
+} // namespace giada::m
 
 #endif
index d60df06eeb32a350e104b2ebb0cac9e85a9c5f0f..e029c346c839729a3f4da183a58f7b12b6e4e6c3 100644 (file)
  *
  * -------------------------------------------------------------------------- */
 
-#include "sampleAdvancer.h"
+#include "core/channels/sampleAdvancer.h"
 #include "core/channels/channel.h"
-#include "core/clock.h"
 #include <cassert>
 
-namespace giada::m::sampleAdvancer
+namespace giada::m
 {
-namespace
+void SampleAdvancer::onLastFrame(const Channel& ch, bool seqIsRunning) const
 {
-void rewind_(const channel::Data& ch, Frame localFrame)
+       const SamplePlayerMode mode   = ch.samplePlayer->mode;
+       const bool             isLoop = ch.samplePlayer->isAnyLoopMode();
+
+       switch (ch.shared->playStatus.load())
+       {
+       case ChannelStatus::PLAY:
+               /* Stop LOOP_* when the sequencer is off, or SINGLE_* except for
+               SINGLE_ENDLESS, which runs forever unless it's in ENDING mode. 
+               Other loop once modes are put in wait mode. */
+               if ((mode == SamplePlayerMode::SINGLE_BASIC ||
+                       mode == SamplePlayerMode::SINGLE_BASIC_PAUSE ||
+                       mode == SamplePlayerMode::SINGLE_PRESS ||
+                       mode == SamplePlayerMode::SINGLE_RETRIG) ||
+                   (isLoop && !seqIsRunning))
+                       stop(ch, 0);
+               else if (mode == SamplePlayerMode::LOOP_ONCE || mode == SamplePlayerMode::LOOP_ONCE_BAR)
+                       wait(ch);
+               break;
+
+       case ChannelStatus::ENDING:
+               stop(ch, 0);
+               break;
+
+       default:
+               break;
+       }
+}
+
+/* -------------------------------------------------------------------------- */
+
+void SampleAdvancer::advance(const Channel& ch, const Sequencer::Event& e) const
 {
-       ch.state->rewinding = true;
-       ch.state->offset    = localFrame;
+       switch (e.type)
+       {
+       case Sequencer::EventType::FIRST_BEAT:
+               onFirstBeat(ch, e.delta);
+               break;
+
+       case Sequencer::EventType::BAR:
+               onBar(ch, e.delta);
+               break;
+
+       case Sequencer::EventType::REWIND:
+               rewind(ch, e.delta);
+               break;
+
+       case Sequencer::EventType::ACTIONS:
+               if (ch.shared->readActions.load() == true)
+                       parseActions(ch, *e.actions, e.delta);
+               break;
+
+       default:
+               break;
+       }
 }
 
 /* -------------------------------------------------------------------------- */
 
-void stop_(const channel::Data& ch, Frame localFrame)
+void SampleAdvancer::rewind(const Channel& ch, Frame localFrame) const
 {
-       ch.state->playStatus.store(ChannelStatus::OFF);
-       ch.state->tracker.store(ch.samplePlayer->begin);
+       ch.shared->rewinding = true;
+       ch.shared->offset    = localFrame;
+}
+
+/* -------------------------------------------------------------------------- */
+
+void SampleAdvancer::stop(const Channel& ch, Frame localFrame) const
+{
+       ch.shared->playStatus.store(ChannelStatus::OFF);
+       ch.shared->tracker.store(ch.samplePlayer->begin);
 
        /*  Clear data in range [localFrame, (buffer.size)) if the event occurs in
     the middle of the buffer. TODO - samplePlayer should be responsible for this*/
 
        if (localFrame != 0)
-               ch.buffer->audio.clear(localFrame);
+               ch.shared->audioBuffer.clear(localFrame);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void SampleAdvancer::play(const Channel& ch, Frame localFrame) const
+{
+       ch.shared->playStatus.store(ChannelStatus::PLAY);
+       ch.shared->offset = localFrame;
 }
 
 /* -------------------------------------------------------------------------- */
 
-void play_(const channel::Data& ch, Frame localFrame)
+void SampleAdvancer::wait(const Channel& ch) const
 {
-       ch.state->playStatus.store(ChannelStatus::PLAY);
-       ch.state->offset = localFrame;
+       ch.shared->playStatus.store(ChannelStatus::WAIT);
+       ch.shared->tracker.store(ch.samplePlayer->begin);
 }
 
 /* -------------------------------------------------------------------------- */
 
-void onFirstBeat_(const channel::Data& ch, Frame localFrame)
+void SampleAdvancer::onFirstBeat(const Channel& ch, Frame localFrame) const
 {
        G_DEBUG("onFirstBeat ch=" << ch.id << ", localFrame=" << localFrame);
 
-       ChannelStatus playStatus = ch.state->playStatus.load();
-       ChannelStatus recStatus  = ch.state->recStatus.load();
-       bool          isLoop     = ch.samplePlayer->isAnyLoopMode();
+       const ChannelStatus playStatus = ch.shared->playStatus.load();
+       const ChannelStatus recStatus  = ch.shared->recStatus.load();
+       const bool          isLoop     = ch.samplePlayer->isAnyLoopMode();
 
        switch (playStatus)
        {
        case ChannelStatus::PLAY:
                if (isLoop)
-                       rewind_(ch, localFrame);
+                       rewind(ch, localFrame);
                break;
 
        case ChannelStatus::WAIT:
-               play_(ch, localFrame);
+               play(ch, localFrame);
                break;
 
        case ChannelStatus::ENDING:
                if (isLoop)
-                       stop_(ch, localFrame);
+                       stop(ch, localFrame);
                break;
 
        default:
@@ -94,13 +159,13 @@ void onFirstBeat_(const channel::Data& ch, Frame localFrame)
        switch (recStatus)
        {
        case ChannelStatus::WAIT:
-               ch.state->recStatus.store(ChannelStatus::PLAY);
-               ch.state->readActions.store(true);
+               ch.shared->recStatus.store(ChannelStatus::PLAY);
+               ch.shared->readActions.store(true);
                break;
 
        case ChannelStatus::ENDING:
-               ch.state->recStatus.store(ChannelStatus::OFF);
-               ch.state->readActions.store(false);
+               ch.shared->recStatus.store(ChannelStatus::OFF);
+               ch.shared->readActions.store(false);
                break;
 
        default:
@@ -110,59 +175,46 @@ void onFirstBeat_(const channel::Data& ch, Frame localFrame)
 
 /* -------------------------------------------------------------------------- */
 
-void onBar_(const channel::Data& ch, Frame localFrame)
+void SampleAdvancer::onBar(const Channel& ch, Frame localFrame) const
 {
        G_DEBUG("onBar ch=" << ch.id << ", localFrame=" << localFrame);
 
-       ChannelStatus    playStatus = ch.state->playStatus.load();
-       SamplePlayerMode mode       = ch.samplePlayer->mode;
+       const ChannelStatus    playStatus = ch.shared->playStatus.load();
+       const SamplePlayerMode mode       = ch.samplePlayer->mode;
 
        if (playStatus == ChannelStatus::PLAY && (mode == SamplePlayerMode::LOOP_REPEAT ||
                                                     mode == SamplePlayerMode::LOOP_ONCE_BAR))
-               rewind_(ch, localFrame);
+               rewind(ch, localFrame);
        else if (playStatus == ChannelStatus::WAIT && mode == SamplePlayerMode::LOOP_ONCE_BAR)
-               ch.state->playStatus.store(ChannelStatus::PLAY);
+               play(ch, localFrame);
 }
 
 /* -------------------------------------------------------------------------- */
 
-void onNoteOn_(const channel::Data& ch, Frame localFrame)
+void SampleAdvancer::onNoteOn(const Channel& ch, Frame localFrame) const
 {
-       ChannelStatus playStatus = ch.state->playStatus.load();
-
-       if (playStatus == ChannelStatus::OFF)
-       {
-               playStatus = ChannelStatus::PLAY;
-       }
-       else if (playStatus == ChannelStatus::PLAY)
+       switch (ch.shared->playStatus.load())
        {
+       case ChannelStatus::OFF:
+               play(ch, localFrame);
+               break;
+
+       case ChannelStatus::PLAY:
                if (ch.samplePlayer->mode == SamplePlayerMode::SINGLE_RETRIG)
-                       rewind_(ch, localFrame);
+                       rewind(ch, localFrame);
                else
-                       playStatus = ChannelStatus::OFF;
-       }
-
-       ch.state->playStatus.store(playStatus);
-       ch.state->offset = localFrame;
-}
-
-/* -------------------------------------------------------------------------- */
-
-void onNoteOff_(const channel::Data& ch, Frame localFrame)
-{
-       ch.state->playStatus.store(ChannelStatus::OFF);
-       ch.state->tracker.store(ch.samplePlayer->begin);
-
-       /*  Clear data in range [localFrame, (buffer.size)) if the kill event occurs
-    in the middle of the buffer. */
+                       stop(ch, localFrame);
+               break;
 
-       if (localFrame != 0)
-               ch.buffer->audio.clear(localFrame);
+       default:
+               break;
+       }
 }
 
 /* -------------------------------------------------------------------------- */
 
-void parseActions_(const channel::Data& ch, const std::vector<Action>& as, Frame localFrame)
+void SampleAdvancer::parseActions(const Channel& ch,
+    const std::vector<Action>& as, Frame localFrame) const
 {
        if (ch.samplePlayer->isAnyLoopMode() || !ch.isReadingActions())
                return;
@@ -175,15 +227,12 @@ void parseActions_(const channel::Data& ch, const std::vector<Action>& as, Frame
                switch (a.event.getStatus())
                {
                case MidiEvent::NOTE_ON:
-                       onNoteOn_(ch, localFrame);
+                       onNoteOn(ch, localFrame);
                        break;
 
                case MidiEvent::NOTE_OFF:
-                       onNoteOff_(ch, localFrame);
-                       break;
-
                case MidiEvent::NOTE_KILL:
-                       onNoteOff_(ch, localFrame);
+                       stop(ch, localFrame);
                        break;
 
                default:
@@ -191,63 +240,4 @@ void parseActions_(const channel::Data& ch, const std::vector<Action>& as, Frame
                }
        }
 }
-} // namespace
-
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-
-void onLastFrame(const channel::Data& ch)
-{
-       ChannelStatus    playStatus = ch.state->playStatus.load();
-       SamplePlayerMode mode       = ch.samplePlayer->mode;
-       bool             isLoop     = ch.samplePlayer->isAnyLoopMode();
-       bool             running    = clock::isRunning();
-
-       if (playStatus == ChannelStatus::PLAY)
-       {
-               /* Stop LOOP_* when the sequencer is off, or SINGLE_* except for
-               SINGLE_ENDLESS, which runs forever unless it's in ENDING mode. 
-               Other loop once modes are put in wait mode. */
-               if ((mode == SamplePlayerMode::SINGLE_BASIC ||
-                       mode == SamplePlayerMode::SINGLE_PRESS ||
-                       mode == SamplePlayerMode::SINGLE_RETRIG) ||
-                   (isLoop && !running))
-                       playStatus = ChannelStatus::OFF;
-               else if (mode == SamplePlayerMode::LOOP_ONCE || mode == SamplePlayerMode::LOOP_ONCE_BAR)
-                       playStatus = ChannelStatus::WAIT;
-       }
-       else if (playStatus == ChannelStatus::ENDING)
-               playStatus = ChannelStatus::OFF;
-
-       ch.state->playStatus.store(playStatus);
-}
-
-/* -------------------------------------------------------------------------- */
-
-void advance(const channel::Data& ch, const sequencer::Event& e)
-{
-       switch (e.type)
-       {
-       case sequencer::EventType::FIRST_BEAT:
-               onFirstBeat_(ch, e.delta);
-               break;
-
-       case sequencer::EventType::BAR:
-               onBar_(ch, e.delta);
-               break;
-
-       case sequencer::EventType::REWIND:
-               rewind_(ch, e.delta);
-               break;
-
-       case sequencer::EventType::ACTIONS:
-               if (ch.state->readActions.load() == true)
-                       parseActions_(ch, *e.actions, e.delta);
-               break;
-
-       default:
-               break;
-       }
-}
-} // namespace giada::m::sampleAdvancer
\ No newline at end of file
+} // namespace giada::m
\ No newline at end of file
index c19b97ecda4ef190a73aa72d966ae1656c50fc0b..bf5a8d4d95f57ef4d5bf0568da9e38c1c2a5e60f 100644 (file)
 
 #include "core/sequencer.h"
 
-namespace giada::m::channel
+namespace giada::m
 {
-struct Data;
-}
-namespace giada::m::sampleAdvancer
+class Channel;
+class SampleAdvancer final
 {
-void onLastFrame(const channel::Data& ch);
-void advance(const channel::Data& ch, const sequencer::Event& e);
-} // namespace giada::m::sampleAdvancer
+public:
+       void onLastFrame(const Channel& ch, bool seqIsRunning) const;
+       void advance(const Channel& ch, const Sequencer::Event& e) const;
+
+private:
+       void rewind(const Channel& ch, Frame localFrame) const;
+       void stop(const Channel& ch, Frame localFrame) const;
+       void play(const Channel& ch, Frame localFrame) const;
+       void wait(const Channel& ch) const;
+       void onFirstBeat(const Channel& ch, Frame localFrame) const;
+       void onBar(const Channel& ch, Frame localFrame) const;
+       void onNoteOn(const Channel& ch, Frame localFrame) const;
+       void parseActions(const Channel& ch, const std::vector<Action>& as, Frame localFrame) const;
+};
+} // namespace giada::m
 
 #endif
index 78c67474eae06148795ba0424cee0a9ce8af2509..350d34112e5888d490eabe30fac177880b026442 100644 (file)
 
 #include "samplePlayer.h"
 #include "core/channels/channel.h"
-#include "core/clock.h"
 #include "core/wave.h"
-#include "core/waveManager.h"
+#include "deps/mcl-audio-buffer/src/audioBuffer.hpp"
 #include <algorithm>
 #include <cassert>
 
-namespace giada::m::samplePlayer
+namespace giada::m
 {
-namespace
-{
-bool shouldLoop_(const channel::Data& ch)
-{
-       ChannelStatus    playStatus = ch.state->playStatus.load();
-       SamplePlayerMode mode       = ch.samplePlayer->mode;
-
-       return (mode == SamplePlayerMode::LOOP_BASIC ||
-                  mode == SamplePlayerMode::LOOP_REPEAT ||
-                  mode == SamplePlayerMode::SINGLE_ENDLESS) &&
-              playStatus == ChannelStatus::PLAY;
-}
-
-/* -------------------------------------------------------------------------- */
-
-WaveReader::Result fillBuffer_(const channel::Data& ch, Frame start, Frame offset)
-{
-       AudioBuffer&      buffer     = ch.buffer->audio;
-       const WaveReader& waveReader = ch.samplePlayer->waveReader;
-
-       return waveReader.fill(buffer, start, ch.samplePlayer->end, offset, ch.samplePlayer->pitch);
-}
-
-/* -------------------------------------------------------------------------- */
-
-bool isPlaying_(const channel::Data& ch)
-{
-       return ch.samplePlayer->waveReader.wave != nullptr && ch.isPlaying();
-}
-
-/* -------------------------------------------------------------------------- */
-
-void setWave_(samplePlayer::Data& sp, Wave* w, float samplerateRatio)
-{
-       if (w == nullptr)
-       {
-               sp.waveReader.wave = nullptr;
-               return;
-       }
-
-       sp.waveReader.wave = w;
-
-       if (samplerateRatio != 1.0f)
-       {
-               sp.begin *= samplerateRatio;
-               sp.end *= samplerateRatio;
-               sp.shift *= samplerateRatio;
-       }
-}
-} // namespace
-
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-
-Data::Data(Resampler* r)
+SamplePlayer::SamplePlayer(Resampler* r)
 : pitch(G_DEFAULT_PITCH)
 , mode(SamplePlayerMode::SINGLE_BASIC)
+, shift(0)
+, begin(0)
+, end(0)
 , velocityAsVol(false)
 , waveReader(r)
 {
@@ -99,7 +46,7 @@ Data::Data(Resampler* r)
 
 /* -------------------------------------------------------------------------- */
 
-Data::Data(const patch::Channel& p, float samplerateRatio, Resampler* r)
+SamplePlayer::SamplePlayer(const Patch::Channel& p, float samplerateRatio, Resampler* r, Wave* w)
 : pitch(p.pitch)
 , mode(p.mode)
 , shift(p.shift)
@@ -107,19 +54,20 @@ Data::Data(const patch::Channel& p, float samplerateRatio, Resampler* r)
 , end(p.end)
 , velocityAsVol(p.midiInVeloAsVol)
 , waveReader(r)
+, onLastFrame(nullptr)
 {
-       setWave_(*this, waveManager::hydrateWave(p.waveId), samplerateRatio);
+       setWave(w, samplerateRatio);
 }
 
 /* -------------------------------------------------------------------------- */
 
-bool Data::hasWave() const { return waveReader.wave != nullptr; }
-bool Data::hasLogicalWave() const { return hasWave() && waveReader.wave->isLogical(); }
-bool Data::hasEditedWave() const { return hasWave() && waveReader.wave->isEdited(); }
+bool SamplePlayer::hasWave() const { return waveReader.wave != nullptr; }
+bool SamplePlayer::hasLogicalWave() const { return hasWave() && waveReader.wave->isLogical(); }
+bool SamplePlayer::hasEditedWave() const { return hasWave() && waveReader.wave->isEdited(); }
 
 /* -------------------------------------------------------------------------- */
 
-bool Data::isAnyLoopMode() const
+bool SamplePlayer::isAnyLoopMode() const
 {
        return mode == SamplePlayerMode::LOOP_BASIC ||
               mode == SamplePlayerMode::LOOP_ONCE ||
@@ -129,12 +77,12 @@ bool Data::isAnyLoopMode() const
 
 /* -------------------------------------------------------------------------- */
 
-Wave* Data::getWave() const
+Wave* SamplePlayer::getWave() const
 {
        return waveReader.wave;
 }
 
-ID Data::getWaveId() const
+ID SamplePlayer::getWaveId() const
 {
        if (hasWave())
                return waveReader.wave->id;
@@ -143,57 +91,45 @@ ID Data::getWaveId() const
 
 /* -------------------------------------------------------------------------- */
 
-Frame Data::getWaveSize() const
+Frame SamplePlayer::getWaveSize() const
 {
        return hasWave() ? waveReader.wave->getBuffer().countFrames() : 0;
 }
 
 /* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-
-void react(channel::Data& ch, const eventDispatcher::Event& e)
-{
-       if (e.type == eventDispatcher::EventType::CHANNEL_PITCH)
-               ch.samplePlayer->pitch = std::get<float>(e.data);
-}
 
-/* -------------------------------------------------------------------------- */
-
-void advance(const channel::Data& ch, const sequencer::Event& e)
+void SamplePlayer::react(const EventDispatcher::Event& e)
 {
-       sampleAdvancer::advance(ch, e);
+       if (e.type == EventDispatcher::EventType::CHANNEL_PITCH)
+               pitch = std::get<float>(e.data);
 }
 
 /* -------------------------------------------------------------------------- */
 
-void render(const channel::Data& ch)
+void SamplePlayer::render(const Channel& ch) const
 {
-       if (!isPlaying_(ch))
+       if (!isPlaying(ch))
                return;
 
-       const Frame begin = ch.samplePlayer->begin;
-       const Frame end   = ch.samplePlayer->end;
-
        /* Make sure tracker stays within begin-end range. */
 
-       Frame tracker = std::clamp(ch.state->tracker.load(), begin, end);
+       Frame tracker = std::clamp(ch.shared->tracker.load(), begin, end);
 
        /* If rewinding, fill the tail first, then reset the tracker to the begin
     point. The rest is performed as usual. */
 
-       if (ch.state->rewinding)
+       if (ch.shared->rewinding)
        {
                if (tracker < end)
                {
-                       fillBuffer_(ch, tracker, 0);
-                       ch.samplePlayer->waveReader.last();
+                       fillBuffer(ch, tracker, 0);
+                       waveReader.last();
                }
-               ch.state->rewinding = false;
+               ch.shared->rewinding = false;
                tracker             = begin;
        }
 
-       WaveReader::Result res = fillBuffer_(ch, tracker, ch.state->offset);
+       WaveReader::Result res = fillBuffer(ch, tracker, ch.shared->offset);
        tracker += res.used;
 
        /* If tracker has looped, special care is needed for the rendering. If the
@@ -202,53 +138,94 @@ void render(const channel::Data& ch)
 
        if (tracker >= end)
        {
-               ch.samplePlayer->waveReader.last();
+               assert(onLastFrame != nullptr);
+
                tracker = begin;
-               sampleAdvancer::onLastFrame(ch); // TODO - better moving this to samplerAdvancer::advance
-               if (shouldLoop_(ch) && res.generated < ch.buffer->audio.countFrames())
-                       tracker += fillBuffer_(ch, tracker, res.generated).used;
+               waveReader.last();
+               onLastFrame();
+               if (shouldLoop(ch) && res.generated < ch.shared->audioBuffer.countFrames())
+                       tracker += fillBuffer(ch, tracker, res.generated).used;
        }
 
-       ch.state->offset = 0;
-       ch.state->tracker.store(tracker);
+       ch.shared->offset = 0;
+       ch.shared->tracker.store(tracker);
 }
 
 /* -------------------------------------------------------------------------- */
 
-void loadWave(channel::Data& ch, Wave* w)
+void SamplePlayer::loadWave(Channel& ch, Wave* w)
 {
-       ch.samplePlayer->waveReader.wave = w;
+       waveReader.wave = w;
 
-       ch.state->tracker.store(0);
-       ch.samplePlayer->shift = 0;
-       ch.samplePlayer->begin = 0;
+       ch.shared->tracker.store(0);
+       shift = 0;
+       begin = 0;
 
        if (w != nullptr)
        {
-               ch.state->playStatus.store(ChannelStatus::OFF);
-               ch.name              = w->getBasename(/*ext=*/false);
-               ch.samplePlayer->end = w->getBuffer().countFrames() - 1;
+               ch.shared->playStatus.store(ChannelStatus::OFF);
+               ch.name = w->getBasename(/*ext=*/false);
+               end     = w->getBuffer().countFrames() - 1;
        }
        else
        {
-               ch.state->playStatus.store(ChannelStatus::EMPTY);
-               ch.name              = "";
-               ch.samplePlayer->end = 0;
+               ch.shared->playStatus.store(ChannelStatus::EMPTY);
+               ch.name = "";
+               end     = 0;
+       }
+}
+
+/* -------------------------------------------------------------------------- */
+
+void SamplePlayer::setWave(Wave* w, float samplerateRatio)
+{
+       if (w == nullptr)
+       {
+               waveReader.wave = nullptr;
+               return;
+       }
+
+       waveReader.wave = w;
+
+       if (samplerateRatio != 1.0f)
+       {
+               begin *= samplerateRatio;
+               end *= samplerateRatio;
+               shift *= samplerateRatio;
        }
 }
 
 /* -------------------------------------------------------------------------- */
 
-void setWave(channel::Data& ch, Wave* w, float samplerateRatio)
+void SamplePlayer::kickIn(Channel& ch, Frame f)
+{
+       ch.shared->tracker.store(f);
+       ch.shared->playStatus.store(ChannelStatus::PLAY);
+}
+
+/* -------------------------------------------------------------------------- */
+
+bool SamplePlayer::isPlaying(const Channel& ch) const
 {
-       setWave_(ch.samplePlayer.value(), w, samplerateRatio);
+       return waveReader.wave != nullptr && ch.isPlaying();
 }
 
 /* -------------------------------------------------------------------------- */
 
-void kickIn(channel::Data& ch, Frame f)
+WaveReader::Result SamplePlayer::fillBuffer(const Channel& ch, Frame start, Frame offset) const
 {
-       ch.state->tracker.store(f);
-       ch.state->playStatus.store(ChannelStatus::PLAY);
+       return waveReader.fill(ch.shared->audioBuffer, start, end, offset, pitch);
+}
+
+/* -------------------------------------------------------------------------- */
+
+bool SamplePlayer::shouldLoop(const Channel& ch) const
+{
+       const ChannelStatus playStatus = ch.shared->playStatus.load();
+
+       return (mode == SamplePlayerMode::LOOP_BASIC ||
+                  mode == SamplePlayerMode::LOOP_REPEAT ||
+                  mode == SamplePlayerMode::SINGLE_ENDLESS) &&
+              playStatus == ChannelStatus::PLAY;
 }
-} // namespace giada::m::samplePlayer
\ No newline at end of file
+} // namespace giada::m
\ No newline at end of file
index e8dff83ffd060790e9afdb369b555abb2f55162b..d9bcbcff20df6332881b7b511ad690be1674fe38 100644 (file)
 #ifndef G_CHANNEL_SAMPLE_PLAYER_H
 #define G_CHANNEL_SAMPLE_PLAYER_H
 
-#include "core/channels/sampleAdvancer.h"
-#include "core/channels/sampleReactor.h"
 #include "core/channels/waveReader.h"
 #include "core/const.h"
+#include "core/eventDispatcher.h"
+#include "core/patch.h"
+#include "core/sequencer.h"
 #include "core/types.h"
+#include <functional>
 
-namespace giada::m::channel
+namespace giada::m
 {
-struct Data;
-}
-namespace giada::m::patch
+class Channel;
+class SamplePlayer final
 {
-struct Channel;
-}
-namespace giada::m::samplePlayer
-{
-struct Data
-{
-       Data(Resampler* r);
-       Data(const patch::Channel& p, float samplerateRatio, Resampler* r);
-       Data(const Data& o) = default;
-       Data(Data&& o)      = default;
-       Data& operator=(const Data&) = default;
-       Data& operator=(Data&&) = default;
+public:
+       SamplePlayer(Resampler* r);
+       SamplePlayer(const Patch::Channel& p, float samplerateRatio, Resampler* r, Wave* w);
 
        bool  hasWave() const;
        bool  hasLogicalWave() const;
@@ -59,6 +51,27 @@ struct Data
        ID    getWaveId() const;
        Frame getWaveSize() const;
        Wave* getWave() const;
+       void  render(const Channel& ch) const;
+
+       void react(const EventDispatcher::Event& e);
+
+       /* loadWave
+       Loads Wave 'w' into channel ch and sets it up (name, markers, ...). */
+
+       void loadWave(Channel& ch, Wave* w);
+
+       /* setWave
+       Just sets the pointer to a Wave object. Used during de-serialization. The
+       ratio is used to adjust begin/end points in case of patch vs. conf sample
+       rate mismatch. If nullptr, set the wave to invalid. */
+
+       void setWave(Wave* w, float samplerateRatio);
+
+       /* kickIn
+       Starts the player right away at frame 'f'. Used when launching a loop after
+       being live recorded. */
+
+       void kickIn(Channel& ch, Frame f);
 
        float            pitch;
        SamplePlayerMode mode;
@@ -67,29 +80,14 @@ struct Data
        Frame            end;
        bool             velocityAsVol; // Velocity drives volume
        WaveReader       waveReader;
-};
-
-void react(channel::Data& ch, const eventDispatcher::Event& e);
-void advance(const channel::Data& ch, const sequencer::Event& e);
-void render(const channel::Data& ch);
-
-/* loadWave
-Loads Wave 'w' into channel ch and sets it up (name, markers, ...). */
 
-void loadWave(channel::Data& ch, Wave* w);
+       std::function<void()> onLastFrame;
 
-/* setWave
-Just sets the pointer to a Wave object. Used during de-serialization. The
-ratio is used to adjust begin/end points in case of patch vs. conf sample
-rate mismatch. If nullptr, set the wave to invalid. */
-
-void setWave(channel::Data& ch, Wave* w, float samplerateRatio);
-
-/* kickIn
-Starts the player right away at frame 'f'. Used when launching a loop after
-being live recorded. */
-
-void kickIn(channel::Data& ch, Frame f);
-} // namespace giada::m::samplePlayer
+private:
+       bool               isPlaying(const Channel& ch) const;
+       WaveReader::Result fillBuffer(const Channel& ch, Frame start, Frame offset) const;
+       bool               shouldLoop(const Channel& ch) const;
+};
+} // namespace giada::m
 
 #endif
index 378cb58fcf7db10bfe091814d2a2c54a0122874b..1358a5f3b48cb5a1716380e18395e722265763e1 100644 (file)
  *
  * -------------------------------------------------------------------------- */
 
-#include "sampleReactor.h"
+#include "core/channels/sampleReactor.h"
 #include "core/channels/channel.h"
-#include "core/clock.h"
 #include "core/conf.h"
-#include "src/core/model/model.h"
+#include "core/model/model.h"
 #include "utils/math.h"
 #include <cassert>
 
-namespace giada::m::sampleReactor
+namespace giada::m
 {
 namespace
 {
 constexpr int Q_ACTION_PLAY   = 0;
 constexpr int Q_ACTION_REWIND = 1;
+} // namespace
+
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+
+SampleReactor::SampleReactor(ID channelId, Sequencer& sequencer, model::Model& model)
+{
+       sequencer.quantizer.schedule(Q_ACTION_PLAY + channelId, [channelId, &model](Frame delta) {
+               Channel& ch      = model.get().getChannel(channelId);
+               ch.shared->offset = delta;
+               ch.shared->playStatus.store(ChannelStatus::PLAY);
+       });
 
-void          press_(channel::Data& ch, int velocity);
-void          release_(channel::Data& ch);
-void          kill_(channel::Data& ch);
-void          onStopBySeq_(channel::Data& ch);
-void          toggleReadActions_(channel::Data& ch);
-ChannelStatus pressWhileOff_(channel::Data& ch, int velocity, bool isLoop);
-ChannelStatus pressWhilePlay_(channel::Data& ch, SamplePlayerMode mode, bool isLoop);
-void          rewind_(channel::Data& ch, Frame localFrame = 0);
+       sequencer.quantizer.schedule(Q_ACTION_REWIND + channelId, [this, channelId, &model](Frame delta) {
+               Channel& ch = model.get().getChannel(channelId);
+               ch.isPlaying() ? rewind(ch, delta) : reset(ch);
+       });
+}
 
 /* -------------------------------------------------------------------------- */
 
-void press_(channel::Data& ch, int velocity)
+void SampleReactor::react(Channel& ch, const EventDispatcher::Event& e,
+    Sequencer& sequencer, const Conf::Data& conf) const
 {
-       ChannelStatus    playStatus = ch.state->playStatus.load();
-       SamplePlayerMode mode       = ch.samplePlayer->mode;
-       bool             isLoop     = ch.samplePlayer->isAnyLoopMode();
+       if (!ch.hasWave())
+               return;
 
-       switch (playStatus)
+       switch (e.type)
        {
-       case ChannelStatus::OFF:
-               playStatus = pressWhileOff_(ch, velocity, isLoop);
+       case EventDispatcher::EventType::KEY_PRESS:
+               press(ch, sequencer, std::get<int>(e.data));
                break;
 
-       case ChannelStatus::PLAY:
-               playStatus = pressWhilePlay_(ch, mode, isLoop);
+       case EventDispatcher::EventType::KEY_RELEASE:
+               release(ch, sequencer);
                break;
 
-       case ChannelStatus::WAIT:
-               playStatus = ChannelStatus::OFF;
+       case EventDispatcher::EventType::KEY_KILL:
+               kill(ch);
                break;
 
-       case ChannelStatus::ENDING:
-               playStatus = ChannelStatus::PLAY;
+       case EventDispatcher::EventType::SEQUENCER_STOP:
+               onStopBySeq(ch, conf.chansStopOnSeqHalt);
+               break;
+
+       case EventDispatcher::EventType::CHANNEL_TOGGLE_READ_ACTIONS:
+               toggleReadActions(ch, sequencer.isRunning(), conf.treatRecsAsLoops);
                break;
 
        default:
                break;
        }
-
-       ch.state->playStatus.store(playStatus);
 }
 
 /* -------------------------------------------------------------------------- */
 
-void release_(channel::Data& ch)
+void SampleReactor::rewind(Channel& ch, Frame localFrame) const
 {
-       /* Key release is meaningful only for SINGLE_PRESS modes. */
-
-       if (ch.samplePlayer->mode != SamplePlayerMode::SINGLE_PRESS)
-               return;
-
-       /* Kill it if it's SINGLE_PRESS is playing. Otherwise there might be a 
-       quantization step in progress that would play the channel later on: 
-       disable it. */
-
-       if (ch.state->playStatus.load() == ChannelStatus::PLAY)
-               kill_(ch);
-       else if (sequencer::quantizer.hasBeenTriggered())
-               sequencer::quantizer.clear();
+       ch.shared->rewinding = true;
+       ch.shared->offset    = localFrame;
 }
 
 /* -------------------------------------------------------------------------- */
 
-void kill_(channel::Data& ch)
+void SampleReactor::reset(Channel& ch) const
 {
-       ch.state->playStatus.store(ChannelStatus::OFF);
-       ch.state->tracker.store(ch.samplePlayer->begin);
+       ch.shared->tracker.store(ch.samplePlayer->begin);
 }
 
 /* -------------------------------------------------------------------------- */
 
-void onStopBySeq_(channel::Data& ch)
-{
-       G_DEBUG("onStopBySeq ch=" << ch.id);
-
-       ChannelStatus playStatus       = ch.state->playStatus.load();
-       bool          isReadingActions = ch.state->readActions.load();
-       bool          isLoop           = ch.samplePlayer->isAnyLoopMode();
-
-       switch (playStatus)
-       {
-
-       case ChannelStatus::WAIT:
-               /* Loop-mode channels in wait status get stopped right away. */
-               if (isLoop)
-                       ch.state->playStatus.store(ChannelStatus::OFF);
-               break;
-
-       case ChannelStatus::PLAY:
-               if (conf::conf.chansStopOnSeqHalt && (isLoop || isReadingActions))
-                       kill_(ch);
-               break;
-
-       default:
-               break;
-       }
-}
-
-/* -------------------------------------------------------------------------- */
-
-ChannelStatus pressWhileOff_(channel::Data& ch, int velocity, bool isLoop)
+ChannelStatus SampleReactor::pressWhileOff(Channel& ch, Sequencer& sequencer,
+    int velocity, bool isLoop) const
 {
        if (isLoop)
                return ChannelStatus::WAIT;
@@ -147,9 +118,9 @@ ChannelStatus pressWhileOff_(channel::Data& ch, int velocity, bool isLoop)
        if (ch.samplePlayer->velocityAsVol)
                ch.volume_i = u::math::map(velocity, G_MAX_VELOCITY, G_MAX_VOLUME);
 
-       if (clock::canQuantize())
+       if (sequencer.canQuantize())
        {
-               sequencer::quantizer.trigger(Q_ACTION_PLAY + ch.id);
+               sequencer.quantizer.trigger(Q_ACTION_PLAY + ch.id);
                return ChannelStatus::OFF;
        }
        else
@@ -158,103 +129,127 @@ ChannelStatus pressWhileOff_(channel::Data& ch, int velocity, bool isLoop)
 
 /* -------------------------------------------------------------------------- */
 
-ChannelStatus pressWhilePlay_(channel::Data& ch, SamplePlayerMode mode, bool isLoop)
+ChannelStatus SampleReactor::pressWhilePlay(Channel& ch, Sequencer& sequencer,
+    SamplePlayerMode mode, bool isLoop) const
 {
-       if (mode == SamplePlayerMode::SINGLE_RETRIG)
+       if (isLoop)
+               return ChannelStatus::ENDING;
+
+       switch (mode)
        {
-               if (clock::canQuantize())
-                       sequencer::quantizer.trigger(Q_ACTION_REWIND + ch.id);
+       case SamplePlayerMode::SINGLE_RETRIG:
+               if (sequencer.canQuantize())
+                       sequencer.quantizer.trigger(Q_ACTION_REWIND + ch.id);
                else
-                       rewind_(ch);
+                       rewind(ch, /*localFrame=*/0);
                return ChannelStatus::PLAY;
-       }
 
-       if (isLoop || mode == SamplePlayerMode::SINGLE_ENDLESS)
+       case SamplePlayerMode::SINGLE_ENDLESS:
                return ChannelStatus::ENDING;
 
-       if (mode == SamplePlayerMode::SINGLE_BASIC)
-       {
-               rewind_(ch);
+       case SamplePlayerMode::SINGLE_BASIC:
+               reset(ch);
                return ChannelStatus::OFF;
-       }
 
-       return ChannelStatus::OFF;
+       default:
+               return ChannelStatus::OFF;
+       }
 }
 
 /* -------------------------------------------------------------------------- */
 
-void toggleReadActions_(channel::Data& ch)
+void SampleReactor::press(Channel& ch, Sequencer& sequencer, int velocity) const
 {
-       if (clock::isRunning() && ch.state->recStatus.load() == ChannelStatus::PLAY && !conf::conf.treatRecsAsLoops)
-               kill_(ch);
-}
+       const SamplePlayerMode mode   = ch.samplePlayer->mode;
+       const bool             isLoop = ch.samplePlayer->isAnyLoopMode();
 
-/* -------------------------------------------------------------------------- */
+       ChannelStatus playStatus = ch.shared->playStatus.load();
 
-void rewind_(channel::Data& ch, Frame localFrame)
-{
-       if (ch.isPlaying())
+       switch (playStatus)
        {
-               ch.state->rewinding = true;
-               ch.state->offset    = localFrame;
+       case ChannelStatus::OFF:
+               playStatus = pressWhileOff(ch, sequencer, velocity, isLoop);
+               break;
+
+       case ChannelStatus::PLAY:
+               playStatus = pressWhilePlay(ch, sequencer, mode, isLoop);
+               break;
+
+       case ChannelStatus::WAIT:
+               playStatus = ChannelStatus::OFF;
+               break;
+
+       case ChannelStatus::ENDING:
+               playStatus = ChannelStatus::PLAY;
+               break;
+
+       default:
+               break;
        }
-       else
-               ch.state->tracker.store(ch.samplePlayer->begin);
+
+       ch.shared->playStatus.store(playStatus);
 }
-} // namespace
 
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
 /* -------------------------------------------------------------------------- */
 
-Data::Data(ID channelId)
+void SampleReactor::kill(Channel& ch) const
 {
-       sequencer::quantizer.schedule(Q_ACTION_PLAY + channelId, [channelId](Frame delta) {
-               channel::Data& ch = model::get().getChannel(channelId);
-               ch.state->offset  = delta;
-               ch.state->playStatus.store(ChannelStatus::PLAY);
-       });
-
-       sequencer::quantizer.schedule(Q_ACTION_REWIND + channelId, [channelId](Frame delta) {
-               channel::Data& ch = model::get().getChannel(channelId);
-               rewind_(ch, delta);
-       });
+       ch.shared->playStatus.store(ChannelStatus::OFF);
+       ch.shared->tracker.store(ch.samplePlayer->begin);
 }
-
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
 /* -------------------------------------------------------------------------- */
 
-void react(channel::Data& ch, const eventDispatcher::Event& e)
+void SampleReactor::release(Channel& ch, Sequencer& sequencer) const
 {
-       if (!ch.hasWave())
+       /* Key release is meaningful only for SINGLE_PRESS modes. */
+
+       if (ch.samplePlayer->mode != SamplePlayerMode::SINGLE_PRESS)
                return;
 
-       switch (e.type)
-       {
+       /* Kill it if it's SINGLE_PRESS is playing. Otherwise there might be a 
+       quantization step in progress that would play the channel later on: 
+       disable it. */
 
-       case eventDispatcher::EventType::KEY_PRESS:
-               press_(ch, std::get<int>(e.data));
-               break;
+       if (ch.shared->playStatus.load() == ChannelStatus::PLAY)
+               kill(ch);
+       else if (sequencer.quantizer.hasBeenTriggered())
+               sequencer.quantizer.clear();
+}
 
-       case eventDispatcher::EventType::KEY_RELEASE:
-               release_(ch);
-               break;
+/* -------------------------------------------------------------------------- */
 
-       case eventDispatcher::EventType::KEY_KILL:
-               kill_(ch);
-               break;
+void SampleReactor::onStopBySeq(Channel& ch, bool chansStopOnSeqHalt) const
+{
+       G_DEBUG("onStopBySeq ch=" << ch.id);
+
+       ChannelStatus playStatus       = ch.shared->playStatus.load();
+       bool          isReadingActions = ch.shared->readActions.load();
+       bool          isLoop           = ch.samplePlayer->isAnyLoopMode();
+
+       switch (playStatus)
+       {
 
-       case eventDispatcher::EventType::SEQUENCER_STOP:
-               onStopBySeq_(ch);
+       case ChannelStatus::WAIT:
+               /* Loop-mode channels in wait status get stopped right away. */
+               if (isLoop)
+                       ch.shared->playStatus.store(ChannelStatus::OFF);
                break;
 
-       case eventDispatcher::EventType::CHANNEL_TOGGLE_READ_ACTIONS:
-               toggleReadActions_(ch);
+       case ChannelStatus::PLAY:
+               if (chansStopOnSeqHalt && (isLoop || isReadingActions))
+                       kill(ch);
                break;
 
        default:
                break;
        }
 }
-} // namespace giada::m::sampleReactor
\ No newline at end of file
+
+/* -------------------------------------------------------------------------- */
+
+void SampleReactor::toggleReadActions(Channel& ch, bool isSequencerRunning, bool treatRecsAsLoops) const
+{
+       if (isSequencerRunning && ch.shared->recStatus.load() == ChannelStatus::PLAY && !treatRecsAsLoops)
+               kill(ch);
+}
+} // namespace giada::m
\ No newline at end of file
index a82b1c778e0080c1e2123aab30c2dbd8f20a18e6..cfb725c6f9cfc115caee9f7e2b1d4ae63d9344be 100644 (file)
 #ifndef G_CHANNEL_SAMPLE_REACTOR_H
 #define G_CHANNEL_SAMPLE_REACTOR_H
 
+#include "core/conf.h"
 #include "core/eventDispatcher.h"
 #include "core/quantizer.h"
 
-namespace giada::m::channel
+namespace giada::m::model
 {
-struct Data;
+class Model;
 }
 
-/* sampleReactor
+namespace giada::m
+{
+class Channel;
+class Sequencer;
+
+/* SampleReactor
 Reacts to manual events sent to Sample Channels: key press, key release, 
 sequencer stop, ... . */
 
-namespace giada::m::sampleReactor
-{
-struct Data
+class SampleReactor final
 {
-       Data(ID channelId);
-       Data(const Data&) = default;
-       Data(Data&&)      = default;
-       Data& operator=(const Data&) = default;
-       Data& operator=(Data&&) = default;
+public:
+       SampleReactor(ID channelId, Sequencer&, model::Model&);
+
+       void react(Channel&, const EventDispatcher::Event&, Sequencer&, const Conf::Data&) const;
+
+private:
+       void          toggleReadActions(Channel&, bool isSequencerRunning, bool treatRecsAsLoops) const;
+       void          onStopBySeq(Channel&, bool chansStopOnSeqHalt) const;
+       void          release(Channel&, Sequencer&) const;
+       void          kill(Channel&) const;
+       void          press(Channel&, Sequencer&, int velocity) const;
+       ChannelStatus pressWhilePlay(Channel&, Sequencer&, SamplePlayerMode, bool isLoop) const;
+       ChannelStatus pressWhileOff(Channel&, Sequencer&, int velocity, bool isLoop) const;
+       void          reset(Channel&) const;
+       void          rewind(Channel&, Frame localFrame) const;
 };
 
-void react(channel::Data& ch, const eventDispatcher::Event& e);
-} // namespace giada::m::sampleReactor
+} // namespace giada::m
 
 #endif
index fd190ca1bd2e8e083403c6313b105f62bf6bfc17..0d7946ba5e548c7e4193f44aeede1451437cf1f2 100644 (file)
  * -------------------------------------------------------------------------- */
 
 #include "waveReader.h"
-#include "core/audioBuffer.h"
 #include "core/const.h"
 #include "core/model/model.h"
 #include "core/wave.h"
+#include "deps/mcl-audio-buffer/src/audioBuffer.hpp"
 #include "utils/log.h"
 #include <algorithm>
 #include <cassert>
@@ -44,7 +44,7 @@ WaveReader::WaveReader(Resampler* r)
 
 /* -------------------------------------------------------------------------- */
 
-WaveReader::Result WaveReader::fill(AudioBuffer& out, Frame start, Frame max,
+WaveReader::Result WaveReader::fill(mcl::AudioBuffer& out, Frame start, Frame max,
     Frame offset, float pitch) const
 {
        assert(wave != nullptr);
@@ -60,7 +60,7 @@ WaveReader::Result WaveReader::fill(AudioBuffer& out, Frame start, Frame max,
 
 /* -------------------------------------------------------------------------- */
 
-WaveReader::Result WaveReader::fillResampled(AudioBuffer& dest, Frame start,
+WaveReader::Result WaveReader::fillResampled(mcl::AudioBuffer& dest, Frame start,
     Frame max, Frame offset, float pitch) const
 {
        Resampler::Result res = m_resampler->process(
@@ -78,7 +78,8 @@ WaveReader::Result WaveReader::fillResampled(AudioBuffer& dest, Frame start,
 
 /* -------------------------------------------------------------------------- */
 
-WaveReader::Result WaveReader::fillCopy(AudioBuffer& dest, Frame start, Frame max, Frame offset) const
+WaveReader::Result WaveReader::fillCopy(mcl::AudioBuffer& dest, Frame start,
+    Frame max, Frame offset) const
 {
        Frame used = dest.countFrames() - offset;
        if (used > max - start)
index ee0ab6d28cdd6ee9649ecd1946036dc1c044c09b..9af720d8390559fe03cdc5237b77a876b72a790d 100644 (file)
 
 #include "core/types.h"
 
+namespace mcl
+{
+class AudioBuffer;
+}
+
 namespace giada::m
 {
 class Wave;
-class AudioBuffer;
 class Resampler;
 class WaveReader final
 {
@@ -56,7 +60,7 @@ public:
        Fills audio buffer 'out' with data coming from Wave, copying it from 'start'
        frame up to 'max'. The buffer is filled starting at 'offset'. */
 
-       Result fill(AudioBuffer& out, Frame start, Frame max, Frame offset,
+       Result fill(mcl::AudioBuffer& out, Frame start, Frame max, Frame offset,
            float pitch) const;
 
        /* last
@@ -71,9 +75,9 @@ public:
        Wave* wave;
 
 private:
-       Result fillResampled(AudioBuffer& out, Frame start, Frame max, Frame offset,
+       Result fillResampled(mcl::AudioBuffer& out, Frame start, Frame max, Frame offset,
            float pitch) const;
-       Result fillCopy(AudioBuffer& out, Frame start, Frame max, Frame offset) const;
+       Result fillCopy(mcl::AudioBuffer& out, Frame start, Frame max, Frame offset) const;
 
        Resampler* m_resampler;
 };
diff --git a/src/core/clock.cpp b/src/core/clock.cpp
deleted file mode 100644 (file)
index da98093..0000000
+++ /dev/null
@@ -1,457 +0,0 @@
-/* -----------------------------------------------------------------------------
- *
- * Giada - Your Hardcore Loopmachine
- *
- * -----------------------------------------------------------------------------
- *
- * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
- *
- * This file is part of Giada - Your Hardcore Loopmachine.
- *
- * Giada - Your Hardcore Loopmachine is free software: you can
- * redistribute it and/or modify it under the terms of the GNU General
- * Public License as published by the Free Software Foundation, either
- * version 3 of the License, or (at your option) any later version.
- *
- * Giada - Your Hardcore Loopmachine is distributed in the hope that it
- * will be useful, but WITHOUT ANY WARRANTY; without even the implied
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Giada - Your Hardcore Loopmachine. If not, see
- * <http://www.gnu.org/licenses/>.
- *
- * -------------------------------------------------------------------------- */
-
-#include "clock.h"
-#include "core/conf.h"
-#include "core/const.h"
-#include "core/kernelAudio.h"
-#include "core/kernelMidi.h"
-#include "core/mixerHandler.h"
-#include "core/model/model.h"
-#include "core/recorderHandler.h"
-#include "core/sequencer.h"
-#include "glue/events.h"
-#include "utils/log.h"
-#include "utils/math.h"
-#include <atomic>
-#include <cassert>
-
-namespace giada::m::clock
-{
-namespace
-{
-/* quantizerStep_
-Tells how many frames to wait to perform a quantized action. */
-
-int quantizerStep_ = 1;
-
-/* midiTC*
-MIDI timecode variables. */
-
-int midiTCrate_    = 0; // Send MTC data every midiTCrate_ frames
-int midiTCframes_  = 0;
-int midiTCseconds_ = 0;
-int midiTCminutes_ = 0;
-int midiTChours_   = 0;
-
-#ifdef WITH_AUDIO_JACK
-kernelAudio::JackState jackStatePrev_;
-#endif
-
-/* -------------------------------------------------------------------------- */
-
-/* recomputeFrames_
-Updates bpm, frames, beats and so on. Private version. */
-
-void recomputeFrames_(model::Clock& c)
-{
-       c.framesInLoop = static_cast<int>((conf::conf.samplerate * (60.0f / c.bpm)) * c.beats);
-       c.framesInBar  = static_cast<int>(c.framesInLoop / (float)c.bars);
-       c.framesInBeat = static_cast<int>(c.framesInLoop / (float)c.beats);
-       c.framesInSeq  = c.framesInBeat * G_MAX_BEATS;
-
-       if (c.quantize != 0)
-               quantizerStep_ = c.framesInBeat / c.quantize;
-}
-
-/* -------------------------------------------------------------------------- */
-
-void setBpm_(float current)
-{
-       float ratio = model::get().clock.bpm / current;
-
-       model::get().clock.bpm = current;
-       recomputeFrames_(model::get().clock);
-
-       m::recorderHandler::updateBpm(ratio, quantizerStep_);
-
-       model::swap(model::SwapType::HARD);
-
-       u::log::print("[clock::setBpm_] Bpm changed to %f\n", current);
-}
-} // namespace
-
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-
-void init(int sampleRate, float midiTCfps)
-{
-       midiTCrate_ = static_cast<int>((sampleRate / midiTCfps) * G_MAX_IO_CHANS); // stereo values
-
-       model::get().clock.bars     = G_DEFAULT_BARS;
-       model::get().clock.beats    = G_DEFAULT_BEATS;
-       model::get().clock.bpm      = G_DEFAULT_BPM;
-       model::get().clock.quantize = G_DEFAULT_QUANTIZE;
-       recomputeFrames_(model::get().clock);
-
-       model::swap(model::SwapType::NONE);
-}
-
-/* -------------------------------------------------------------------------- */
-
-void recomputeFrames()
-{
-       recomputeFrames_(model::get().clock);
-       model::swap(model::SwapType::NONE);
-}
-
-/* -------------------------------------------------------------------------- */
-
-bool isRunning()
-{
-       return model::get().clock.status == ClockStatus::RUNNING;
-}
-
-bool isActive()
-{
-       const model::Clock& c = model::get().clock;
-       return c.status == ClockStatus::RUNNING || c.status == ClockStatus::WAITING;
-}
-
-bool quantoHasPassed()
-{
-       const model::Clock& c = model::get().clock;
-       return clock::getQuantizerValue() != 0 && c.state->currentFrame.load() % quantizerStep_ == 0;
-}
-
-bool isOnBar()
-{
-       const model::Clock& c = model::get().clock;
-
-       int currentFrame = c.state->currentFrame.load();
-
-       if (c.status == ClockStatus::WAITING || currentFrame == 0)
-               return false;
-       return currentFrame % c.framesInBar == 0;
-}
-
-bool isOnBeat()
-{
-       const model::Clock& c = model::get().clock;
-
-       if (c.status == ClockStatus::WAITING)
-               return c.state->currentFrameWait.load() % c.framesInBeat == 0;
-       return c.state->currentFrame.load() % c.framesInBeat == 0;
-}
-
-bool isOnFirstBeat()
-{
-       return model::get().clock.state->currentFrame.load() == 0;
-}
-
-/* -------------------------------------------------------------------------- */
-
-void setBpm(float b)
-{
-       b = std::clamp(b, G_MIN_BPM, G_MAX_BPM);
-
-       /* If JACK is being used, let it handle the bpm change. */
-
-#ifdef WITH_AUDIO_JACK
-       if (kernelAudio::getAPI() == G_SYS_API_JACK)
-       {
-               kernelAudio::jackSetBpm(b);
-               return;
-       }
-#endif
-
-       setBpm_(b);
-}
-
-void setBeats(int newBeats, int newBars)
-{
-       newBeats = std::clamp(newBeats, 1, G_MAX_BEATS);
-       newBars  = std::clamp(newBars, 1, newBeats); // Bars cannot be greater than beats
-
-       model::get().clock.beats = newBeats;
-       model::get().clock.bars  = newBars;
-       recomputeFrames_(model::get().clock);
-
-       model::swap(model::SwapType::HARD);
-}
-
-void setQuantize(int q)
-{
-       model::get().clock.quantize = q;
-       recomputeFrames_(model::get().clock);
-
-       model::swap(model::SwapType::HARD);
-}
-
-void setStatus(ClockStatus s)
-{
-       model::get().clock.status = s;
-       model::swap(model::SwapType::SOFT);
-
-       if (s == ClockStatus::RUNNING)
-       {
-               if (conf::conf.midiSync == MIDI_SYNC_CLOCK_M)
-               {
-                       kernelMidi::send(MIDI_START, -1, -1);
-                       kernelMidi::send(MIDI_POSITION_PTR, 0, 0);
-               }
-       }
-       else if (s == ClockStatus::STOPPED)
-       {
-               if (conf::conf.midiSync == MIDI_SYNC_CLOCK_M)
-                       kernelMidi::send(MIDI_STOP, -1, -1);
-       }
-}
-
-/* -------------------------------------------------------------------------- */
-
-void advance(Frame amount)
-{
-       const model::Clock& c = model::get().clock;
-
-       if (c.status == ClockStatus::WAITING)
-       {
-               int f = (c.state->currentFrameWait.load() + amount) % c.framesInLoop;
-               c.state->currentFrameWait.store(f);
-               return;
-       }
-
-       int f = (c.state->currentFrame.load() + amount) % c.framesInLoop;
-       int b = f / c.framesInBeat;
-
-       c.state->currentFrame.store(f);
-       c.state->currentBeat.store(b);
-}
-
-/* -------------------------------------------------------------------------- */
-
-void rewind()
-{
-       const model::Clock& c = model::get().clock;
-
-       c.state->currentFrame.store(0);
-       c.state->currentBeat.store(0);
-       c.state->currentFrameWait.store(0);
-
-       sendMIDIrewind();
-}
-
-/* -------------------------------------------------------------------------- */
-
-void sendMIDIsync()
-{
-       const model::Clock& c = model::get().clock;
-
-       /* Sending MIDI sync while waiting is meaningless. */
-
-       if (c.status == ClockStatus::WAITING)
-               return;
-
-       int currentFrame = c.state->currentFrame.load();
-
-       /* TODO - only Master (_M) is implemented so far. */
-
-       if (conf::conf.midiSync == MIDI_SYNC_CLOCK_M)
-       {
-               if (currentFrame % (c.framesInBeat / 24) == 0)
-                       kernelMidi::send(MIDI_CLOCK, -1, -1);
-               return;
-       }
-
-       if (conf::conf.midiSync == MIDI_SYNC_MTC_M)
-       {
-
-               /* check if a new timecode frame has passed. If so, send MIDI TC
-                * quarter frames. 8 quarter frames, divided in two branches:
-                * 1-4 and 5-8. We check timecode frame's parity: if even, send
-                * range 1-4, if odd send 5-8. */
-
-               if (currentFrame % midiTCrate_ != 0) // no timecode frame passed
-                       return;
-
-               /* frame low nibble
-                * frame high nibble
-                * seconds low nibble
-                * seconds high nibble */
-
-               if (midiTCframes_ % 2 == 0)
-               {
-                       kernelMidi::send(MIDI_MTC_QUARTER, (midiTCframes_ & 0x0F) | 0x00, -1);
-                       kernelMidi::send(MIDI_MTC_QUARTER, (midiTCframes_ >> 4) | 0x10, -1);
-                       kernelMidi::send(MIDI_MTC_QUARTER, (midiTCseconds_ & 0x0F) | 0x20, -1);
-                       kernelMidi::send(MIDI_MTC_QUARTER, (midiTCseconds_ >> 4) | 0x30, -1);
-               }
-
-               /* minutes low nibble
-                * minutes high nibble
-                * hours low nibble
-                * hours high nibble SMPTE frame rate */
-
-               else
-               {
-                       kernelMidi::send(MIDI_MTC_QUARTER, (midiTCminutes_ & 0x0F) | 0x40, -1);
-                       kernelMidi::send(MIDI_MTC_QUARTER, (midiTCminutes_ >> 4) | 0x50, -1);
-                       kernelMidi::send(MIDI_MTC_QUARTER, (midiTChours_ & 0x0F) | 0x60, -1);
-                       kernelMidi::send(MIDI_MTC_QUARTER, (midiTChours_ >> 4) | 0x70, -1);
-               }
-
-               midiTCframes_++;
-
-               /* check if total timecode frames are greater than timecode fps:
-                * if so, a second has passed */
-
-               if (midiTCframes_ > conf::conf.midiTCfps)
-               {
-                       midiTCframes_ = 0;
-                       midiTCseconds_++;
-                       if (midiTCseconds_ >= 60)
-                       {
-                               midiTCminutes_++;
-                               midiTCseconds_ = 0;
-                               if (midiTCminutes_ >= 60)
-                               {
-                                       midiTChours_++;
-                                       midiTCminutes_ = 0;
-                               }
-                       }
-                       //u::log::print("%d:%d:%d:%d\n", midiTChours_, midiTCminutes_, midiTCseconds_, midiTCframes_);
-               }
-       }
-}
-
-/* -------------------------------------------------------------------------- */
-
-void sendMIDIrewind()
-{
-       midiTCframes_  = 0;
-       midiTCseconds_ = 0;
-       midiTCminutes_ = 0;
-       midiTChours_   = 0;
-
-       /* For cueing the slave to a particular start point, Quarter Frame
-        * messages are not used. Instead, an MTC Full Frame message should
-        * be sent. The Full Frame is a SysEx message that encodes the entire
-        * SMPTE time in one message */
-
-       if (conf::conf.midiSync == MIDI_SYNC_MTC_M)
-       {
-               kernelMidi::send(MIDI_SYSEX, 0x7F, 0x00); // send msg on channel 0
-               kernelMidi::send(0x01, 0x01, 0x00);       // hours 0
-               kernelMidi::send(0x00, 0x00, 0x00);       // mins, secs, frames 0
-               kernelMidi::send(MIDI_EOX, -1, -1);       // end of sysex
-       }
-       else if (conf::conf.midiSync == MIDI_SYNC_CLOCK_M)
-               kernelMidi::send(MIDI_POSITION_PTR, 0, 0);
-}
-
-/* -------------------------------------------------------------------------- */
-
-#ifdef WITH_AUDIO_JACK
-
-void recvJackSync()
-{
-       /* TODO - these things should be processed by a higher level, 
-       above clock:: ----> clockManager */
-
-       kernelAudio::JackState jackStateCurr = kernelAudio::jackTransportQuery();
-
-       if (jackStateCurr != jackStatePrev_)
-       {
-               if (jackStateCurr.frame != jackStatePrev_.frame && jackStateCurr.frame == 0)
-               {
-                       G_DEBUG("JackState received - rewind to frame 0");
-                       sequencer::rawRewind();
-               }
-
-               // jackStateCurr.bpm == 0 if JACK doesn't send that info
-               if (jackStateCurr.bpm != jackStatePrev_.bpm && jackStateCurr.bpm > 1.0f)
-               {
-                       G_DEBUG("JackState received - bpm=" << jackStateCurr.bpm);
-                       setBpm_(jackStateCurr.bpm);
-               }
-
-               if (jackStateCurr.running != jackStatePrev_.running)
-               {
-                       G_DEBUG("JackState received - running=" << jackStateCurr.running);
-                       jackStateCurr.running ? sequencer::rawStart() : sequencer::rawStop();
-               }
-       }
-
-       jackStatePrev_ = jackStateCurr;
-}
-
-#endif
-
-/* -------------------------------------------------------------------------- */
-
-bool canQuantize()
-{
-       const model::Clock& c = model::get().clock;
-
-       return c.quantize > 0 && c.status == ClockStatus::RUNNING;
-}
-
-/* -------------------------------------------------------------------------- */
-
-Frame quantize(Frame f)
-{
-       if (!canQuantize())
-               return f;
-       return u::math::quantize(f, quantizerStep_) % getFramesInLoop(); // No overflow
-}
-
-/* -------------------------------------------------------------------------- */
-
-int         getCurrentFrame() { return model::get().clock.state->currentFrame.load(); }
-int         getCurrentBeat() { return model::get().clock.state->currentBeat.load(); }
-int         getQuantizerStep() { return quantizerStep_; }
-ClockStatus getStatus() { return model::get().clock.status; }
-int         getFramesInLoop() { return model::get().clock.framesInLoop; }
-int         getFramesInBar() { return model::get().clock.framesInBar; }
-int         getFramesInBeat() { return model::get().clock.framesInBeat; }
-int         getFramesInSeq() { return model::get().clock.framesInSeq; }
-int         getQuantizerValue() { return model::get().clock.quantize; }
-float       getBpm() { return model::get().clock.bpm; }
-int         getBeats() { return model::get().clock.beats; }
-int         getBars() { return model::get().clock.bars; }
-
-/* -------------------------------------------------------------------------- */
-
-float getCurrentSecond()
-{
-       return getCurrentFrame() / static_cast<float>(conf::conf.samplerate);
-}
-
-/* -------------------------------------------------------------------------- */
-
-Frame getMaxFramesInLoop()
-{
-       return (conf::conf.samplerate * (60.0f / G_MIN_BPM)) * getBeats();
-}
-
-/* -------------------------------------------------------------------------- */
-
-float calcBpmFromRec(Frame recordedFrames)
-{
-       return (60.0f * getBeats()) / (recordedFrames / static_cast<float>(conf::conf.samplerate));
-}
-} // namespace giada::m::clock
diff --git a/src/core/clock.h b/src/core/clock.h
deleted file mode 100644 (file)
index 716ec9f..0000000
+++ /dev/null
@@ -1,125 +0,0 @@
-/* -----------------------------------------------------------------------------
- *
- * Giada - Your Hardcore Loopmachine
- *
- * -----------------------------------------------------------------------------
- *
- * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
- *
- * This file is part of Giada - Your Hardcore Loopmachine.
- *
- * Giada - Your Hardcore Loopmachine is free software: you can
- * redistribute it and/or modify it under the terms of the GNU General
- * Public License as published by the Free Software Foundation, either
- * version 3 of the License, or (at your option) any later version.
- *
- * Giada - Your Hardcore Loopmachine is distributed in the hope that it
- * will be useful, but WITHOUT ANY WARRANTY; without even the implied
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Giada - Your Hardcore Loopmachine. If not, see
- * <http://www.gnu.org/licenses/>.
- *
- * -------------------------------------------------------------------------- */
-
-#ifndef G_CLOCK_H
-#define G_CLOCK_H
-
-#include "types.h"
-
-namespace giada::m::clock
-{
-void init(int sampleRate, float midiTCfps);
-
-/* recomputeFrames
-Updates bpm, frames, beats and so on. */
-
-void recomputeFrames();
-
-/* sendMIDIsync
-Generates MIDI sync output data. */
-/*TODO - move this to giada::m::sync*/
-void sendMIDIsync();
-
-/* sendMIDIrewind
-Rewinds timecode to beat 0 and also send a MTC full frame to cue the slave. */
-/*TODO - move this to giada::m::sync*/
-void sendMIDIrewind();
-
-#if defined(G_OS_LINUX) || defined(G_OS_FREEBSD) || defined(G_OS_MAC)
-/*TODO - move this to giada::m::sync*/
-void recvJackSync();
-#endif
-
-float       getBpm();
-int         getBeats();
-int         getBars();
-int         getCurrentBeat();
-int         getCurrentFrame();
-float       getCurrentSecond();
-int         getFramesInBar();
-int         getFramesInBeat();
-int         getFramesInLoop();
-int         getFramesInSeq();
-int         getQuantizerValue();
-int         getQuantizerStep();
-ClockStatus getStatus();
-
-/* getMaxFramesInLoop
-Returns how many frames the current loop length might contain at the slowest
-speed possible (G_MIN_BPM). Call this whenever you change the number or beats. */
-
-Frame getMaxFramesInLoop();
-
-/* advance
-Increases current frame by a specific amount. */
-
-void advance(Frame amount);
-
-/* quantoHasPassed
-Tells whether a quantizer unit has passed yet. */
-
-bool quantoHasPassed();
-
-/* canQuantize
-Tells whether the quantizer value is > 0 and the clock is running. */
-
-bool canQuantize();
-
-/* quantize
-Quantizes the global frame 'f'.  */
-
-Frame quantize(Frame f);
-
-void setBpm(float b);
-void setBeats(int beats, int bars);
-void setQuantize(int q);
-
-/* isRunning
-When clock is actually moving forward, i.e. ClockStatus == RUNNING. */
-
-bool isRunning();
-
-/* isActive
-Clock is enabled, but might be in wait mode, i.e. ClockStatus == RUNNING or
-ClockStatus == WAITING. */
-
-bool isActive();
-
-bool isOnBeat();
-bool isOnBar();
-bool isOnFirstBeat();
-
-void rewind();
-void setStatus(ClockStatus s);
-
-/* calcBpmFromRec
-Given the amount of recorded frames, returns the speed of the current 
-performance. Used while input recording in FREE mode. */
-
-float calcBpmFromRec(Frame recordedFrames);
-} // namespace giada::m::clock
-
-#endif
index 02c9f15a3833596ee5a32e523ee557c80a5afb59..7240433c01cb1ae77ca7446267130ce4478ec3f4 100644 (file)
@@ -24,7 +24,7 @@
  *
  * -------------------------------------------------------------------------- */
 
-#include "conf.h"
+#include "core/conf.h"
 #include "core/const.h"
 #include "core/types.h"
 #include "deps/json/single_include/nlohmann/json.hpp"
 
 namespace nl = nlohmann;
 
-namespace giada::m::conf
+namespace giada::m
 {
-namespace
+Conf::Conf()
 {
-std::string confFilePath_ = "";
-std::string confDirPath_  = "";
+       /* Initialize m_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 */
 
-/* -------------------------------------------------------------------------- */
-
-void sanitize_()
-{
-       conf.soundDeviceOut   = std::max(0, conf.soundDeviceOut);
-       conf.channelsOutCount = G_MAX_IO_CHANS;
-       conf.channelsOutStart = std::max(0, conf.channelsOutStart);
-       conf.channelsInCount  = std::max(1, conf.channelsInCount);
-       conf.channelsInStart  = std::max(0, conf.channelsInStart);
-}
-
-/* -------------------------------------------------------------------------- */
-
-/* createConfigFolder
-Creates local folder where to put the configuration file. Path differs from OS
-to OS. */
-
-int createConfigFolder_()
-{
 #if defined(__linux__) || defined(__FreeBSD__) || defined(__APPLE__)
 
-       if (u::fs::dirExists(confDirPath_))
-               return 1;
-
-       u::log::print("[conf::createConfigFolder] .giada folder not present. Updating...\n");
-
-       if (u::fs::mkdir(confDirPath_))
-       {
-               u::log::print("[conf::createConfigFolder] status: ok\n");
-               return 1;
-       }
-       else
-       {
-               u::log::print("[conf::createConfigFolder] status: error!\n");
-               return 0;
-       }
-
-#else // Windows: nothing to do
-
-       return 1;
-
-#endif
-}
-} // namespace
-
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-
-Conf conf;
-
-/* -------------------------------------------------------------------------- */
-
-void init()
-{
-       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(__FreeBSD__) || defined(__APPLE__)
-
-       confFilePath_ = u::fs::getHomePath() + G_SLASH + CONF_FILENAME;
-       confDirPath_  = u::fs::getHomePath() + G_SLASH;
+       m_confFilePath = u::fs::getHomePath() + G_SLASH + CONF_FILENAME;
+       m_confDirPath  = u::fs::getHomePath() + G_SLASH;
 
 #elif defined(_WIN32)
 
-       confFilePath_ = CONF_FILENAME;
-       confDirPath_  = "";
+       m_confFilePath = CONF_FILENAME;
+       m_confDirPath  = "";
 
 #endif
 }
 
 /* -------------------------------------------------------------------------- */
 
-bool read()
+bool Conf::read()
 {
-       init();
+       data = Data(); // Reset it first
 
-       std::ifstream ifs(confFilePath_);
+       std::ifstream ifs(m_confFilePath);
        if (!ifs.good())
                return false;
 
        nl::json j = nl::json::parse(ifs);
 
-       conf.logMode                    = j.value(CONF_KEY_LOG_MODE, conf.logMode);
-       conf.showTooltips               = j.value(CONF_KEY_SHOW_TOOLTIPS, conf.showTooltips);
-       conf.soundSystem                = j.value(CONF_KEY_SOUND_SYSTEM, conf.soundSystem);
-       conf.soundDeviceOut             = j.value(CONF_KEY_SOUND_DEVICE_OUT, conf.soundDeviceOut);
-       conf.soundDeviceIn              = j.value(CONF_KEY_SOUND_DEVICE_IN, conf.soundDeviceIn);
-       conf.channelsOutCount           = j.value(CONF_KEY_CHANNELS_OUT_COUNT, conf.channelsOutCount);
-       conf.channelsOutStart           = j.value(CONF_KEY_CHANNELS_OUT_START, conf.channelsOutStart);
-       conf.channelsInCount            = j.value(CONF_KEY_CHANNELS_IN_COUNT, conf.channelsInCount);
-       conf.channelsInStart            = j.value(CONF_KEY_CHANNELS_IN_START, conf.channelsInStart);
-       conf.samplerate                 = j.value(CONF_KEY_SAMPLERATE, conf.samplerate);
-       conf.buffersize                 = j.value(CONF_KEY_BUFFER_SIZE, conf.buffersize);
-       conf.limitOutput                = j.value(CONF_KEY_LIMIT_OUTPUT, conf.limitOutput);
-       conf.rsmpQuality                = j.value(CONF_KEY_RESAMPLE_QUALITY, conf.rsmpQuality);
-       conf.midiSystem                 = j.value(CONF_KEY_MIDI_SYSTEM, conf.midiSystem);
-       conf.midiPortOut                = j.value(CONF_KEY_MIDI_PORT_OUT, conf.midiPortOut);
-       conf.midiPortIn                 = j.value(CONF_KEY_MIDI_PORT_IN, conf.midiPortIn);
-       conf.midiMapPath                = j.value(CONF_KEY_MIDIMAP_PATH, conf.midiMapPath);
-       conf.lastFileMap                = j.value(CONF_KEY_LAST_MIDIMAP, conf.lastFileMap);
-       conf.midiSync                   = j.value(CONF_KEY_MIDI_SYNC, conf.midiSync);
-       conf.midiTCfps                  = j.value(CONF_KEY_MIDI_TC_FPS, conf.midiTCfps);
-       conf.chansStopOnSeqHalt         = j.value(CONF_KEY_CHANS_STOP_ON_SEQ_HALT, conf.chansStopOnSeqHalt);
-       conf.treatRecsAsLoops           = j.value(CONF_KEY_TREAT_RECS_AS_LOOPS, conf.treatRecsAsLoops);
-       conf.inputMonitorDefaultOn      = j.value(CONF_KEY_INPUT_MONITOR_DEFAULT_ON, conf.inputMonitorDefaultOn);
-       conf.overdubProtectionDefaultOn = j.value(CONF_KEY_OVERDUB_PROTECTION_DEFAULT_ON, conf.overdubProtectionDefaultOn);
-       conf.pluginPath                 = j.value(CONF_KEY_PLUGINS_PATH, conf.pluginPath);
-       conf.patchPath                  = j.value(CONF_KEY_PATCHES_PATH, conf.patchPath);
-       conf.samplePath                 = j.value(CONF_KEY_SAMPLES_PATH, conf.samplePath);
-       conf.mainWindowX                = j.value(CONF_KEY_MAIN_WINDOW_X, conf.mainWindowX);
-       conf.mainWindowY                = j.value(CONF_KEY_MAIN_WINDOW_Y, conf.mainWindowY);
-       conf.mainWindowW                = j.value(CONF_KEY_MAIN_WINDOW_W, conf.mainWindowW);
-       conf.mainWindowH                = j.value(CONF_KEY_MAIN_WINDOW_H, conf.mainWindowH);
-       conf.browserX                   = j.value(CONF_KEY_BROWSER_X, conf.browserX);
-       conf.browserY                   = j.value(CONF_KEY_BROWSER_Y, conf.browserY);
-       conf.browserW                   = j.value(CONF_KEY_BROWSER_W, conf.browserW);
-       conf.browserH                   = j.value(CONF_KEY_BROWSER_H, conf.browserH);
-       conf.browserPosition            = j.value(CONF_KEY_BROWSER_POSITION, conf.browserPosition);
-       conf.browserLastPath            = j.value(CONF_KEY_BROWSER_LAST_PATH, conf.browserLastPath);
-       conf.browserLastValue           = j.value(CONF_KEY_BROWSER_LAST_VALUE, conf.browserLastValue);
-       conf.actionEditorX              = j.value(CONF_KEY_ACTION_EDITOR_X, conf.actionEditorX);
-       conf.actionEditorY              = j.value(CONF_KEY_ACTION_EDITOR_Y, conf.actionEditorY);
-       conf.actionEditorW              = j.value(CONF_KEY_ACTION_EDITOR_W, conf.actionEditorW);
-       conf.actionEditorH              = j.value(CONF_KEY_ACTION_EDITOR_H, conf.actionEditorH);
-       conf.actionEditorZoom           = j.value(CONF_KEY_ACTION_EDITOR_ZOOM, conf.actionEditorZoom);
-       conf.actionEditorGridVal        = j.value(CONF_KEY_ACTION_EDITOR_GRID_VAL, conf.actionEditorGridVal);
-       conf.actionEditorGridOn         = j.value(CONF_KEY_ACTION_EDITOR_GRID_ON, conf.actionEditorGridOn);
-       conf.sampleEditorX              = j.value(CONF_KEY_SAMPLE_EDITOR_X, conf.sampleEditorX);
-       conf.sampleEditorY              = j.value(CONF_KEY_SAMPLE_EDITOR_Y, conf.sampleEditorY);
-       conf.sampleEditorW              = j.value(CONF_KEY_SAMPLE_EDITOR_W, conf.sampleEditorW);
-       conf.sampleEditorH              = j.value(CONF_KEY_SAMPLE_EDITOR_H, conf.sampleEditorH);
-       conf.sampleEditorGridVal        = j.value(CONF_KEY_SAMPLE_EDITOR_GRID_VAL, conf.sampleEditorGridVal);
-       conf.sampleEditorGridOn         = j.value(CONF_KEY_SAMPLE_EDITOR_GRID_ON, conf.sampleEditorGridOn);
-       conf.pianoRollY                 = j.value(CONF_KEY_PIANO_ROLL_Y, conf.pianoRollY);
-       conf.pianoRollH                 = j.value(CONF_KEY_PIANO_ROLL_H, conf.pianoRollH);
-       conf.sampleActionEditorH        = j.value(CONF_KEY_SAMPLE_ACTION_EDITOR_H, conf.sampleActionEditorH);
-       conf.velocityEditorH            = j.value(CONF_KEY_VELOCITY_EDITOR_H, conf.velocityEditorH);
-       conf.envelopeEditorH            = j.value(CONF_KEY_ENVELOPE_EDITOR_H, conf.envelopeEditorH);
-       conf.pluginListX                = j.value(CONF_KEY_PLUGIN_LIST_X, conf.pluginListX);
-       conf.pluginListY                = j.value(CONF_KEY_PLUGIN_LIST_Y, conf.pluginListY);
-       conf.midiInputX                 = j.value(CONF_KEY_MIDI_INPUT_X, conf.midiInputX);
-       conf.midiInputY                 = j.value(CONF_KEY_MIDI_INPUT_Y, conf.midiInputY);
-       conf.midiInputW                 = j.value(CONF_KEY_MIDI_INPUT_W, conf.midiInputW);
-       conf.midiInputH                 = j.value(CONF_KEY_MIDI_INPUT_H, conf.midiInputH);
-       conf.recTriggerMode             = j.value(CONF_KEY_REC_TRIGGER_MODE, conf.recTriggerMode);
-       conf.recTriggerLevel            = j.value(CONF_KEY_REC_TRIGGER_LEVEL, conf.recTriggerLevel);
-       conf.inputRecMode               = j.value(CONF_KEY_INPUT_REC_MODE, conf.inputRecMode);
-       conf.midiInEnabled              = j.value(CONF_KEY_MIDI_IN, conf.midiInEnabled);
-       conf.midiInFilter               = j.value(CONF_KEY_MIDI_IN_FILTER, conf.midiInFilter);
-       conf.midiInRewind               = j.value(CONF_KEY_MIDI_IN_REWIND, conf.midiInRewind);
-       conf.midiInStartStop            = j.value(CONF_KEY_MIDI_IN_START_STOP, conf.midiInStartStop);
-       conf.midiInActionRec            = j.value(CONF_KEY_MIDI_IN_ACTION_REC, conf.midiInActionRec);
-       conf.midiInInputRec             = j.value(CONF_KEY_MIDI_IN_INPUT_REC, conf.midiInInputRec);
-       conf.midiInMetronome            = j.value(CONF_KEY_MIDI_IN_METRONOME, conf.midiInMetronome);
-       conf.midiInVolumeIn             = j.value(CONF_KEY_MIDI_IN_VOLUME_IN, conf.midiInVolumeIn);
-       conf.midiInVolumeOut            = j.value(CONF_KEY_MIDI_IN_VOLUME_OUT, conf.midiInVolumeOut);
-       conf.midiInBeatDouble           = j.value(CONF_KEY_MIDI_IN_BEAT_DOUBLE, conf.midiInBeatDouble);
-       conf.midiInBeatHalf             = j.value(CONF_KEY_MIDI_IN_BEAT_HALF, conf.midiInBeatHalf);
+       data.logMode                    = j.value(CONF_KEY_LOG_MODE, data.logMode);
+       data.showTooltips               = j.value(CONF_KEY_SHOW_TOOLTIPS, data.showTooltips);
+       data.soundSystem                = j.value(CONF_KEY_SOUND_SYSTEM, data.soundSystem);
+       data.soundDeviceOut             = j.value(CONF_KEY_SOUND_DEVICE_OUT, data.soundDeviceOut);
+       data.soundDeviceIn              = j.value(CONF_KEY_SOUND_DEVICE_IN, data.soundDeviceIn);
+       data.channelsOutCount           = j.value(CONF_KEY_CHANNELS_OUT_COUNT, data.channelsOutCount);
+       data.channelsOutStart           = j.value(CONF_KEY_CHANNELS_OUT_START, data.channelsOutStart);
+       data.channelsInCount            = j.value(CONF_KEY_CHANNELS_IN_COUNT, data.channelsInCount);
+       data.channelsInStart            = j.value(CONF_KEY_CHANNELS_IN_START, data.channelsInStart);
+       data.samplerate                 = j.value(CONF_KEY_SAMPLERATE, data.samplerate);
+       data.buffersize                 = j.value(CONF_KEY_BUFFER_SIZE, data.buffersize);
+       data.limitOutput                = j.value(CONF_KEY_LIMIT_OUTPUT, data.limitOutput);
+       data.rsmpQuality                = j.value(CONF_KEY_RESAMPLE_QUALITY, data.rsmpQuality);
+       data.midiSystem                 = j.value(CONF_KEY_MIDI_SYSTEM, data.midiSystem);
+       data.midiPortOut                = j.value(CONF_KEY_MIDI_PORT_OUT, data.midiPortOut);
+       data.midiPortIn                 = j.value(CONF_KEY_MIDI_PORT_IN, data.midiPortIn);
+       data.midiMapPath                = j.value(CONF_KEY_MIDIMAP_PATH, data.midiMapPath);
+       data.lastFileMap                = j.value(CONF_KEY_LAST_MIDIMAP, data.lastFileMap);
+       data.midiSync                   = j.value(CONF_KEY_MIDI_SYNC, data.midiSync);
+       data.midiTCfps                  = j.value(CONF_KEY_MIDI_TC_FPS, data.midiTCfps);
+       data.chansStopOnSeqHalt         = j.value(CONF_KEY_CHANS_STOP_ON_SEQ_HALT, data.chansStopOnSeqHalt);
+       data.treatRecsAsLoops           = j.value(CONF_KEY_TREAT_RECS_AS_LOOPS, data.treatRecsAsLoops);
+       data.inputMonitorDefaultOn      = j.value(CONF_KEY_INPUT_MONITOR_DEFAULT_ON, data.inputMonitorDefaultOn);
+       data.overdubProtectionDefaultOn = j.value(CONF_KEY_OVERDUB_PROTECTION_DEFAULT_ON, data.overdubProtectionDefaultOn);
+       data.pluginPath                 = j.value(CONF_KEY_PLUGINS_PATH, data.pluginPath);
+       data.patchPath                  = j.value(CONF_KEY_PATCHES_PATH, data.patchPath);
+       data.samplePath                 = j.value(CONF_KEY_SAMPLES_PATH, data.samplePath);
+       data.mainWindowX                = j.value(CONF_KEY_MAIN_WINDOW_X, data.mainWindowX);
+       data.mainWindowY                = j.value(CONF_KEY_MAIN_WINDOW_Y, data.mainWindowY);
+       data.mainWindowW                = j.value(CONF_KEY_MAIN_WINDOW_W, data.mainWindowW);
+       data.mainWindowH                = j.value(CONF_KEY_MAIN_WINDOW_H, data.mainWindowH);
+       data.browserX                   = j.value(CONF_KEY_BROWSER_X, data.browserX);
+       data.browserY                   = j.value(CONF_KEY_BROWSER_Y, data.browserY);
+       data.browserW                   = j.value(CONF_KEY_BROWSER_W, data.browserW);
+       data.browserH                   = j.value(CONF_KEY_BROWSER_H, data.browserH);
+       data.browserPosition            = j.value(CONF_KEY_BROWSER_POSITION, data.browserPosition);
+       data.browserLastPath            = j.value(CONF_KEY_BROWSER_LAST_PATH, data.browserLastPath);
+       data.browserLastValue           = j.value(CONF_KEY_BROWSER_LAST_VALUE, data.browserLastValue);
+       data.actionEditorX              = j.value(CONF_KEY_ACTION_EDITOR_X, data.actionEditorX);
+       data.actionEditorY              = j.value(CONF_KEY_ACTION_EDITOR_Y, data.actionEditorY);
+       data.actionEditorW              = j.value(CONF_KEY_ACTION_EDITOR_W, data.actionEditorW);
+       data.actionEditorH              = j.value(CONF_KEY_ACTION_EDITOR_H, data.actionEditorH);
+       data.actionEditorZoom           = j.value(CONF_KEY_ACTION_EDITOR_ZOOM, data.actionEditorZoom);
+       data.actionEditorSplitH         = j.value(CONF_KEY_ACTION_EDITOR_SPLIT_H, data.actionEditorSplitH);
+       data.actionEditorGridVal        = j.value(CONF_KEY_ACTION_EDITOR_GRID_VAL, data.actionEditorGridVal);
+       data.actionEditorGridOn         = j.value(CONF_KEY_ACTION_EDITOR_GRID_ON, data.actionEditorGridOn);
+       data.actionEditorPianoRollY     = j.value(CONF_KEY_ACTION_EDITOR_PIANO_ROLL_Y, data.actionEditorPianoRollY);
+       data.sampleEditorX              = j.value(CONF_KEY_SAMPLE_EDITOR_X, data.sampleEditorX);
+       data.sampleEditorY              = j.value(CONF_KEY_SAMPLE_EDITOR_Y, data.sampleEditorY);
+       data.sampleEditorW              = j.value(CONF_KEY_SAMPLE_EDITOR_W, data.sampleEditorW);
+       data.sampleEditorH              = j.value(CONF_KEY_SAMPLE_EDITOR_H, data.sampleEditorH);
+       data.sampleEditorGridVal        = j.value(CONF_KEY_SAMPLE_EDITOR_GRID_VAL, data.sampleEditorGridVal);
+       data.sampleEditorGridOn         = j.value(CONF_KEY_SAMPLE_EDITOR_GRID_ON, data.sampleEditorGridOn);
+       data.pluginListX                = j.value(CONF_KEY_PLUGIN_LIST_X, data.pluginListX);
+       data.pluginListY                = j.value(CONF_KEY_PLUGIN_LIST_Y, data.pluginListY);
+       data.midiInputX                 = j.value(CONF_KEY_MIDI_INPUT_X, data.midiInputX);
+       data.midiInputY                 = j.value(CONF_KEY_MIDI_INPUT_Y, data.midiInputY);
+       data.midiInputW                 = j.value(CONF_KEY_MIDI_INPUT_W, data.midiInputW);
+       data.midiInputH                 = j.value(CONF_KEY_MIDI_INPUT_H, data.midiInputH);
+       data.recTriggerMode             = j.value(CONF_KEY_REC_TRIGGER_MODE, data.recTriggerMode);
+       data.recTriggerLevel            = j.value(CONF_KEY_REC_TRIGGER_LEVEL, data.recTriggerLevel);
+       data.inputRecMode               = j.value(CONF_KEY_INPUT_REC_MODE, data.inputRecMode);
+       data.midiInEnabled              = j.value(CONF_KEY_MIDI_IN, data.midiInEnabled);
+       data.midiInFilter               = j.value(CONF_KEY_MIDI_IN_FILTER, data.midiInFilter);
+       data.midiInRewind               = j.value(CONF_KEY_MIDI_IN_REWIND, data.midiInRewind);
+       data.midiInStartStop            = j.value(CONF_KEY_MIDI_IN_START_STOP, data.midiInStartStop);
+       data.midiInActionRec            = j.value(CONF_KEY_MIDI_IN_ACTION_REC, data.midiInActionRec);
+       data.midiInInputRec             = j.value(CONF_KEY_MIDI_IN_INPUT_REC, data.midiInInputRec);
+       data.midiInMetronome            = j.value(CONF_KEY_MIDI_IN_METRONOME, data.midiInMetronome);
+       data.midiInVolumeIn             = j.value(CONF_KEY_MIDI_IN_VOLUME_IN, data.midiInVolumeIn);
+       data.midiInVolumeOut            = j.value(CONF_KEY_MIDI_IN_VOLUME_OUT, data.midiInVolumeOut);
+       data.midiInBeatDouble           = j.value(CONF_KEY_MIDI_IN_BEAT_DOUBLE, data.midiInBeatDouble);
+       data.midiInBeatHalf             = j.value(CONF_KEY_MIDI_IN_BEAT_HALF, data.midiInBeatHalf);
 #ifdef WITH_VST
-       conf.pluginChooserX   = j.value(CONF_KEY_PLUGIN_CHOOSER_X, conf.pluginChooserX);
-       conf.pluginChooserY   = j.value(CONF_KEY_PLUGIN_CHOOSER_Y, conf.pluginChooserY);
-       conf.pluginChooserW   = j.value(CONF_KEY_PLUGIN_CHOOSER_W, conf.pluginChooserW);
-       conf.pluginChooserH   = j.value(CONF_KEY_PLUGIN_CHOOSER_H, conf.pluginChooserH);
-       conf.pluginSortMethod = j.value(CONF_KEY_PLUGIN_SORT_METHOD, conf.pluginSortMethod);
+       data.pluginChooserX   = j.value(CONF_KEY_PLUGIN_CHOOSER_X, data.pluginChooserX);
+       data.pluginChooserY   = j.value(CONF_KEY_PLUGIN_CHOOSER_Y, data.pluginChooserY);
+       data.pluginChooserW   = j.value(CONF_KEY_PLUGIN_CHOOSER_W, data.pluginChooserW);
+       data.pluginChooserH   = j.value(CONF_KEY_PLUGIN_CHOOSER_H, data.pluginChooserH);
+       data.pluginSortMethod = j.value(CONF_KEY_PLUGIN_SORT_METHOD, data.pluginSortMethod);
 #endif
 
-       sanitize_();
+       sanitize();
 
        return true;
 }
 
 /* -------------------------------------------------------------------------- */
 
-bool write()
+bool Conf::write() const
 {
-       if (!createConfigFolder_())
+       if (!createConfigFolder())
                return false;
 
        nl::json j;
 
        j[CONF_KEY_HEADER]                        = "GIADACFG";
-       j[CONF_KEY_LOG_MODE]                      = conf.logMode;
-       j[CONF_KEY_SHOW_TOOLTIPS]                 = conf.showTooltips;
-       j[CONF_KEY_SOUND_SYSTEM]                  = conf.soundSystem;
-       j[CONF_KEY_SOUND_DEVICE_OUT]              = conf.soundDeviceOut;
-       j[CONF_KEY_SOUND_DEVICE_IN]               = conf.soundDeviceIn;
-       j[CONF_KEY_CHANNELS_OUT_COUNT]            = conf.channelsOutCount;
-       j[CONF_KEY_CHANNELS_OUT_START]            = conf.channelsOutStart;
-       j[CONF_KEY_CHANNELS_IN_COUNT]             = conf.channelsInCount;
-       j[CONF_KEY_CHANNELS_IN_START]             = conf.channelsInStart;
-       j[CONF_KEY_SAMPLERATE]                    = conf.samplerate;
-       j[CONF_KEY_BUFFER_SIZE]                   = conf.buffersize;
-       j[CONF_KEY_LIMIT_OUTPUT]                  = conf.limitOutput;
-       j[CONF_KEY_RESAMPLE_QUALITY]              = conf.rsmpQuality;
-       j[CONF_KEY_MIDI_SYSTEM]                   = conf.midiSystem;
-       j[CONF_KEY_MIDI_PORT_OUT]                 = conf.midiPortOut;
-       j[CONF_KEY_MIDI_PORT_IN]                  = conf.midiPortIn;
-       j[CONF_KEY_MIDIMAP_PATH]                  = conf.midiMapPath;
-       j[CONF_KEY_LAST_MIDIMAP]                  = conf.lastFileMap;
-       j[CONF_KEY_MIDI_SYNC]                     = conf.midiSync;
-       j[CONF_KEY_MIDI_TC_FPS]                   = conf.midiTCfps;
-       j[CONF_KEY_MIDI_IN]                       = conf.midiInEnabled;
-       j[CONF_KEY_MIDI_IN_FILTER]                = conf.midiInFilter;
-       j[CONF_KEY_MIDI_IN_REWIND]                = conf.midiInRewind;
-       j[CONF_KEY_MIDI_IN_START_STOP]            = conf.midiInStartStop;
-       j[CONF_KEY_MIDI_IN_ACTION_REC]            = conf.midiInActionRec;
-       j[CONF_KEY_MIDI_IN_INPUT_REC]             = conf.midiInInputRec;
-       j[CONF_KEY_MIDI_IN_METRONOME]             = conf.midiInMetronome;
-       j[CONF_KEY_MIDI_IN_VOLUME_IN]             = conf.midiInVolumeIn;
-       j[CONF_KEY_MIDI_IN_VOLUME_OUT]            = conf.midiInVolumeOut;
-       j[CONF_KEY_MIDI_IN_BEAT_DOUBLE]           = conf.midiInBeatDouble;
-       j[CONF_KEY_MIDI_IN_BEAT_HALF]             = conf.midiInBeatHalf;
-       j[CONF_KEY_CHANS_STOP_ON_SEQ_HALT]        = conf.chansStopOnSeqHalt;
-       j[CONF_KEY_TREAT_RECS_AS_LOOPS]           = conf.treatRecsAsLoops;
-       j[CONF_KEY_INPUT_MONITOR_DEFAULT_ON]      = conf.inputMonitorDefaultOn;
-       j[CONF_KEY_OVERDUB_PROTECTION_DEFAULT_ON] = conf.overdubProtectionDefaultOn;
-       j[CONF_KEY_PLUGINS_PATH]                  = conf.pluginPath;
-       j[CONF_KEY_PATCHES_PATH]                  = conf.patchPath;
-       j[CONF_KEY_SAMPLES_PATH]                  = conf.samplePath;
-       j[CONF_KEY_MAIN_WINDOW_X]                 = conf.mainWindowX;
-       j[CONF_KEY_MAIN_WINDOW_Y]                 = conf.mainWindowY;
-       j[CONF_KEY_MAIN_WINDOW_W]                 = conf.mainWindowW;
-       j[CONF_KEY_MAIN_WINDOW_H]                 = conf.mainWindowH;
-       j[CONF_KEY_BROWSER_X]                     = conf.browserX;
-       j[CONF_KEY_BROWSER_Y]                     = conf.browserY;
-       j[CONF_KEY_BROWSER_W]                     = conf.browserW;
-       j[CONF_KEY_BROWSER_H]                     = conf.browserH;
-       j[CONF_KEY_BROWSER_POSITION]              = conf.browserPosition;
-       j[CONF_KEY_BROWSER_LAST_PATH]             = conf.browserLastPath;
-       j[CONF_KEY_BROWSER_LAST_VALUE]            = conf.browserLastValue;
-       j[CONF_KEY_ACTION_EDITOR_X]               = conf.actionEditorX;
-       j[CONF_KEY_ACTION_EDITOR_Y]               = conf.actionEditorY;
-       j[CONF_KEY_ACTION_EDITOR_W]               = conf.actionEditorW;
-       j[CONF_KEY_ACTION_EDITOR_H]               = conf.actionEditorH;
-       j[CONF_KEY_ACTION_EDITOR_ZOOM]            = conf.actionEditorZoom;
-       j[CONF_KEY_ACTION_EDITOR_GRID_VAL]        = conf.actionEditorGridVal;
-       j[CONF_KEY_ACTION_EDITOR_GRID_ON]         = conf.actionEditorGridOn;
-       j[CONF_KEY_SAMPLE_EDITOR_X]               = conf.sampleEditorX;
-       j[CONF_KEY_SAMPLE_EDITOR_Y]               = conf.sampleEditorY;
-       j[CONF_KEY_SAMPLE_EDITOR_W]               = conf.sampleEditorW;
-       j[CONF_KEY_SAMPLE_EDITOR_H]               = conf.sampleEditorH;
-       j[CONF_KEY_SAMPLE_EDITOR_GRID_VAL]        = conf.sampleEditorGridVal;
-       j[CONF_KEY_SAMPLE_EDITOR_GRID_ON]         = conf.sampleEditorGridOn;
-       j[CONF_KEY_PIANO_ROLL_Y]                  = conf.pianoRollY;
-       j[CONF_KEY_PIANO_ROLL_H]                  = conf.pianoRollH;
-       j[CONF_KEY_SAMPLE_ACTION_EDITOR_H]        = conf.sampleActionEditorH;
-       j[CONF_KEY_VELOCITY_EDITOR_H]             = conf.velocityEditorH;
-       j[CONF_KEY_ENVELOPE_EDITOR_H]             = conf.envelopeEditorH;
-       j[CONF_KEY_PLUGIN_LIST_X]                 = conf.pluginListX;
-       j[CONF_KEY_PLUGIN_LIST_Y]                 = conf.pluginListY;
-       j[CONF_KEY_MIDI_INPUT_X]                  = conf.midiInputX;
-       j[CONF_KEY_MIDI_INPUT_Y]                  = conf.midiInputY;
-       j[CONF_KEY_MIDI_INPUT_W]                  = conf.midiInputW;
-       j[CONF_KEY_MIDI_INPUT_H]                  = conf.midiInputH;
-       j[CONF_KEY_REC_TRIGGER_MODE]              = static_cast<int>(conf.recTriggerMode);
-       j[CONF_KEY_REC_TRIGGER_LEVEL]             = conf.recTriggerLevel;
-       j[CONF_KEY_INPUT_REC_MODE]                = static_cast<int>(conf.inputRecMode);
+       j[CONF_KEY_LOG_MODE]                      = data.logMode;
+       j[CONF_KEY_SHOW_TOOLTIPS]                 = data.showTooltips;
+       j[CONF_KEY_SOUND_SYSTEM]                  = data.soundSystem;
+       j[CONF_KEY_SOUND_DEVICE_OUT]              = data.soundDeviceOut;
+       j[CONF_KEY_SOUND_DEVICE_IN]               = data.soundDeviceIn;
+       j[CONF_KEY_CHANNELS_OUT_COUNT]            = data.channelsOutCount;
+       j[CONF_KEY_CHANNELS_OUT_START]            = data.channelsOutStart;
+       j[CONF_KEY_CHANNELS_IN_COUNT]             = data.channelsInCount;
+       j[CONF_KEY_CHANNELS_IN_START]             = data.channelsInStart;
+       j[CONF_KEY_SAMPLERATE]                    = data.samplerate;
+       j[CONF_KEY_BUFFER_SIZE]                   = data.buffersize;
+       j[CONF_KEY_LIMIT_OUTPUT]                  = data.limitOutput;
+       j[CONF_KEY_RESAMPLE_QUALITY]              = data.rsmpQuality;
+       j[CONF_KEY_MIDI_SYSTEM]                   = data.midiSystem;
+       j[CONF_KEY_MIDI_PORT_OUT]                 = data.midiPortOut;
+       j[CONF_KEY_MIDI_PORT_IN]                  = data.midiPortIn;
+       j[CONF_KEY_MIDIMAP_PATH]                  = data.midiMapPath;
+       j[CONF_KEY_LAST_MIDIMAP]                  = data.lastFileMap;
+       j[CONF_KEY_MIDI_SYNC]                     = data.midiSync;
+       j[CONF_KEY_MIDI_TC_FPS]                   = data.midiTCfps;
+       j[CONF_KEY_MIDI_IN]                       = data.midiInEnabled;
+       j[CONF_KEY_MIDI_IN_FILTER]                = data.midiInFilter;
+       j[CONF_KEY_MIDI_IN_REWIND]                = data.midiInRewind;
+       j[CONF_KEY_MIDI_IN_START_STOP]            = data.midiInStartStop;
+       j[CONF_KEY_MIDI_IN_ACTION_REC]            = data.midiInActionRec;
+       j[CONF_KEY_MIDI_IN_INPUT_REC]             = data.midiInInputRec;
+       j[CONF_KEY_MIDI_IN_METRONOME]             = data.midiInMetronome;
+       j[CONF_KEY_MIDI_IN_VOLUME_IN]             = data.midiInVolumeIn;
+       j[CONF_KEY_MIDI_IN_VOLUME_OUT]            = data.midiInVolumeOut;
+       j[CONF_KEY_MIDI_IN_BEAT_DOUBLE]           = data.midiInBeatDouble;
+       j[CONF_KEY_MIDI_IN_BEAT_HALF]             = data.midiInBeatHalf;
+       j[CONF_KEY_CHANS_STOP_ON_SEQ_HALT]        = data.chansStopOnSeqHalt;
+       j[CONF_KEY_TREAT_RECS_AS_LOOPS]           = data.treatRecsAsLoops;
+       j[CONF_KEY_INPUT_MONITOR_DEFAULT_ON]      = data.inputMonitorDefaultOn;
+       j[CONF_KEY_OVERDUB_PROTECTION_DEFAULT_ON] = data.overdubProtectionDefaultOn;
+       j[CONF_KEY_PLUGINS_PATH]                  = data.pluginPath;
+       j[CONF_KEY_PATCHES_PATH]                  = data.patchPath;
+       j[CONF_KEY_SAMPLES_PATH]                  = data.samplePath;
+       j[CONF_KEY_MAIN_WINDOW_X]                 = data.mainWindowX;
+       j[CONF_KEY_MAIN_WINDOW_Y]                 = data.mainWindowY;
+       j[CONF_KEY_MAIN_WINDOW_W]                 = data.mainWindowW;
+       j[CONF_KEY_MAIN_WINDOW_H]                 = data.mainWindowH;
+       j[CONF_KEY_BROWSER_X]                     = data.browserX;
+       j[CONF_KEY_BROWSER_Y]                     = data.browserY;
+       j[CONF_KEY_BROWSER_W]                     = data.browserW;
+       j[CONF_KEY_BROWSER_H]                     = data.browserH;
+       j[CONF_KEY_BROWSER_POSITION]              = data.browserPosition;
+       j[CONF_KEY_BROWSER_LAST_PATH]             = data.browserLastPath;
+       j[CONF_KEY_BROWSER_LAST_VALUE]            = data.browserLastValue;
+       j[CONF_KEY_ACTION_EDITOR_X]               = data.actionEditorX;
+       j[CONF_KEY_ACTION_EDITOR_Y]               = data.actionEditorY;
+       j[CONF_KEY_ACTION_EDITOR_W]               = data.actionEditorW;
+       j[CONF_KEY_ACTION_EDITOR_H]               = data.actionEditorH;
+       j[CONF_KEY_ACTION_EDITOR_ZOOM]            = data.actionEditorZoom;
+       j[CONF_KEY_ACTION_EDITOR_SPLIT_H]         = data.actionEditorSplitH;
+       j[CONF_KEY_ACTION_EDITOR_GRID_VAL]        = data.actionEditorGridVal;
+       j[CONF_KEY_ACTION_EDITOR_GRID_ON]         = data.actionEditorGridOn;
+       j[CONF_KEY_ACTION_EDITOR_PIANO_ROLL_Y]    = data.actionEditorPianoRollY;
+       j[CONF_KEY_SAMPLE_EDITOR_X]               = data.sampleEditorX;
+       j[CONF_KEY_SAMPLE_EDITOR_Y]               = data.sampleEditorY;
+       j[CONF_KEY_SAMPLE_EDITOR_W]               = data.sampleEditorW;
+       j[CONF_KEY_SAMPLE_EDITOR_H]               = data.sampleEditorH;
+       j[CONF_KEY_SAMPLE_EDITOR_GRID_VAL]        = data.sampleEditorGridVal;
+       j[CONF_KEY_SAMPLE_EDITOR_GRID_ON]         = data.sampleEditorGridOn;
+       j[CONF_KEY_PLUGIN_LIST_X]                 = data.pluginListX;
+       j[CONF_KEY_PLUGIN_LIST_Y]                 = data.pluginListY;
+       j[CONF_KEY_MIDI_INPUT_X]                  = data.midiInputX;
+       j[CONF_KEY_MIDI_INPUT_Y]                  = data.midiInputY;
+       j[CONF_KEY_MIDI_INPUT_W]                  = data.midiInputW;
+       j[CONF_KEY_MIDI_INPUT_H]                  = data.midiInputH;
+       j[CONF_KEY_REC_TRIGGER_MODE]              = static_cast<int>(data.recTriggerMode);
+       j[CONF_KEY_REC_TRIGGER_LEVEL]             = data.recTriggerLevel;
+       j[CONF_KEY_INPUT_REC_MODE]                = static_cast<int>(data.inputRecMode);
 #ifdef WITH_VST
-       j[CONF_KEY_PLUGIN_CHOOSER_X]   = conf.pluginChooserX;
-       j[CONF_KEY_PLUGIN_CHOOSER_Y]   = conf.pluginChooserY;
-       j[CONF_KEY_PLUGIN_CHOOSER_W]   = conf.pluginChooserW;
-       j[CONF_KEY_PLUGIN_CHOOSER_H]   = conf.pluginChooserH;
-       j[CONF_KEY_PLUGIN_SORT_METHOD] = conf.pluginSortMethod;
+       j[CONF_KEY_PLUGIN_CHOOSER_X]   = data.pluginChooserX;
+       j[CONF_KEY_PLUGIN_CHOOSER_Y]   = data.pluginChooserY;
+       j[CONF_KEY_PLUGIN_CHOOSER_W]   = data.pluginChooserW;
+       j[CONF_KEY_PLUGIN_CHOOSER_H]   = data.pluginChooserH;
+       j[CONF_KEY_PLUGIN_SORT_METHOD] = data.pluginSortMethod;
 #endif
 
-       std::ofstream ofs(confFilePath_);
+       std::ofstream ofs(m_confFilePath);
        if (!ofs.good())
        {
                u::log::print("[conf::write] unable to write configuration file!\n");
@@ -322,4 +256,47 @@ bool write()
        ofs << j;
        return true;
 }
-} // namespace giada::m::conf
\ No newline at end of file
+
+/* -------------------------------------------------------------------------- */
+
+bool Conf::createConfigFolder() const
+{
+#if defined(__linux__) || defined(__FreeBSD__) || defined(__APPLE__)
+
+       if (u::fs::dirExists(m_confDirPath))
+               return true;
+
+       u::log::print("[conf::createConfigFolder] .giada folder not present. Updating...\n");
+
+       if (u::fs::mkdir(m_confDirPath))
+       {
+               u::log::print("[conf::createConfigFolder] status: ok\n");
+               return true;
+       }
+       else
+       {
+               u::log::print("[conf::createConfigFolder] status: error!\n");
+               return false;
+       }
+
+#else // Windows: nothing to do
+
+       return true;
+
+#endif
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Conf::sanitize()
+{
+       data.soundDeviceOut   = std::max(0, data.soundDeviceOut);
+       data.channelsOutCount = G_MAX_IO_CHANS;
+       data.channelsOutStart = std::max(0, data.channelsOutStart);
+       data.channelsInCount  = std::max(1, data.channelsInCount);
+       data.channelsInStart  = std::max(0, data.channelsInStart);
+
+       data.midiPortOut = std::max(-1, data.midiPortOut);
+       data.midiPortIn  = std::max(-1, data.midiPortIn);
+}
+} // namespace giada::m
\ No newline at end of file
index 4d1a2dbb7065c35d6fd0b81fef338cfc2fb3dcd3..a34b544efa15a1c9d9911a919479edc1db0663cd 100644 (file)
 #include "utils/gui.h"
 #include <string>
 
-namespace giada::m::conf
+namespace giada::m
 {
-struct Conf
+class Conf final
 {
-       int  logMode          = LOG_MODE_MUTE;
-       bool showTooltips     = true;
-       int  soundSystem      = G_DEFAULT_SOUNDSYS;
-       int  soundDeviceOut   = G_DEFAULT_SOUNDDEV_OUT;
-       int  soundDeviceIn    = G_DEFAULT_SOUNDDEV_IN;
-       int  channelsOutCount = G_MAX_IO_CHANS;
-       int  channelsOutStart = 0;
-       int  channelsInCount  = 1;
-       int  channelsInStart  = 0;
-       int  samplerate       = G_DEFAULT_SAMPLERATE;
-       int  buffersize       = G_DEFAULT_BUFSIZE;
-       bool limitOutput      = false;
-       int  rsmpQuality      = 0;
-
-       int         midiSystem  = 0;
-       int         midiPortOut = G_DEFAULT_MIDI_PORT_OUT;
-       int         midiPortIn  = G_DEFAULT_MIDI_PORT_IN;
-       std::string midiMapPath = "";
-       std::string lastFileMap = "";
-       int         midiSync    = MIDI_SYNC_NONE;
-       float       midiTCfps   = 25.0f;
-
-       bool chansStopOnSeqHalt         = false;
-       bool treatRecsAsLoops           = false;
-       bool inputMonitorDefaultOn      = false;
-       bool overdubProtectionDefaultOn = false;
-
-       std::string pluginPath;
-       std::string patchPath;
-       std::string samplePath;
-
-       int mainWindowX = u::gui::centerWindowX(G_MIN_GUI_WIDTH);
-       int mainWindowY = u::gui::centerWindowY(G_MIN_GUI_HEIGHT);
-       int mainWindowW = G_MIN_GUI_WIDTH;
-       int mainWindowH = G_MIN_GUI_HEIGHT;
-
-       int         browserX = u::gui::centerWindowX(G_DEFAULT_SUBWINDOW_W);
-       int         browserY = u::gui::centerWindowY(G_DEFAULT_SUBWINDOW_H);
-       int         browserW = G_DEFAULT_SUBWINDOW_W;
-       int         browserH = G_DEFAULT_SUBWINDOW_H;
-       int         browserPosition;
-       int         browserLastValue;
-       std::string browserLastPath;
-
-       int actionEditorY       = u::gui::centerWindowY(G_DEFAULT_SUBWINDOW_H);
-       int actionEditorX       = u::gui::centerWindowX(G_DEFAULT_SUBWINDOW_W);
-       int actionEditorW       = G_DEFAULT_SUBWINDOW_W;
-       int actionEditorH       = G_DEFAULT_SUBWINDOW_H;
-       int actionEditorZoom    = 100;
-       int actionEditorGridVal = 0;
-       int actionEditorGridOn  = false;
-
-       int sampleEditorX;
-       int sampleEditorY;
-       int sampleEditorW       = G_DEFAULT_SUBWINDOW_W;
-       int sampleEditorH       = G_DEFAULT_SUBWINDOW_H;
-       int sampleEditorGridVal = 0;
-       int sampleEditorGridOn  = false;
-
-       int midiInputX;
-       int midiInputY;
-       int midiInputW = G_DEFAULT_SUBWINDOW_W;
-       int midiInputH = G_DEFAULT_SUBWINDOW_H;
-
-       int pianoRollY = -1;
-       int pianoRollH = 422;
-
-       int sampleActionEditorH = 40;
-       int velocityEditorH     = 40;
-       int envelopeEditorH     = 40;
-
-       int pluginListX;
-       int pluginListY;
-
-       RecTriggerMode recTriggerMode  = RecTriggerMode::NORMAL;
-       float          recTriggerLevel = G_DEFAULT_REC_TRIGGER_LEVEL;
-       InputRecMode   inputRecMode    = InputRecMode::FREE;
-
-       bool     midiInEnabled    = false;
-       int      midiInFilter     = -1;
-       uint32_t midiInRewind     = 0x0;
-       uint32_t midiInStartStop  = 0x0;
-       uint32_t midiInActionRec  = 0x0;
-       uint32_t midiInInputRec   = 0x0;
-       uint32_t midiInMetronome  = 0x0;
-       uint32_t midiInVolumeIn   = 0x0;
-       uint32_t midiInVolumeOut  = 0x0;
-       uint32_t midiInBeatDouble = 0x0;
-       uint32_t midiInBeatHalf   = 0x0;
+public:
+       struct Data
+       {
+               int  logMode          = LOG_MODE_MUTE;
+               bool showTooltips     = true;
+               int  soundSystem      = G_DEFAULT_SOUNDSYS;
+               int  soundDeviceOut   = G_DEFAULT_SOUNDDEV_OUT;
+               int  soundDeviceIn    = G_DEFAULT_SOUNDDEV_IN;
+               int  channelsOutCount = G_MAX_IO_CHANS;
+               int  channelsOutStart = 0;
+               int  channelsInCount  = 1;
+               int  channelsInStart  = 0;
+               int  samplerate       = G_DEFAULT_SAMPLERATE;
+               int  buffersize       = G_DEFAULT_BUFSIZE;
+               bool limitOutput      = false;
+               int  rsmpQuality      = 0;
+
+               int         midiSystem  = 0;
+               int         midiPortOut = G_DEFAULT_MIDI_PORT_OUT;
+               int         midiPortIn  = G_DEFAULT_MIDI_PORT_IN;
+               std::string midiMapPath = "";
+               std::string lastFileMap = "";
+               int         midiSync    = G_MIDI_SYNC_NONE;
+               float       midiTCfps   = 25.0f;
+
+               bool chansStopOnSeqHalt         = false;
+               bool treatRecsAsLoops           = false;
+               bool inputMonitorDefaultOn      = false;
+               bool overdubProtectionDefaultOn = false;
+
+               std::string pluginPath;
+               std::string patchPath;
+               std::string samplePath;
+
+               int mainWindowX = u::gui::centerWindowX(G_MIN_GUI_WIDTH);
+               int mainWindowY = u::gui::centerWindowY(G_MIN_GUI_HEIGHT);
+               int mainWindowW = G_MIN_GUI_WIDTH;
+               int mainWindowH = G_MIN_GUI_HEIGHT;
+
+               int         browserX = u::gui::centerWindowX(G_DEFAULT_SUBWINDOW_W);
+               int         browserY = u::gui::centerWindowY(G_DEFAULT_SUBWINDOW_H);
+               int         browserW = G_DEFAULT_SUBWINDOW_W;
+               int         browserH = G_DEFAULT_SUBWINDOW_H;
+               int         browserPosition;
+               int         browserLastValue;
+               std::string browserLastPath;
+
+               int actionEditorY          = u::gui::centerWindowY(G_DEFAULT_SUBWINDOW_H);
+               int actionEditorX          = u::gui::centerWindowX(G_DEFAULT_SUBWINDOW_W);
+               int actionEditorW          = G_DEFAULT_SUBWINDOW_W;
+               int actionEditorH          = G_DEFAULT_SUBWINDOW_H;
+               int actionEditorZoom       = G_DEFAULT_ZOOM_RATIO;
+               int actionEditorSplitH     = -1;
+               int actionEditorGridVal    = 0;
+               int actionEditorGridOn     = false;
+               int actionEditorPianoRollY = -1;
+
+               int sampleEditorX;
+               int sampleEditorY;
+               int sampleEditorW       = G_DEFAULT_SUBWINDOW_W;
+               int sampleEditorH       = G_DEFAULT_SUBWINDOW_H;
+               int sampleEditorGridVal = 0;
+               int sampleEditorGridOn  = false;
+
+               int midiInputX;
+               int midiInputY;
+               int midiInputW = G_DEFAULT_SUBWINDOW_W;
+               int midiInputH = G_DEFAULT_SUBWINDOW_H;
+
+               int pluginListX;
+               int pluginListY;
+
+               RecTriggerMode recTriggerMode  = RecTriggerMode::NORMAL;
+               float          recTriggerLevel = G_DEFAULT_REC_TRIGGER_LEVEL;
+               InputRecMode   inputRecMode    = InputRecMode::FREE;
+
+               bool     midiInEnabled    = false;
+               int      midiInFilter     = -1;
+               uint32_t midiInRewind     = 0x0;
+               uint32_t midiInStartStop  = 0x0;
+               uint32_t midiInActionRec  = 0x0;
+               uint32_t midiInInputRec   = 0x0;
+               uint32_t midiInMetronome  = 0x0;
+               uint32_t midiInVolumeIn   = 0x0;
+               uint32_t midiInVolumeOut  = 0x0;
+               uint32_t midiInBeatDouble = 0x0;
+               uint32_t midiInBeatHalf   = 0x0;
 
 #ifdef WITH_VST
 
-       int pluginChooserX;
-       int pluginChooserY;
-       int pluginChooserW   = G_DEFAULT_SUBWINDOW_W;
-       int pluginChooserH   = G_DEFAULT_SUBWINDOW_H;
-       int pluginSortMethod = 0;
+               int pluginChooserX;
+               int pluginChooserY;
+               int pluginChooserW   = G_DEFAULT_SUBWINDOW_W;
+               int pluginChooserH   = G_DEFAULT_SUBWINDOW_H;
+               int pluginSortMethod = 0;
 
 #endif
-};
+       };
+
+       Conf();
+
+       bool read();
+       bool write() const;
 
-/* -------------------------------------------------------------------------- */
+       Data data;
 
-extern Conf conf;
+private:
+       /* createConfigFolder
+       Creates local folder where to put the configuration file. Path differs from 
+       OS to OS. */
 
-/* -------------------------------------------------------------------------- */
+       bool createConfigFolder() const;
 
-void init();
-bool read();
-bool write();
-} // namespace giada::m::conf
+       void sanitize();
+
+       std::string m_confFilePath;
+       std::string m_confDirPath;
+};
+} // namespace giada::m
 
 #endif
index 8da87cbbc422db3ae675c40fb034ac1432e11d09..35eccbbfb03b46ff1b2cc69cff5cdf36be576d39 100644 (file)
@@ -59,9 +59,9 @@
 
 /* -- version --------------------------------------------------------------- */
 constexpr auto G_APP_NAME      = "Giada";
-constexpr auto G_VERSION_STR   = "0.18.1";
+constexpr auto G_VERSION_STR   = "0.19.1";
 constexpr int  G_VERSION_MAJOR = 0;
-constexpr int  G_VERSION_MINOR = 18;
+constexpr int  G_VERSION_MINOR = 19;
 constexpr int  G_VERSION_PATCH = 1;
 
 constexpr auto CONF_FILENAME = "giada.conf";
@@ -92,7 +92,6 @@ constexpr int   G_GUI_ZOOM_FACTOR    = 2;
 
 #define G_COLOR_RED fl_rgb_color(28, 32, 80)
 #define G_COLOR_BLUE fl_rgb_color(113, 31, 31)
-#define G_COLOR_RED_ALERT fl_rgb_color(239, 75, 53)
 #define G_COLOR_LIGHT_2 fl_rgb_color(200, 200, 200)
 #define G_COLOR_LIGHT_1 fl_rgb_color(170, 170, 170)
 #define G_COLOR_GREY_4 fl_rgb_color(78, 78, 78)
@@ -141,18 +140,20 @@ constexpr int G_SYS_API_PULSE  = 6;
 constexpr int G_SYS_API_WASAPI = 7;
 
 /* -- kernel midi ----------------------------------------------------------- */
-constexpr int G_MIDI_API_JACK = 0x01; // 0000 0001
-constexpr int G_MIDI_API_ALSA = 0x02; // 0000 0010
+constexpr int G_MIDI_API_JACK = 1;
+constexpr int G_MIDI_API_ALSA = 2;
+constexpr int G_MIDI_API_MM   = 3;
+constexpr int G_MIDI_API_CORE = 4;
 
 /* -- default system -------------------------------------------------------- */
 #if defined(G_OS_LINUX)
-#define G_DEFAULT_SOUNDSYS G_SYS_API_NONE
+constexpr int G_DEFAULT_SOUNDSYS = G_SYS_API_NONE;
 #elif defined(G_OS_FREEBSD)
-#define G_DEFAULT_SOUNDSYS G_SYS_API_PULSE
+constexpr int G_DEFAULT_SOUNDSYS = G_SYS_API_PULSE;
 #elif defined(G_OS_WINDOWS)
-#define G_DEFAULT_SOUNDSYS G_SYS_API_DS
+constexpr int G_DEFAULT_SOUNDSYS = G_SYS_API_DS;
 #elif defined(G_OS_MAC)
-#define G_DEFAULT_SOUNDSYS G_SYS_API_CORE
+constexpr int G_DEFAULT_SOUNDSYS = G_SYS_API_CORE;
 #endif
 
 constexpr int   G_DEFAULT_SOUNDDEV_OUT        = -1; // disabled by default
@@ -191,9 +192,9 @@ constexpr int G_RES_ERR               = 0;
 constexpr int G_RES_OK                = 1;
 
 /* -- log modes ------------------------------------------------------------- */
-constexpr int LOG_MODE_STDOUT = 0x01;
-constexpr int LOG_MODE_FILE   = 0x02;
-constexpr int LOG_MODE_MUTE   = 0x04;
+constexpr int LOG_MODE_MUTE   = 0;
+constexpr int LOG_MODE_STDOUT = 1;
+constexpr int LOG_MODE_FILE   = 2;
 
 /* -- unique IDs of mainWin's subwindows ------------------------------------ */
 /* -- wid > 0 are reserved by gg_keyboard ----------------------------------- */
@@ -273,11 +274,11 @@ constexpr int MIDI_EOX          = 0xF7; // end of sysex
 
 /* midi sync constants */
 
-constexpr int MIDI_SYNC_NONE    = 0x00;
-constexpr int MIDI_SYNC_CLOCK_M = 0x01; // master
-constexpr int MIDI_SYNC_CLOCK_S = 0x02; // slave
-constexpr int MIDI_SYNC_MTC_M   = 0x04; // master
-constexpr int MIDI_SYNC_MTC_S   = 0x08; // slave
+constexpr int G_MIDI_SYNC_NONE    = 0;
+constexpr int G_MIDI_SYNC_CLOCK_M = 1; // master
+constexpr int G_MIDI_SYNC_CLOCK_S = 2; // slave
+constexpr int G_MIDI_SYNC_MTC_M   = 3; // master
+constexpr int G_MIDI_SYNC_MTC_S   = 4; // slave
 
 /* JSON patch keys */
 
@@ -423,6 +424,7 @@ constexpr auto CONF_KEY_ACTION_EDITOR_Y               = "action_editor_y";
 constexpr auto CONF_KEY_ACTION_EDITOR_W               = "action_editor_w";
 constexpr auto CONF_KEY_ACTION_EDITOR_H               = "action_editor_h";
 constexpr auto CONF_KEY_ACTION_EDITOR_ZOOM            = "action_editor_zoom";
+constexpr auto CONF_KEY_ACTION_EDITOR_SPLIT_H         = "action_editor_split_h";
 constexpr auto CONF_KEY_ACTION_EDITOR_GRID_VAL        = "action_editor_grid_val";
 constexpr auto CONF_KEY_ACTION_EDITOR_GRID_ON         = "action_editor_grid_on";
 constexpr auto CONF_KEY_SAMPLE_EDITOR_X               = "sample_editor_x";
@@ -431,11 +433,7 @@ constexpr auto CONF_KEY_SAMPLE_EDITOR_W               = "sample_editor_w";
 constexpr auto CONF_KEY_SAMPLE_EDITOR_H               = "sample_editor_h";
 constexpr auto CONF_KEY_SAMPLE_EDITOR_GRID_VAL        = "sample_editor_grid_val";
 constexpr auto CONF_KEY_SAMPLE_EDITOR_GRID_ON         = "sample_editor_grid_on";
-constexpr auto CONF_KEY_PIANO_ROLL_Y                  = "piano_roll_y";
-constexpr auto CONF_KEY_PIANO_ROLL_H                  = "piano_roll_h";
-constexpr auto CONF_KEY_SAMPLE_ACTION_EDITOR_H        = "sample_action_editor_h";
-constexpr auto CONF_KEY_VELOCITY_EDITOR_H             = "velocity_editor_h";
-constexpr auto CONF_KEY_ENVELOPE_EDITOR_H             = "envelope_editor_h";
+constexpr auto CONF_KEY_ACTION_EDITOR_PIANO_ROLL_Y    = "piano_roll_y";
 constexpr auto CONF_KEY_PLUGIN_LIST_X                 = "plugin_list_x";
 constexpr auto CONF_KEY_PLUGIN_LIST_Y                 = "plugin_list_y";
 constexpr auto CONF_KEY_PLUGIN_CHOOSER_X              = "plugin_chooser_x";
diff --git a/src/core/engine.cpp b/src/core/engine.cpp
new file mode 100644 (file)
index 0000000..a71c863
--- /dev/null
@@ -0,0 +1,373 @@
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+#include "core/engine.h"
+#include "core/model/model.h"
+#include "core/model/storage.h"
+#include "utils/fs.h"
+#include "utils/log.h"
+
+namespace giada::m
+{
+Engine::Engine()
+: midiMapper(kernelMidi)
+, channelManager(conf.data, model)
+, midiDispatcher(model)
+, actionRecorder(model)
+, synchronizer(conf.data, kernelMidi)
+, sequencer(model, synchronizer, jackTransport)
+, mixer(model)
+, mixerHandler(model, mixer)
+, recorder(model, sequencer, mixerHandler)
+#ifdef WITH_VST
+, pluginHost(model)
+#endif
+{
+       kernelAudio.onAudioCallback = [this](KernelAudio::CallbackInfo info) {
+               return audioCallback(info);
+       };
+
+       kernelMidi.onMidiReceived = [this](uint32_t msg) { midiDispatcher.dispatch(msg); };
+
+#ifdef WITH_AUDIO_JACK
+       if (kernelAudio.getAPI() == G_SYS_API_JACK)
+               jackTransport.setHandle(kernelAudio.getJackHandle());
+
+       synchronizer.onJackRewind    = [this]() { sequencer.rawRewind(); };
+       synchronizer.onJackChangeBpm = [this](float bpm) { sequencer.rawSetBpm(bpm, kernelAudio.getSampleRate()); };
+       synchronizer.onJackStart     = [this]() { sequencer.rawStart(); };
+       synchronizer.onJackStop      = [this]() { sequencer.rawStop(); };
+#endif
+
+       eventDispatcher.onMidiLearn       = [this](const MidiEvent& e) { midiDispatcher.learn(e); };
+       eventDispatcher.onMidiProcess     = [this](const MidiEvent& e) { midiDispatcher.process(e); };
+       eventDispatcher.onProcessChannels = [this](const EventDispatcher::EventBuffer& eb) {
+               for (Channel& ch : model.get().channels)
+                       ch.react(eb);
+               model.swap(model::SwapType::SOFT); // TODO - is this necessary???
+       };
+       eventDispatcher.onProcessSequencer = [this](const EventDispatcher::EventBuffer& eb) {
+               sequencer.react(eb);
+       };
+       eventDispatcher.onMixerSignalCallback = [this]() {
+               recorder.startInputRecOnCallback();
+       };
+       eventDispatcher.onMixerEndOfRecCallback = [this]() {
+               if (recorder.isRecordingInput())
+                       recorder.stopInputRec(conf.data.inputRecMode, kernelAudio.getSampleRate());
+       };
+
+       midiDispatcher.onDispatch = [this](EventDispatcher::EventType event, Action action) {
+               /* Notify Event Dispatcher when a MIDI signal is received. */
+               eventDispatcher.pumpMidiEvent({event, 0, 0, action});
+       };
+
+       midiDispatcher.onEventReceived = [this]() {
+               recorder.startActionRecOnCallback();
+       };
+
+       mixer.onSignalTresholdReached = [this]() {
+               /* Invokes the signal callback. This is done by pumping a MIXER_SIGNAL_CALLBACK
+        event to the Event Dispatcher, rather than invoking the callback directly.
+        This is done on purpose: the callback might (and surely will) contain 
+        blocking stuff from model:: that the realtime thread cannot perform directly. */
+               eventDispatcher.pumpUIevent({EventDispatcher::EventType::MIXER_SIGNAL_CALLBACK});
+       };
+
+       mixer.onEndOfRecording = [this]() {
+               /* Same rationale as above, for the end-of-recording callback. */
+               eventDispatcher.pumpUIevent({EventDispatcher::EventType::MIXER_END_OF_REC_CALLBACK});
+       };
+
+       mixerHandler.onChannelsAltered = [this]() {
+               if (!recorder.canEnableFreeInputRec())
+                       conf.data.inputRecMode = InputRecMode::RIGID;
+       };
+       mixerHandler.onChannelRecorded = [this](Frame recordedFrames) {
+               std::string filename = "TAKE-" + std::to_string(patch.data.lastTakeId++) + ".wav";
+               return waveManager.createEmpty(recordedFrames, G_MAX_IO_CHANS, kernelAudio.getSampleRate(), filename);
+       };
+
+       sequencer.onAboutStart = [this](SeqStatus status) {
+               /* TODO move this logic to Recorder */
+               if (status == SeqStatus::WAITING)
+                       recorder.stopActionRec(actionRecorder);
+               conf.data.recTriggerMode = RecTriggerMode::NORMAL;
+       };
+
+       sequencer.onAboutStop = [this]() {
+               /* If recordings (both input and action) are active deactivate them, but 
+       store the takes. RecManager takes care of it. */
+               /* TODO move this logic to Recorder */
+               if (recorder.isRecordingAction())
+                       recorder.stopActionRec(actionRecorder);
+               else if (recorder.isRecordingInput())
+                       recorder.stopInputRec(conf.data.inputRecMode, kernelAudio.getSampleRate());
+       };
+
+       sequencer.onBpmChange = [this](float oldVal, float newVal, int quantizerStep) {
+               actionRecorder.updateBpm(oldVal / newVal, quantizerStep);
+       };
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Engine::updateMixerModel()
+{
+       model.get().mixer.limitOutput     = conf.data.limitOutput;
+       model.get().mixer.allowsOverdub   = conf.data.inputRecMode == InputRecMode::RIGID;
+       model.get().mixer.maxFramesToRec  = conf.data.inputRecMode == InputRecMode::FREE ? sequencer.getMaxFramesInLoop(kernelAudio.getSampleRate()) : sequencer.getFramesInLoop();
+       model.get().mixer.recTriggerLevel = conf.data.recTriggerLevel;
+       model.swap(model::SwapType::NONE);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Engine::init()
+{
+       if (!conf.read())
+               u::log::print("[Engine::init] Can't read configuration file! Using default values\n");
+
+       model::load(conf.data);
+
+       if (!u::log::init(conf.data.logMode))
+               u::log::print("[Engine::init] log init failed! Using default stdout\n");
+
+       init::printBuildInfo();
+
+       midiMapper.init();
+       if (midiMapper.read(conf.data.midiMapPath) != MIDIMAP_READ_OK)
+               u::log::print("[Engine::init] MIDI map read failed!\n");
+
+       /* Initialize KernelAudio. If fails, interrupt the Engine initialization:
+    Giada can't work without a functional KernelAudio. */
+
+       kernelAudio.openDevice(conf.data);
+       if (!kernelAudio.isReady())
+               return;
+
+       mixerHandler.reset(sequencer.getMaxFramesInLoop(kernelAudio.getSampleRate()),
+           kernelAudio.getBufferSize(), channelManager);
+       sequencer.reset(kernelAudio.getSampleRate());
+#ifdef WITH_VST
+       pluginHost.reset(kernelAudio.getBufferSize());
+       pluginManager.reset(static_cast<PluginManager::SortMethod>(conf.data.pluginSortMethod));
+#endif
+       mixer.enable();
+       kernelAudio.startStream();
+
+       kernelMidi.openOutDevice(conf.data.midiSystem, conf.data.midiPortOut);
+       kernelMidi.openInDevice(conf.data.midiSystem, conf.data.midiPortIn);
+       kernelMidi.logPorts();
+
+       midiMapper.sendInitMessages(midiMapper.currentMap);
+
+       eventDispatcher.start();
+
+       updateMixerModel();
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Engine::reset()
+{
+       model.reset();
+       mixerHandler.reset(sequencer.getMaxFramesInLoop(kernelAudio.getSampleRate()),
+           kernelAudio.getBufferSize(), channelManager);
+       channelManager.reset();
+       waveManager.reset();
+       synchronizer.reset();
+       sequencer.reset(kernelAudio.getSampleRate());
+       actionRecorder.reset();
+#ifdef WITH_VST
+       pluginHost.reset(kernelAudio.getBufferSize());
+       pluginManager.reset(static_cast<PluginManager::SortMethod>(conf.data.pluginSortMethod));
+#endif
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Engine::shutdown()
+{
+       if (kernelAudio.isReady())
+       {
+               kernelAudio.closeDevice();
+               u::log::print("[Engine::shutdown] KernelAudio closed\n");
+               mixer.disable();
+               u::log::print("[Engine::shutdown] Mixer closed\n");
+       }
+
+       model::store(conf.data);
+       if (!conf.write())
+               u::log::print("[Engine::shutdown] error while saving configuration file!\n");
+       else
+               u::log::print("[Engine::shutdown] configuration saved\n");
+
+       u::log::close();
+
+#ifdef WITH_VST
+       /* Currently the Engine is global/static, and so are all of its sub-components, 
+       Model included. Some plug-ins (JUCE-based ones) crash hard on destructor when 
+       deleted as a result of returning from main, so it's better to free them all first.
+       TODO - investigate this! */
+
+       pluginHost.freeAllPlugins();
+#endif
+}
+
+/* -------------------------------------------------------------------------- */
+
+int Engine::audioCallback(KernelAudio::CallbackInfo kernelInfo)
+{
+       mcl::AudioBuffer out(static_cast<float*>(kernelInfo.outBuf), kernelInfo.bufferSize, kernelInfo.channelsOutCount);
+       mcl::AudioBuffer in;
+       if (kernelInfo.channelsInCount > 0)
+               in = mcl::AudioBuffer(static_cast<float*>(kernelInfo.inBuf), kernelInfo.bufferSize, kernelInfo.channelsInCount);
+
+       /* Clean up output buffer before any rendering. Do this even if mixer is
+       disabled to avoid audio leftovers during a temporary suspension (e.g. when
+       loading a new patch). */
+
+       out.clear();
+
+       if (!kernelInfo.ready)
+               return 0;
+
+       /* Prepare the LayoutLock. From this point on (until out of scope) the 
+       Layout is locked for realtime rendering by the audio thread. Rendering 
+       functions must access the realtime layout coming from layoutLock.get(). */
+
+       const model::LayoutLock layoutLock = model.get_RT();
+       const model::Layout&    layout_RT  = layoutLock.get();
+
+       /* Mixer disabled, nothing to do here. */
+
+       if (!layout_RT.mixer.a_isActive())
+               return 0;
+
+#ifdef WITH_AUDIO_JACK
+       if (kernelInfo.withJack)
+               synchronizer.recvJackSync(jackTransport.getState());
+#endif
+
+       /* If the sequencer is running, advance it first (i.e. parse it for events). 
+       Also advance channels (i.e. let them react to sequencer events), only if the 
+       layout is not locked: another thread might altering channel's data in the 
+       meantime (e.g. Plugins or Waves). */
+
+       if (layout_RT.sequencer.isRunning())
+       {
+               const Sequencer::EventBuffer& events = sequencer.advance(in.countFrames(), actionRecorder);
+               sequencer.render(out);
+               if (!layout_RT.locked)
+                       mixer.advanceChannels(events, layout_RT);
+       }
+
+       /* Then render Mixer: render channels, process I/O. */
+
+       mixer.render(out, in, layout_RT);
+
+       return 0;
+}
+
+/* -------------------------------------------------------------------------- */
+
+bool Engine::store(const std::string& projectName, const std::string& projectPath,
+    const std::string& patchPath)
+{
+       if (!u::fs::mkdir(projectPath))
+       {
+               u::log::print("[Engine::store] Unable to make project directory!\n");
+               return false;
+       }
+
+       u::log::print("[Engine::store] Project dir created: %s\n", projectPath);
+
+       /* Update all existing file paths in Waves, so that they point to the project
+       folder they belong to. */
+
+       for (std::unique_ptr<Wave>& w : model.getAllShared<model::WavePtrs>())
+       {
+               w->setPath(makeUniqueWavePath(projectPath, *w, model.getAllShared<model::WavePtrs>()));
+               waveManager.save(*w, w->getPath()); // TODO - error checking
+       }
+
+       /* Write Model into Patch, then into file. */
+
+       patch.data.name = projectName;
+       model::store(patch.data);
+
+       if (!patch.write(patchPath))
+               return false;
+
+       /* Store the parent folder the project belongs to, in order to reuse it the 
+       next time. */
+
+       conf.data.patchPath = u::fs::getUpDir(u::fs::getUpDir(patchPath));
+
+       u::log::print("[Engine::store] Project patch saved as %s\n", patchPath);
+
+       return true;
+}
+
+/* -------------------------------------------------------------------------- */
+
+int Engine::load(const std::string& projectPath, const std::string& patchPath)
+{
+       u::log::print("[Engine::load] Load project from %s\n", projectPath);
+
+       patch.reset();
+       if (int res = patch.read(patchPath, projectPath); res != G_PATCH_OK)
+               return res;
+
+       /* Then suspend Mixer, reset and fill the model. */
+
+       mixer.disable();
+       reset();
+       m::model::load(patch.data);
+
+       /* Prepare the engine. Recorder has to recompute the actions positions if 
+       the current samplerate != patch samplerate. Clock needs to update frames
+       in sequencer. */
+
+       mixerHandler.updateSoloCount();
+       actionRecorder.updateSamplerate(kernelAudio.getSampleRate(), patch.data.samplerate);
+       sequencer.recomputeFrames(kernelAudio.getSampleRate());
+       mixer.allocRecBuffer(sequencer.getMaxFramesInLoop(kernelAudio.getSampleRate()));
+
+       /* Store the parent folder the project belongs to, in order to reuse it the 
+       next time. */
+
+       conf.data.patchPath = u::fs::getUpDir(projectPath);
+
+       /* Mixer is ready to go back online. */
+
+       mixer.enable();
+
+       return G_PATCH_OK;
+}
+
+} // namespace giada::m
\ No newline at end of file
diff --git a/src/core/engine.h b/src/core/engine.h
new file mode 100644 (file)
index 0000000..b081162
--- /dev/null
@@ -0,0 +1,126 @@
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+#ifndef G_ENGINE_H
+#define G_ENGINE_H
+
+#include "core/actions/actionRecorder.h"
+#include "core/actions/actions.h"
+#include "core/channels/channelManager.h"
+#include "core/conf.h"
+#include "core/eventDispatcher.h"
+#include "core/init.h"
+#include "core/jackTransport.h"
+#include "core/kernelAudio.h"
+#include "core/kernelMidi.h"
+#include "core/midiDispatcher.h"
+#include "core/midiMapper.h"
+#include "core/mixer.h"
+#include "core/mixerHandler.h"
+#include "core/model/model.h"
+#include "core/patch.h"
+#include "core/plugins/pluginHost.h"
+#include "core/plugins/pluginManager.h"
+#include "core/recorder.h"
+#include "core/sequencer.h"
+#include "core/synchronizer.h"
+#include "core/waveManager.h"
+
+namespace giada::m
+{
+class Engine final
+{
+public:
+       /* Engine()
+    Prepares all sub-components by constructing them and setting up the required
+    callback for inter-component communication. It doesn't start the engine yet. */
+
+       Engine();
+
+       /* store
+       Saves the current state to a Patch, then saves it to file. Returns true
+       on success. */
+
+       bool store(const std::string& projectName, const std::string& projectPath,
+           const std::string& patchPath);
+
+       /* load
+       Reads a Patch from file and then de-serialize its content into the model. 
+       Returns G_PATCH_OK on success or any G_PATCH_* on failure. */
+
+       int load(const std::string& projectPath, const std::string& patchPath);
+
+       /* updateMixerModel
+       Updates some values in model::Mixer data struct needed by m::Mixer for the
+       audio rendering. Call this whenever the audio configuration changes. */
+
+       void updateMixerModel();
+
+       /* init
+    Initializes all sub-components. If KernelAudio fails to start, the process
+    interrupts and Giada is put in an invalid state. */
+
+       void init();
+
+       /* reset
+    Resets all sub-components to the initial state. Useful when Giada needs to
+    be brought back to the starup state. */
+
+       void reset();
+
+       /* shutdown
+    Closes the current audio device. */
+
+       void shutdown();
+
+       model::Model           model;
+       Conf                   conf;
+       Patch                  patch;
+       KernelAudio            kernelAudio;
+       KernelMidi             kernelMidi;
+       JackTransport          jackTransport;
+       WaveManager            waveManager;
+       EventDispatcher        eventDispatcher;
+       MidiMapper<KernelMidi> midiMapper;
+       ChannelManager         channelManager;
+       MidiDispatcher         midiDispatcher;
+       ActionRecorder         actionRecorder;
+       Synchronizer           synchronizer;
+       Sequencer              sequencer;
+       Mixer                  mixer;
+       MixerHandler           mixerHandler;
+       Recorder               recorder;
+#ifdef WITH_VST
+       PluginHost    pluginHost;
+       PluginManager pluginManager;
+#endif
+
+private:
+       int audioCallback(KernelAudio::CallbackInfo);
+};
+} // namespace giada::m
+
+#endif
index 6027fe4b8989e6d929a8860a5840ec23e64f6f63..e0c893974dd34d5a9b017c14ba0e6985f49bdf3b 100644 (file)
  *
  * -------------------------------------------------------------------------- */
 
-#include "eventDispatcher.h"
-#include "core/clock.h"
+#include "core/eventDispatcher.h"
 #include "core/const.h"
-#include "core/model/model.h"
-#include "core/sequencer.h"
-#include "core/worker.h"
-#include "utils/log.h"
-#include <functional>
+#include <cassert>
 
-namespace giada::m::eventDispatcher
+namespace giada::m
 {
-namespace
+EventDispatcher::EventDispatcher()
+: onMidiLearn(nullptr)
+, onMidiProcess(nullptr)
+, onProcessChannels(nullptr)
+, onProcessSequencer(nullptr)
+, onMixerSignalCallback(nullptr)
+, onMixerEndOfRecCallback(nullptr)
 {
-Worker worker_;
-
-/* eventBuffer_
-Buffer of events sent to channels for event parsing. This is filled with Events
-coming from the two event queues.*/
-
-EventBuffer eventBuffer_;
+}
 
 /* -------------------------------------------------------------------------- */
 
-void processFuntions_()
+void EventDispatcher::start()
 {
-       for (const Event& e : eventBuffer_)
-       {
-               if (e.type == EventType::FUNCTION)
-                       std::get<std::function<void()>>(e.data)();
-               G_DEBUG("Event type=" << (int)e.type << ", delta=" << e.delta << ", frame=" << clock::getCurrentFrame());
-       }
+       m_worker.start([this]() { process(); }, /*sleep=*/G_EVENT_DISPATCHER_RATE_MS);
 }
 
 /* -------------------------------------------------------------------------- */
 
-void processChannels_()
-{
-       for (channel::Data& ch : model::get().channels)
-               channel::react(ch, eventBuffer_, mixer::isChannelAudible(ch));
-       model::swap(model::SwapType::SOFT);
-}
+void EventDispatcher::pumpUIevent(Event e) { UIevents.push(e); }
+void EventDispatcher::pumpMidiEvent(Event e) { MidiEvents.push(e); }
 
 /* -------------------------------------------------------------------------- */
 
-void processSequencer_()
+void EventDispatcher::processFuntions()
 {
-       sequencer::react(eventBuffer_);
+       assert(onMidiLearn != nullptr);
+       assert(onMidiProcess != nullptr);
+       assert(onMixerSignalCallback != nullptr);
+       assert(onMixerEndOfRecCallback != nullptr);
+
+       for (const Event& e : m_eventBuffer)
+       {
+               switch (e.type)
+               {
+               case EventType::MIDI_DISPATCHER_LEARN:
+                       onMidiLearn(std::get<Action>(e.data).event);
+                       break;
+
+               case EventType::MIDI_DISPATCHER_PROCESS:
+                       onMidiProcess(std::get<Action>(e.data).event);
+                       break;
+
+               case EventType::MIXER_SIGNAL_CALLBACK:
+                       onMixerSignalCallback();
+                       break;
+
+               case EventType::MIXER_END_OF_REC_CALLBACK:
+                       onMixerEndOfRecCallback();
+                       break;
+
+               default:
+                       break;
+               }
+       }
 }
 
 /* -------------------------------------------------------------------------- */
 
-void process_()
+void EventDispatcher::process()
 {
-       eventBuffer_.clear();
+       assert(onProcessChannels != nullptr);
+       assert(onProcessSequencer != nullptr);
+
+       m_eventBuffer.clear();
 
        Event e;
        while (UIevents.pop(e))
-               eventBuffer_.push_back(e);
+               m_eventBuffer.push_back(e);
        while (MidiEvents.pop(e))
-               eventBuffer_.push_back(e);
+               m_eventBuffer.push_back(e);
 
-       if (eventBuffer_.size() == 0)
+       if (m_eventBuffer.size() == 0)
                return;
 
-       processFuntions_();
-       processChannels_();
-       processSequencer_();
-}
-} // namespace
-
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-
-Queue<Event, G_MAX_DISPATCHER_EVENTS> UIevents;
-Queue<Event, G_MAX_DISPATCHER_EVENTS> MidiEvents;
-
-/* -------------------------------------------------------------------------- */
-
-void init()
-{
-       worker_.start(process_, /*sleep=*/G_EVENT_DISPATCHER_RATE_MS);
+       processFuntions();
+       onProcessChannels(m_eventBuffer);
+       onProcessSequencer(m_eventBuffer);
 }
-
-/* -------------------------------------------------------------------------- */
-
-void pumpUIevent(Event e) { UIevents.push(e); }
-void pumpMidiEvent(Event e) { MidiEvents.push(e); }
-} // namespace giada::m::eventDispatcher
\ No newline at end of file
+} // namespace giada::m
\ No newline at end of file
index 094c39c8ba1907034fa868c5c002a696c90bb8d9..b980537b445c3312ef9ac681a65626c9b6302df3 100644 (file)
 #ifndef G_EVENT_DISPATCHER_H
 #define G_EVENT_DISPATCHER_H
 
-#include "core/action.h"
 #include "core/const.h"
 #include "core/queue.h"
 #include "core/ringBuffer.h"
 #include "core/types.h"
+#include "core/worker.h"
+#include "src/core/actions/action.h"
 #include <atomic>
 #include <functional>
 #include <thread>
 #include <variant>
 
-/* giada::m::eventDispatcher
+/* giada::m::EventDispatcher
 Takes events from the two queues (MIDI and UI) filled by c::events and turns 
 them into actual changes in the data model. The EventDispatcher runs in a
 separate worker thread. */
 
-namespace giada::m::eventDispatcher
+namespace giada::m
 {
-enum class EventType
+class EventDispatcher
 {
-       KEY_PRESS,
-       KEY_RELEASE,
-       KEY_KILL,
-       SEQUENCER_START,
-       SEQUENCER_STOP,
-       SEQUENCER_REWIND,
-       MIDI,
-       CHANNEL_TOGGLE_READ_ACTIONS,
-       CHANNEL_KILL_READ_ACTIONS,
-       CHANNEL_TOGGLE_ARM,
-       CHANNEL_MUTE,
-       CHANNEL_SOLO,
-       CHANNEL_VOLUME,
-       CHANNEL_PITCH,
-       CHANNEL_PAN,
-       FUNCTION
-};
+public:
+       enum class EventType
+       {
+               KEY_PRESS,
+               KEY_RELEASE,
+               KEY_KILL,
+               SEQUENCER_START,
+               SEQUENCER_STOP,
+               SEQUENCER_REWIND,
+               MIDI,
+               MIDI_DISPATCHER_LEARN,
+               MIDI_DISPATCHER_PROCESS,
+               MIXER_SIGNAL_CALLBACK,
+               MIXER_END_OF_REC_CALLBACK,
+               CHANNEL_TOGGLE_READ_ACTIONS,
+               CHANNEL_KILL_READ_ACTIONS,
+               CHANNEL_TOGGLE_ARM,
+               CHANNEL_MUTE,
+               CHANNEL_SOLO,
+               CHANNEL_VOLUME,
+               CHANNEL_PITCH,
+               CHANNEL_PAN
+       };
 
-using EventData = std::variant<int, float, Action, std::function<void()>>;
+       struct Event
+       {
+               using EventData = std::variant<int, float, Action>;
 
-struct Event
-{
-       EventType type;
-       Frame     delta     = 0;
-       ID        channelId = 0;
-       EventData data;
-};
+               EventType type;
+               Frame     delta     = 0;
+               ID        channelId = 0;
+               EventData data      = {};
+       };
+
+       /* EventBuffer
+       Alias for a RingBuffer containing events to be sent to engine. The double 
+       size is due to the presence of two distinct Queues for collecting events 
+       coming from other threads. See below. */
+
+       using EventBuffer = RingBuffer<Event, G_MAX_DISPATCHER_EVENTS * 2>;
+
+       EventDispatcher();
+
+       /* start
+       Starts the internal worker on a separate thread. Call this on startup. */
 
-/* EventBuffer
-Alias for a RingBuffer containing events to be sent to engine. The double size
-is due to the presence of two distinct Queues for collecting events coming from
-other threads. See below. */
+       void start();
 
-using EventBuffer = RingBuffer<Event, G_MAX_DISPATCHER_EVENTS * 2>;
+       void pumpUIevent(Event e);
+       void pumpMidiEvent(Event e);
 
-/* Event queues
-Collect events coming from the UI or MIDI devices. Our poor's man Queue is a 
-single-producer/single-consumer one, so we need two queues for two writers. 
-TODO - let's add a multi-producer queue sooner or later! */
+       /* Event queues
+       Collect events coming from the UI or MIDI devices. Our poor man's Queue is a 
+       single-producer/single-consumer one, so we need two queues for two writers. 
+       TODO - let's add a multi-producer queue sooner or later! */
+       /*TODO - make them private*/
 
-extern Queue<Event, G_MAX_DISPATCHER_EVENTS> UIevents;
-extern Queue<Event, G_MAX_DISPATCHER_EVENTS> MidiEvents;
+       Queue<Event, G_MAX_DISPATCHER_EVENTS> UIevents;
+       Queue<Event, G_MAX_DISPATCHER_EVENTS> MidiEvents;
 
-void init();
+       /* on[...]
+       Callbacks fired when something happens in the Event Dispatcher. */
 
-void pumpUIevent(Event e);
-void pumpMidiEvent(Event e);
-} // namespace giada::m::eventDispatcher
+       std::function<void(const MidiEvent& e)> onMidiLearn;
+       std::function<void(const MidiEvent& e)> onMidiProcess;
+       std::function<void(const EventBuffer&)> onProcessChannels;
+       std::function<void(const EventBuffer&)> onProcessSequencer;
+       std::function<void()>                   onMixerSignalCallback;
+       std::function<void()>                   onMixerEndOfRecCallback;
+
+private:
+       void processFuntions();
+       void process();
+
+       /* m_worker
+       A separate thread responsible for the event processing. */
+
+       Worker m_worker;
+
+       /* m_eventBuffer
+       Buffer of events sent to channels for event parsing. This is filled with 
+       Events coming from the two event queues.*/
+
+       EventBuffer m_eventBuffer;
+};
+} // namespace giada::m
 
 #endif
index 2715433473154b98eb25cae65da41309cb76384c..eb41137287a5eec898024d069fa1d98785e2f1df 100644 (file)
@@ -272,6 +272,32 @@ const char* oneshotBasic_xpm[] = {
     "..................",
     ".................."};
 
+const char* oneshotBasicPause_xpm[] = {
+    "18 18 5 1",
+    "  c #252525",
+    ". c #4E4E4E",
+    "+ c #767676",
+    "@ c #9F9F9F",
+    "# c #C8C8C8",
+    "                  ",
+    "                  ",
+    "                  ",
+    "            +##   ",
+    "            +##   ",
+    "            +##   ",
+    "            +##   ",
+    "            +##   ",
+    "            +##   ",
+    "            +##   ",
+    "            +##   ",
+    "            +##   ",
+    "   +++++++++@##   ",
+    "   ############   ",
+    "   ############   ",
+    "                  ",
+    "                  ",
+    "                  "};
+
 const char* oneshotRetrig_xpm[] = {
     "18 18 8 1",
     "  c #181917",
index ce3082af0ba5291a4be094c1a15029f60904495a..6fb2c050b086db2b226da24706d7e1e718528d1d 100644 (file)
@@ -36,6 +36,7 @@ extern const char* loopBasic_xpm[];
 extern const char* loopOnce_xpm[];
 extern const char* loopOnceBar_xpm[];
 extern const char* oneshotBasic_xpm[];
+extern const char* oneshotBasicPause_xpm[];
 extern const char* oneshotRetrig_xpm[];
 extern const char* oneshotPress_xpm[];
 extern const char* oneshotEndless_xpm[];
index fc001420fec10e443cef5f6edf6ad6a44f022c0a..2e34592bb58ecc6a69f48f72c0467b0a229bc135 100644 (file)
@@ -55,8 +55,15 @@ ID IdManager::generate(ID id)
 
 /* -------------------------------------------------------------------------- */
 
-ID IdManager::get()
+ID IdManager::get() const
 {
        return m_id;
 }
+
+/* -------------------------------------------------------------------------- */
+
+ID IdManager::getNext() const
+{
+       return m_id + 1;
+}
 } // namespace giada::m
index a25cccaa719d849c1d90d6f7a064adca1ef065e5..6fe9c6ec7aec515edeee63b31b035f580113c96f 100644 (file)
@@ -51,9 +51,15 @@ public:
        /* get
        Returns the current id, a.k.a. the last generated one. */
 
-       ID get();
+       ID get() const;
 
-  private:
+       /* getNext
+       Returns the upcoming ID, that is the ID that will be generated on the next
+       call. */
+
+       ID getNext() const;
+
+private:
        ID m_id;
 };
 } // namespace giada::m
index 1dee5aa7e925169385dffdfcf6f0f5c9e44bdf1f..9e1504eecd7d2441b6e5af4579cf7e116bb9cbfc 100644 (file)
  *
  * -------------------------------------------------------------------------- */
 
-#include <atomic>
-#include <ctime>
-#include <thread>
 #ifdef __APPLE__
 #include <pwd.h>
 #endif
-#if (defined(__linux__) || defined(__FreeBSD__)) && defined(WITH_VST)
-#include <X11/Xlib.h> // For XInitThreads
-#endif
-#include "core/channels/channelManager.h"
-#include "core/clock.h"
-#include "core/conf.h"
-#include "core/const.h"
-#include "core/eventDispatcher.h"
-#include "core/kernelAudio.h"
-#include "core/kernelMidi.h"
-#include "core/midiMapConf.h"
-#include "core/mixer.h"
-#include "core/mixerHandler.h"
-#include "core/model/model.h"
-#include "core/model/storage.h"
-#include "core/patch.h"
-#include "core/plugins/pluginHost.h"
-#include "core/plugins/pluginManager.h"
-#include "core/recManager.h"
-#include "core/recorder.h"
-#include "core/recorderHandler.h"
-#include "core/sequencer.h"
-#include "core/wave.h"
-#include "core/waveManager.h"
-#include "deps/json/single_include/nlohmann/json.hpp"
-#include "glue/main.h"
-#include "gui/dialogs/mainWindow.h"
+#include "core/engine.h"
 #include "gui/dialogs/warnings.h"
+#include "gui/ui.h"
 #include "gui/updater.h"
-#include "init.h"
-#include "utils/fs.h"
-#include "utils/gui.h"
 #include "utils/log.h"
-#include "utils/time.h"
 #include "utils/ver.h"
+#ifdef WITH_TESTS
+#define CATCH_CONFIG_RUNNER
+#include "tests/actionRecorder.cpp"
+#include "tests/midiLighter.cpp"
+#include "tests/utils.cpp"
+#include "tests/wave.cpp"
+#include "tests/waveFx.cpp"
+#include "tests/waveManager.cpp"
+#include <catch2/catch.hpp>
+#include <string>
+#include <vector>
+#endif
 #include <FL/Fl.H>
 
-extern giada::v::gdMainWindow* G_MainWin;
+extern giada::m::Engine g_engine;
+extern giada::v::Ui     g_ui;
 
 namespace giada::m::init
 {
-namespace
-{
-void initConf_()
-{
-       if (!conf::read())
-               u::log::print("[init] Can't read configuration file! Using default values\n");
-
-       patch::init();
-       midimap::init();
-       midimap::setDefault();
-
-       model::load(conf::conf);
-
-       if (!u::log::init(conf::conf.logMode))
-               u::log::print("[init] log init failed! Using default stdout\n");
-
-       if (midimap::read(conf::conf.midiMapPath) != MIDIMAP_READ_OK)
-               u::log::print("[init] MIDI map read failed!\n");
-}
-
-/* -------------------------------------------------------------------------- */
-
-void initSystem_()
-{
-       model::init();
-       eventDispatcher::init();
-}
-
-/* -------------------------------------------------------------------------- */
-
-void initAudio_()
-{
-       kernelAudio::openDevice();
-       clock::init(conf::conf.samplerate, conf::conf.midiTCfps);
-       mh::init();
-       sequencer::init();
-       recorder::init();
-       recorderHandler::init();
-
-#ifdef WITH_VST
-
-       pluginManager::init(conf::conf.samplerate, kernelAudio::getRealBufSize());
-       pluginHost::init(kernelAudio::getRealBufSize());
-
-#endif
-
-       if (!kernelAudio::isReady())
-               return;
-
-       mixer::enable();
-       kernelAudio::startStream();
-}
-
-/* -------------------------------------------------------------------------- */
-
-void initMIDI_()
-{
-       kernelMidi::setApi(conf::conf.midiSystem);
-       kernelMidi::openOutDevice(conf::conf.midiPortOut);
-       kernelMidi::openInDevice(conf::conf.midiPortIn);
-}
-
-/* -------------------------------------------------------------------------- */
-
-void initGUI_(int argc, char** argv)
-{
-       /* This is of paramount importance on Linux with VST enabled, otherwise many
-       plug-ins go nuts and crash hard. It seems that some plug-ins or our Juce-based
-       PluginHost use Xlib concurrently. */
-
-#if (defined(__linux__) || defined(__FreeBSD__)) && defined(WITH_VST)
-       XInitThreads();
-#endif
-
-       G_MainWin = new v::gdMainWindow(G_MIN_GUI_WIDTH, G_MIN_GUI_HEIGHT, "", argc, argv);
-       G_MainWin->resize(conf::conf.mainWindowX, conf::conf.mainWindowY, conf::conf.mainWindowW,
-           conf::conf.mainWindowH);
-
-       u::gui::updateMainWinLabel(patch::patch.name == "" ? G_DEFAULT_PATCH_NAME : patch::patch.name);
-
-       if (!kernelAudio::isReady())
-               v::gdAlert("Your soundcard isn't configured correctly.\n"
-                          "Check the configuration and restart Giada.");
-
-       v::updater::init();
-       u::gui::updateStaticWidgets();
-}
-
-/* -------------------------------------------------------------------------- */
-
-void shutdownAudio_()
+int tests(int argc, char** argv)
 {
-       if (kernelAudio::isReady())
-       {
-               kernelAudio::closeDevice();
-               u::log::print("[init] KernelAudio closed\n");
-               mh::close();
-               u::log::print("[init] Mixer closed\n");
-       }
-
-       /* TODO - why cleaning plug-ins and mixer memory? Just shutdown the audio
-       device and let the OS take care of the rest. */
-
-#ifdef WITH_VST
-
-       pluginHost::close();
-       u::log::print("[init] PluginHost cleaned up\n");
-
+#ifdef WITH_TESTS
+       std::vector<char*> args(argv, argv + argc);
+       if (args.size() > 1 && strcmp(args[1], "--run-tests") == 0)
+               return Catch::Session().run(args.size() - 1, &args[1]);
+       else
+               return -1;
+#else
+       (void)argc;
+       (void)argv;
+       return -1;
 #endif
 }
 
 /* -------------------------------------------------------------------------- */
 
-void shutdownGUI_()
-{
-       u::gui::closeAllSubwindows();
-
-       u::log::print("[init] All subwindows and UI thread closed\n");
-}
-
-/* -------------------------------------------------------------------------- */
-
-void printBuildInfo_()
+void printBuildInfo()
 {
        u::log::print("[init] Giada %s\n", G_VERSION_STR);
        u::log::print("[init] Build date: " BUILD_DATE "\n");
@@ -213,80 +89,50 @@ void printBuildInfo_()
 #ifdef WITH_VST
        u::log::print("[init]   JUCE - %d.%d.%d\n", JUCE_MAJOR_VERSION, JUCE_MINOR_VERSION, JUCE_BUILDNUMBER);
 #endif
-       kernelAudio::logCompiledAPIs();
+       KernelAudio::logCompiledAPIs();
 }
-} // namespace
 
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
 /* -------------------------------------------------------------------------- */
 
 void startup(int argc, char** argv)
 {
-       printBuildInfo_();
+#ifdef WITH_VST
+       juce::initialiseJuce_GUI();
+#endif
+       g_engine.init();
+       g_ui.init(argc, argv, g_engine);
 
-       initConf_();
-       initSystem_();
-       initAudio_();
-       initMIDI_();
-       initGUI_(argc, argv);
+       if (!g_engine.kernelAudio.isReady())
+               v::gdAlert("Your soundcard isn't configured correctly.\n"
+                          "Check the configuration and restart Giada.");
 }
 
 /* -------------------------------------------------------------------------- */
 
-void closeMainWindow()
+int run()
 {
-       if (!v::gdConfirmWin("Warning", "Quit Giada: are you sure?"))
-               return;
-
-       v::updater::close();
-       G_MainWin->hide();
-       delete G_MainWin;
+       Fl::lock(); // Enable multithreading in FLTK
+       return Fl::run();
 }
 
 /* -------------------------------------------------------------------------- */
 
-void reset()
+void closeMainWindow()
 {
-       u::gui::closeAllSubwindows();
-       G_MainWin->clearKeyboard();
-
-       mh::close();
-#ifdef WITH_VST
-       pluginHost::close();
-#endif
-
-       model::init();
-       channelManager::init();
-       waveManager::init();
-       clock::init(conf::conf.samplerate, conf::conf.midiTCfps);
-       mh::init();
-       sequencer::init();
-       recorder::init();
-#ifdef WITH_VST
-       pluginManager::init(conf::conf.samplerate, kernelAudio::getRealBufSize());
-#endif
-
-       u::gui::updateMainWinLabel(G_DEFAULT_PATCH_NAME);
-       u::gui::updateStaticWidgets();
+       if (!v::gdConfirmWin("Warning", "Quit Giada: are you sure?"))
+               return;
+       shutdown();
 }
 
 /* -------------------------------------------------------------------------- */
 
 void shutdown()
 {
-       shutdownGUI_();
-
-       model::store(conf::conf);
-
-       if (!conf::write())
-               u::log::print("[init] error while saving configuration file!\n");
-       else
-               u::log::print("[init] configuration saved\n");
-
-       shutdownAudio_();
-
+       g_ui.shutdown();
+       g_engine.shutdown();
+#ifdef WITH_VST
+       juce::shutdownJuce_GUI();
+#endif
        u::log::print("[init] Giada %s closed\n\n", G_VERSION_STR);
-       u::log::close();
 }
 } // namespace giada::m::init
index 5370f6c57cdcfb01d64456b59020ea36c76902a4..c29e6efdc8a69f0d5646da81f07c03c4007ec24d 100644 (file)
 
 namespace giada::m::init
 {
+/* tests
+Performs tests, if requested. Returns -1 if no tests are available or the 
+`--run-tests` has not been passed in. */
+
+int tests(int argc, char** argv);
+
+void printBuildInfo();
 void startup(int argc, char** argv);
-void reset();
+int  run();
 void closeMainWindow();
 void shutdown();
 } // namespace giada::m::init
diff --git a/src/core/jackTransport.cpp b/src/core/jackTransport.cpp
new file mode 100644 (file)
index 0000000..45c1c7c
--- /dev/null
@@ -0,0 +1,147 @@
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+#include "jackTransport.h"
+#ifdef WITH_AUDIO_JACK
+#include <jack/intclient.h>
+#include <jack/transport.h>
+#endif
+
+namespace giada::m
+{
+bool JackTransport::State::operator!=(const State& o) const
+{
+       return !(running == o.running && bpm == o.bpm && frame == o.frame);
+}
+
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+
+JackTransport::JackTransport()
+#ifdef WITH_AUDIO_JACK
+: m_jackHandle(nullptr)
+#endif
+{
+}
+
+/* -------------------------------------------------------------------------- */
+
+bool JackTransport::start() const
+{
+#ifdef WITH_AUDIO_JACK
+       if (m_jackHandle == nullptr)
+               return false;
+       jack_transport_start(m_jackHandle);
+       return true;
+#else
+       return false;
+#endif
+}
+
+/* -------------------------------------------------------------------------- */
+
+bool JackTransport::stop() const
+{
+#ifdef WITH_AUDIO_JACK
+       if (m_jackHandle == nullptr)
+               return false;
+       jack_transport_stop(m_jackHandle);
+       return true;
+#else
+       return false;
+#endif
+}
+
+/* -------------------------------------------------------------------------- */
+
+bool JackTransport::setPosition(uint32_t frame) const
+{
+#ifdef WITH_AUDIO_JACK
+       if (m_jackHandle == nullptr)
+               return false;
+       jack_position_t position;
+       jack_transport_query(m_jackHandle, &position);
+       position.frame = frame;
+       jack_transport_reposition(m_jackHandle, &position);
+       return true;
+#else
+       (void)frame;
+       return false;
+#endif
+}
+
+/* -------------------------------------------------------------------------- */
+
+bool JackTransport::setBpm(double bpm) const
+{
+#ifdef WITH_AUDIO_JACK
+       if (m_jackHandle == nullptr)
+               return false;
+       jack_position_t position;
+       jack_transport_query(m_jackHandle, &position);
+       position.valid            = jack_position_bits_t::JackPositionBBT;
+       position.bar              = 0; // no such info from Giada
+       position.beat             = 0; // no such info from Giada
+       position.tick             = 0; // no such info from Giada
+       position.beats_per_minute = bpm;
+       jack_transport_reposition(m_jackHandle, &position);
+       return true;
+#else
+       (void)bpm;
+       return false;
+#endif
+}
+
+/* -------------------------------------------------------------------------- */
+
+JackTransport::State JackTransport::getState() const
+{
+#ifdef WITH_AUDIO_JACK
+       if (m_jackHandle == nullptr)
+               return {};
+
+       jack_position_t        position;
+       jack_transport_state_t ts = jack_transport_query(m_jackHandle, &position);
+
+       return {
+           ts != JackTransportStopped,
+           position.beats_per_minute,
+           position.frame};
+#else
+       return {};
+#endif
+}
+
+/* -------------------------------------------------------------------------- */
+
+#ifdef WITH_AUDIO_JACK
+void JackTransport::setHandle(jack_client_t* h)
+{
+       m_jackHandle = h;
+}
+#endif
+} // namespace giada::m
diff --git a/src/core/jackTransport.h b/src/core/jackTransport.h
new file mode 100644 (file)
index 0000000..5e6f7f7
--- /dev/null
@@ -0,0 +1,74 @@
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+#ifndef G_JACK_TRANSPORT_H
+#define G_JACK_TRANSPORT_H
+
+#ifdef WITH_AUDIO_JACK
+#include <jack/jack.h>
+#endif
+#include <cstdint>
+
+namespace giada::m
+{
+class JackTransport final
+{
+public:
+       struct State
+       {
+               bool     running;
+               double   bpm;
+               uint32_t frame;
+
+               bool operator!=(const State& o) const;
+       };
+
+       JackTransport();
+
+       bool  start() const;
+       bool  stop() const;
+       bool  setPosition(uint32_t frame) const;
+       bool  setBpm(double bpm) const;
+       State getState() const;
+
+#ifdef WITH_AUDIO_JACK
+       void setHandle(jack_client_t*);
+#endif
+
+private:
+       /* m_jackHandle
+       Optional handle to JACK. If nullptr the JackTransport class is not
+       initialized and all public transport methods above will return false. This
+       is useful when you are on a platform that supports JACK (e.g. Linux) but
+       the JACK API is currently not selected. */
+
+#ifdef WITH_AUDIO_JACK
+       jack_client_t* m_jackHandle;
+#endif
+};
+} // namespace giada::m
+
+#endif
index 2ac51ac29607424bf1ccdaf2025b032538f9dbd9..4a52d8806a25060280d89b9513ff004c87e38cc8 100644 (file)
  *
  * -------------------------------------------------------------------------- */
 
-#include "kernelAudio.h"
-#include "conf.h"
-#include "const.h"
-#include "core/clock.h"
-#include "core/mixerHandler.h"
-#include "core/model/model.h"
-#include "core/recManager.h"
-#include "deps/rtaudio/RtAudio.h"
-#include "glue/main.h"
-#include "mixer.h"
+#include "core/kernelAudio.h"
+#include "core/conf.h"
+#include "core/const.h"
 #include "utils/log.h"
 #include "utils/vector.h"
+#include <cassert>
 
-namespace giada::m::kernelAudio
+namespace giada::m
 {
-namespace
+KernelAudio::KernelAudio()
+: onAudioCallback(nullptr)
+, m_ready(false)
+, m_inputEnabled(false)
+, m_realBufferSize(0)
+, m_realSampleRate(0)
+, m_channelsOutCount(0)
+, m_channelsInCount(0)
+, m_api(0)
 {
-std::vector<Device> devices_;
-RtAudio*            rtSystem_     = nullptr;
-bool                inputEnabled_ = false;
-unsigned            realBufsize_  = 0; // Real buffer size from the soundcard
-int                 api_          = 0;
-
-/* -------------------------------------------------------------------------- */
-
-#ifdef WITH_AUDIO_JACK
-
-jack_client_t* jackGetHandle_()
-{
-       return static_cast<jack_client_t*>(rtSystem_->HACK__getJackClient());
-}
-
-#endif
-
-/* -------------------------------------------------------------------------- */
-
-Device fetchDevice_(size_t deviceIndex)
-{
-       try
-       {
-               RtAudio::DeviceInfo info = rtSystem_->getDeviceInfo(deviceIndex);
-
-               if (!info.probed)
-               {
-                       u::log::print("[KA] Can't probe device %d\n", deviceIndex);
-                       return {deviceIndex};
-               }
-
-               return {
-                   deviceIndex,
-                   true,
-                   info.name,
-                   static_cast<int>(info.outputChannels),
-                   static_cast<int>(info.inputChannels),
-                   static_cast<int>(info.duplexChannels),
-                   info.isDefaultOutput,
-                   info.isDefaultInput,
-                   u::vector::cast<int>(info.sampleRates)};
-       }
-       catch (RtAudioError& e)
-       {
-               u::log::print("[KA] Error fetching device %d: %s\n", deviceIndex, e.getMessage());
-               return {0};
-       }
 }
 
 /* -------------------------------------------------------------------------- */
 
-std::vector<Device> fetchDevices_()
+int KernelAudio::openDevice(const Conf::Data& conf)
 {
-       std::vector<Device> out;
-       for (unsigned i = 0; i < rtSystem_->getDeviceCount(); i++)
-               out.push_back(fetchDevice_(i));
-       return out;
-}
+       assert(onAudioCallback != nullptr);
 
-/* -------------------------------------------------------------------------- */
-
-void printDevices_(const std::vector<Device>& devices)
-{
-       u::log::print("[KA] %d device(s) found\n", devices.size());
-       for (const Device& d : devices)
-       {
-               u::log::print("  %d) %s\n", d.index, d.name);
-               u::log::print("      ins=%d outs=%d duplex=%d\n", d.maxInputChannels, d.maxOutputChannels, d.maxDuplexChannels);
-               u::log::print("      isDefaultOut=%d isDefaultIn=%d\n", d.isDefaultOut, d.isDefaultIn);
-               u::log::print("      sampleRates:\n\t");
-               for (int s : d.sampleRates)
-                       u::log::print("%d ", s);
-               u::log::print("\n");
-       }
-}
-
-/* -------------------------------------------------------------------------- */
-
-bool canRender_()
-{
-       return model::get().kernel.audioReady && model::get().mixer.state->active.load() == true;
-}
-
-/* -------------------------------------------------------------------------- */
-
-int callback_(void* outBuf, void* inBuf, unsigned bufferSize, double /*streamTime*/,
-    RtAudioStreamStatus /*status*/, void* /*userData*/)
-{
-       AudioBuffer out(static_cast<float*>(outBuf), bufferSize, G_MAX_IO_CHANS);
-       AudioBuffer in;
-       if (isInputEnabled())
-               in = AudioBuffer(static_cast<float*>(inBuf), bufferSize, conf::conf.channelsInCount);
-
-       /* Clean up output buffer before any rendering. Do this even if mixer is
-       disabled to avoid audio leftovers during a temporary suspension (e.g. when
-       loading a new patch). */
-
-       out.clear();
-
-       if (!canRender_())
-               return 0;
-
-#ifdef WITH_AUDIO_JACK
-       if (getAPI() == G_SYS_API_JACK)
-               clock::recvJackSync();
-#endif
-
-       mixer::RenderInfo info;
-       info.isAudioReady    = model::get().kernel.audioReady;
-       info.hasInput        = isInputEnabled();
-       info.isClockActive   = clock::isActive();
-       info.isClockRunning  = clock::isRunning();
-       info.canLineInRec    = recManager::isRecordingInput() && isInputEnabled();
-       info.limitOutput     = conf::conf.limitOutput;
-       info.inToOut         = mh::getInToOut();
-       info.maxFramesToRec  = conf::conf.inputRecMode == InputRecMode::FREE ? clock::getMaxFramesInLoop() : clock::getFramesInLoop();
-       info.outVol          = mh::getOutVol();
-       info.inVol           = mh::getInVol();
-       info.recTriggerLevel = conf::conf.recTriggerLevel;
-
-       return mixer::render(out, in, info);
-}
-} // namespace
-
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-
-#ifdef WITH_AUDIO_JACK
-
-bool JackState::operator!=(const JackState& o) const
-{
-       return !(running == o.running && bpm == o.bpm && frame == o.frame);
-}
-
-#endif
-
-/* -------------------------------------------------------------------------- */
-
-bool isReady()
-{
-       return model::get().kernel.audioReady;
-}
-
-/* -------------------------------------------------------------------------- */
-
-int openDevice()
-{
-       api_ = conf::conf.soundSystem;
-       u::log::print("[KA] using system 0x%x\n", api_);
+       m_api = conf.soundSystem;
+       u::log::print("[KA] using system 0x%x\n", m_api);
 
 #if defined(__linux__) || defined(__FreeBSD__)
 
-       if (api_ == G_SYS_API_JACK && hasAPI(RtAudio::UNIX_JACK))
-               rtSystem_ = new RtAudio(RtAudio::UNIX_JACK);
-       else if (api_ == G_SYS_API_ALSA && hasAPI(RtAudio::LINUX_ALSA))
-               rtSystem_ = new RtAudio(RtAudio::LINUX_ALSA);
-       else if (api_ == G_SYS_API_PULSE && hasAPI(RtAudio::LINUX_PULSE))
-               rtSystem_ = new RtAudio(RtAudio::LINUX_PULSE);
+       if (m_api == G_SYS_API_JACK && hasAPI(RtAudio::UNIX_JACK))
+               m_rtAudio = std::make_unique<RtAudio>(RtAudio::UNIX_JACK);
+       else if (m_api == G_SYS_API_ALSA && hasAPI(RtAudio::LINUX_ALSA))
+               m_rtAudio = std::make_unique<RtAudio>(RtAudio::LINUX_ALSA);
+       else if (m_api == G_SYS_API_PULSE && hasAPI(RtAudio::LINUX_PULSE))
+               m_rtAudio = std::make_unique<RtAudio>(RtAudio::LINUX_PULSE);
 
 #elif defined(__FreeBSD__)
 
-       if (api_ == G_SYS_API_JACK && hasAPI(RtAudio::UNIX_JACK))
-               rtSystem_ = new RtAudio(RtAudio::UNIX_JACK);
-       else if (api_ == G_SYS_API_PULSE && hasAPI(RtAudio::LINUX_PULSE))
-               rtSystem_ = new RtAudio(RtAudio::LINUX_PULSE);
+       if (m_api == G_SYS_API_JACK && hasAPI(RtAudio::UNIX_JACK))
+               m_rtAudio = std::make_unique<RtAudio>(RtAudio::UNIX_JACK);
+       else if (m_api == G_SYS_API_PULSE && hasAPI(RtAudio::LINUX_PULSE))
+               m_rtAudio = std::make_unique<RtAudio>(RtAudio::LINUX_PULSE);
 
 #elif defined(_WIN32)
 
-       if (api_ == G_SYS_API_DS && hasAPI(RtAudio::WINDOWS_DS))
-               rtSystem_ = new RtAudio(RtAudio::WINDOWS_DS);
-       else if (api_ == G_SYS_API_ASIO && hasAPI(RtAudio::WINDOWS_ASIO))
-               rtSystem_ = new RtAudio(RtAudio::WINDOWS_ASIO);
-       else if (api_ == G_SYS_API_WASAPI && hasAPI(RtAudio::WINDOWS_WASAPI))
-               rtSystem_ = new RtAudio(RtAudio::WINDOWS_WASAPI);
+       if (m_api == G_SYS_API_DS && hasAPI(RtAudio::WINDOWS_DS))
+               m_rtAudio = std::make_unique<RtAudio>(RtAudio::WINDOWS_DS);
+       else if (m_api == G_SYS_API_ASIO && hasAPI(RtAudio::WINDOWS_ASIO))
+               m_rtAudio = std::make_unique<RtAudio>(RtAudio::WINDOWS_ASIO);
+       else if (m_api == G_SYS_API_WASAPI && hasAPI(RtAudio::WINDOWS_WASAPI))
+               m_rtAudio = std::make_unique<RtAudio>(RtAudio::WINDOWS_WASAPI);
 
 #elif defined(__APPLE__)
 
-       if (api_ == G_SYS_API_CORE && hasAPI(RtAudio::MACOSX_CORE))
-               rtSystem_ = new RtAudio(RtAudio::MACOSX_CORE);
+       if (m_api == G_SYS_API_CORE && hasAPI(RtAudio::MACOSX_CORE))
+               m_rtAudio = std::make_unique<RtAudio>(RtAudio::MACOSX_CORE);
 
 #endif
 
@@ -232,15 +94,19 @@ int openDevice()
                return 0;
        }
 
+       m_rtAudio->setErrorCallback([](RtAudioErrorType type, const std::string& msg) {
+               u::log::print("[KA] RtAudio error %d: %s\n", type, msg.c_str());
+       });
+
        u::log::print("[KA] Opening device out=%d, in=%d, samplerate=%d\n",
-           conf::conf.soundDeviceOut, conf::conf.soundDeviceIn, conf::conf.samplerate);
+           conf.soundDeviceOut, conf.soundDeviceIn, conf.samplerate);
 
-       devices_ = fetchDevices_();
-       printDevices_(devices_);
+       m_devices = fetchDevices();
+       printDevices(m_devices);
 
        /* Abort here if devices found are zero. */
 
-       if (devices_.size() == 0)
+       if (m_devices.size() == 0)
        {
                closeDevice();
                return 0;
@@ -249,59 +115,76 @@ int openDevice()
        RtAudio::StreamParameters outParams;
        RtAudio::StreamParameters inParams;
 
-       outParams.deviceId     = conf::conf.soundDeviceOut == G_DEFAULT_SOUNDDEV_OUT ? rtSystem_->getDefaultOutputDevice() : conf::conf.soundDeviceOut;
-       outParams.nChannels    = conf::conf.channelsOutCount;
-       outParams.firstChannel = conf::conf.channelsOutStart;
+       outParams.deviceId     = conf.soundDeviceOut == G_DEFAULT_SOUNDDEV_OUT ? m_rtAudio->getDefaultOutputDevice() : conf.soundDeviceOut;
+       outParams.nChannels    = conf.channelsOutCount;
+       outParams.firstChannel = conf.channelsOutStart;
 
        /* Input device can be disabled. Unlike the output, here we are using all
        channels and let the user choose which one to record from in the configuration
        panel. */
 
-       if (conf::conf.soundDeviceIn != -1)
+       if (conf.soundDeviceIn != -1)
        {
-               inParams.deviceId     = conf::conf.soundDeviceIn;
-               inParams.nChannels    = conf::conf.channelsInCount;
-               inParams.firstChannel = conf::conf.channelsInStart;
-               inputEnabled_         = true;
+               inParams.deviceId     = conf.soundDeviceIn;
+               inParams.nChannels    = conf.channelsInCount;
+               inParams.firstChannel = conf.channelsInStart;
+               m_inputEnabled        = true;
        }
        else
-               inputEnabled_ = false;
+               m_inputEnabled = false;
 
        RtAudio::StreamOptions options;
        options.streamName      = G_APP_NAME;
        options.numberOfBuffers = 4; // TODO - wtf?
 
-       realBufsize_ = conf::conf.buffersize;
+       m_realBufferSize   = conf.buffersize;
+       m_realSampleRate   = conf.samplerate;
+       m_channelsOutCount = conf.channelsOutCount;
+       m_channelsInCount  = conf.channelsInCount;
 
 #ifdef WITH_AUDIO_JACK
 
-       if (api_ == G_SYS_API_JACK)
+       /* If JACK, use its own sample rate, not the one coming from the conf
+       object. */
+
+       if (m_api == G_SYS_API_JACK)
        {
-               conf::conf.samplerate = devices_[conf::conf.soundDeviceOut].sampleRates[0];
-               u::log::print("[KA] JACK in use, samplerate=%d\n", conf::conf.samplerate);
+               assert(m_devices.size() > 0);
+               assert(m_devices[0].sampleRates.size() > 0);
+
+               m_realSampleRate = m_devices[0].sampleRates[0];
+               u::log::print("[KA] JACK in use, samplerate=%d\n", m_realSampleRate);
        }
 
 #endif
 
-       try
+       m_callbackInfo = {
+           /* kernelAudio      = */ this,
+           /* ready            = */ true,
+           /* withJack         = */ getAPI() == G_SYS_API_JACK,
+           /* outBuf           = */ nullptr, // filled later on in audio callback
+           /* inBuf            = */ nullptr, // filled later on in audio callback
+           /* bufferSize       = */ 0,       // filled later on in audio callback
+           /* channelsOutCount = */ m_channelsOutCount,
+           /* channelsInCount  = */ m_channelsInCount};
+
+       RtAudioErrorType res = m_rtAudio->openStream(
+           &outParams,                                     // output params
+           conf.soundDeviceIn != -1 ? &inParams : nullptr, // input params if inDevice is selected
+           RTAUDIO_FLOAT32,                                // audio format
+           m_realSampleRate,                               // sample rate
+           &m_realBufferSize,                              // buffer size in byte
+           &audioCallback,                                 // audio callback
+           &m_callbackInfo,                                // user data passed to callback
+           &options);
+
+       if (res == RtAudioErrorType::RTAUDIO_NO_ERROR)
        {
-               rtSystem_->openStream(
-                   &outParams,                                           // output params
-                   conf::conf.soundDeviceIn != -1 ? &inParams : nullptr, // input params if inDevice is selected
-                   RTAUDIO_FLOAT32,                                      // audio format
-                   conf::conf.samplerate,                                // sample rate
-                   &realBufsize_,                                        // buffer size in byte
-                   &callback_,                                           // audio callback
-                   nullptr,                                              // user data (unused)
-                   &options);
-
-               model::get().kernel.audioReady = true;
-               model::swap(model::SwapType::NONE);
+               m_ready = true;
                return 1;
        }
-       catch (RtAudioError& e)
+       else
        {
-               u::log::print("[KA] rtSystem_ init error: %s\n", e.getMessage());
                closeDevice();
                return 0;
        }
@@ -309,61 +192,59 @@ int openDevice()
 
 /* -------------------------------------------------------------------------- */
 
-int startStream()
+int KernelAudio::startStream()
 {
-       try
+       if (m_rtAudio->startStream() == RtAudioErrorType::RTAUDIO_NO_ERROR)
        {
-               rtSystem_->startStream();
-               u::log::print("[KA] latency = %lu\n", rtSystem_->getStreamLatency());
+               u::log::print("[KA] Start stream - latency = %lu\n", m_rtAudio->getStreamLatency());
                return 1;
        }
-       catch (RtAudioError& e)
-       {
-               u::log::print("[KA] Start stream error: %s\n", e.getMessage());
-               return 0;
-       }
+       return 0;
 }
 
 /* -------------------------------------------------------------------------- */
 
-int stopStream()
+int KernelAudio::stopStream()
 {
-       try
+       if (m_rtAudio->stopStream() == RtAudioErrorType::RTAUDIO_NO_ERROR)
        {
-               rtSystem_->stopStream();
+               u::log::print("[KA] Stop stream\n");
                return 1;
        }
-       catch (RtAudioError& /*e*/)
-       {
-               u::log::print("[KA] Stop stream error\n");
-               return 0;
-       }
+       return 0;
 }
 
 /* -------------------------------------------------------------------------- */
 
-int closeDevice()
+void KernelAudio::closeDevice()
 {
-       if (rtSystem_->isStreamOpen())
-       {
-               rtSystem_->stopStream();
-               rtSystem_->closeStream();
-               delete rtSystem_;
-               rtSystem_ = nullptr;
-       }
-       return 1;
+       if (!m_rtAudio->isStreamOpen())
+               return;
+       m_rtAudio->stopStream();
+       m_rtAudio->closeStream();
+       m_rtAudio.reset(nullptr);
+}
+
+/* -------------------------------------------------------------------------- */
+
+bool KernelAudio::isReady() const
+{
+       return m_ready;
 }
 
 /* -------------------------------------------------------------------------- */
 
-unsigned getRealBufSize() { return realBufsize_; }
-bool     isInputEnabled() { return inputEnabled_; }
+int  KernelAudio::getBufferSize() const { return static_cast<int>(m_realBufferSize); }
+int  KernelAudio::getSampleRate() const { return m_realSampleRate; }
+int  KernelAudio::getChannelsOutCount() const { return m_channelsOutCount; }
+int  KernelAudio::getChannelsInCount() const { return m_channelsInCount; }
+bool KernelAudio::isInputEnabled() const { return m_inputEnabled; }
 
 /* -------------------------------------------------------------------------- */
 
-Device getDevice(const char* name)
+m::KernelAudio::Device KernelAudio::getDevice(const char* name) const
 {
-       for (Device device : devices_)
+       for (Device device : m_devices)
                if (name == device.name)
                        return device;
        return {0, false};
@@ -371,14 +252,23 @@ Device getDevice(const char* name)
 
 /* -------------------------------------------------------------------------- */
 
-const std::vector<Device>& getDevices()
+const std::vector<m::KernelAudio::Device>& KernelAudio::getDevices() const
 {
-       return devices_;
+       return m_devices;
 }
 
 /* -------------------------------------------------------------------------- */
 
-bool hasAPI(int API)
+#ifdef WITH_AUDIO_JACK
+jack_client_t* KernelAudio::getJackHandle() const
+{
+       return static_cast<jack_client_t*>(m_rtAudio->HACK__getJackClient());
+}
+#endif
+
+/* -------------------------------------------------------------------------- */
+
+bool KernelAudio::hasAPI(int API) const
 {
        std::vector<RtAudio::Api> APIs;
        RtAudio::getCompiledApi(APIs);
@@ -388,20 +278,20 @@ bool hasAPI(int API)
        return false;
 }
 
-int getAPI() { return api_; }
+int KernelAudio::getAPI() const { return m_api; }
 
 /* -------------------------------------------------------------------------- */
 
-void logCompiledAPIs()
+void KernelAudio::logCompiledAPIs()
 {
        std::vector<RtAudio::Api> APIs;
        RtAudio::getCompiledApi(APIs);
 
        u::log::print("[KA] Compiled RtAudio APIs: %d\n", APIs.size());
 
-       for (const RtAudio::Api& api_ : APIs)
+       for (const RtAudio::Api& m_api : APIs)
        {
-               switch (api_)
+               switch (m_api)
                {
                case RtAudio::Api::LINUX_ALSA:
                        u::log::print("  ALSA\n");
@@ -436,65 +326,64 @@ void logCompiledAPIs()
 
 /* -------------------------------------------------------------------------- */
 
-#ifdef WITH_AUDIO_JACK
-
-JackState jackTransportQuery()
+m::KernelAudio::Device KernelAudio::fetchDevice(size_t deviceIndex) const
 {
-       if (api_ != G_SYS_API_JACK)
-               return {};
+       RtAudio::DeviceInfo info = m_rtAudio->getDeviceInfo(deviceIndex);
 
-       jack_position_t        position;
-       jack_transport_state_t ts = jack_transport_query(jackGetHandle_(), &position);
+       if (!info.probed)
+       {
+               u::log::print("[KA] Can't probe device %d\n", deviceIndex);
+               return {deviceIndex};
+       }
 
        return {
-           ts != JackTransportStopped,
-           position.beats_per_minute,
-           position.frame};
-}
-
-/* -------------------------------------------------------------------------- */
-
-void jackStart()
-{
-       if (api_ == G_SYS_API_JACK)
-               jack_transport_start(jackGetHandle_());
+           deviceIndex,
+           true,
+           info.name,
+           static_cast<int>(info.outputChannels),
+           static_cast<int>(info.inputChannels),
+           static_cast<int>(info.duplexChannels),
+           info.isDefaultOutput,
+           info.isDefaultInput,
+           u::vector::cast<int>(info.sampleRates)};
 }
 
 /* -------------------------------------------------------------------------- */
 
-void jackSetPosition(uint32_t frame)
+std::vector<m::KernelAudio::Device> KernelAudio::fetchDevices() const
 {
-       if (api_ != G_SYS_API_JACK)
-               return;
-       jack_position_t position;
-       jack_transport_query(jackGetHandle_(), &position);
-       position.frame = frame;
-       jack_transport_reposition(jackGetHandle_(), &position);
+       std::vector<Device> out;
+       for (unsigned i = 0; i < m_rtAudio->getDeviceCount(); i++)
+               out.push_back(fetchDevice(i));
+       return out;
 }
 
 /* -------------------------------------------------------------------------- */
 
-void jackSetBpm(double bpm)
+void KernelAudio::printDevices(const std::vector<m::KernelAudio::Device>& devices) const
 {
-       if (api_ != G_SYS_API_JACK)
-               return;
-       jack_position_t position;
-       jack_transport_query(jackGetHandle_(), &position);
-       position.valid            = jack_position_bits_t::JackPositionBBT;
-       position.bar              = 0; // no such info from Giada
-       position.beat             = 0; // no such info from Giada
-       position.tick             = 0; // no such info from Giada
-       position.beats_per_minute = bpm;
-       jack_transport_reposition(jackGetHandle_(), &position);
+       u::log::print("[KA] %d device(s) found\n", devices.size());
+       for (const m::KernelAudio::Device& d : devices)
+       {
+               u::log::print("  %d) %s\n", d.index, d.name);
+               u::log::print("      ins=%d outs=%d duplex=%d\n", d.maxInputChannels, d.maxOutputChannels, d.maxDuplexChannels);
+               u::log::print("      isDefaultOut=%d isDefaultIn=%d\n", d.isDefaultOut, d.isDefaultIn);
+               u::log::print("      sampleRates:\n\t");
+               for (int s : d.sampleRates)
+                       u::log::print("%d ", s);
+               u::log::print("\n");
+       }
 }
 
 /* -------------------------------------------------------------------------- */
 
-void jackStop()
+int KernelAudio::audioCallback(void* outBuf, void* inBuf, unsigned bufferSize,
+    double /*streamTime*/, RtAudioStreamStatus /*status*/, void*   data)
 {
-       if (api_ == G_SYS_API_JACK)
-               jack_transport_stop(jackGetHandle_());
+       CallbackInfo info = *static_cast<CallbackInfo*>(data);
+       info.outBuf       = outBuf;
+       info.inBuf        = inBuf;
+       info.bufferSize   = bufferSize;
+       return info.kernelAudio->onAudioCallback(info);
 }
-
-#endif // WITH_AUDIO_JACK
-} // namespace giada::m::kernelAudio
+} // namespace giada::m
index 498eebab7df42552e98c82e3e2711783c7ddfa8b..706d662f76080ad3d66cf03785b19bb206473495 100644 (file)
 #ifndef G_KERNELAUDIO_H
 #define G_KERNELAUDIO_H
 
-#include <optional>
+#include "core/conf.h"
+#include "deps/rtaudio/RtAudio.h"
+#include <functional>
+#include <memory>
 #include <string>
 #include <vector>
 #ifdef WITH_AUDIO_JACK
-#include <jack/intclient.h>
-#include <jack/jack.h>
-#include <jack/transport.h>
+#include "core/jackTransport.h"
 #endif
 
-namespace giada::m::kernelAudio
+namespace giada::m
 {
-#ifdef WITH_AUDIO_JACK
-
-struct JackState
+class KernelAudio final
 {
-       bool     running;
-       double   bpm;
-       uint32_t frame;
+public:
+       struct Device
+       {
+               size_t           index             = 0;
+               bool             probed            = false;
+               std::string      name              = "";
+               int              maxOutputChannels = 0;
+               int              maxInputChannels  = 0;
+               int              maxDuplexChannels = 0;
+               bool             isDefaultOut      = false;
+               bool             isDefaultIn       = false;
+               std::vector<int> sampleRates       = {};
+       };
 
-       bool operator!=(const JackState& o) const;
-};
+       struct CallbackInfo
+       {
+               KernelAudio* kernelAudio;
+               bool         ready;
+               bool         withJack;
+               void*        outBuf;
+               void*        inBuf;
+               int          bufferSize;
+               int          channelsOutCount;
+               int          channelsInCount;
+       };
 
-#endif
-
-struct Device
-{
-       size_t           index             = 0;
-       bool             probed            = false;
-       std::string      name              = "";
-       int              maxOutputChannels = 0;
-       int              maxInputChannels  = 0;
-       int              maxDuplexChannels = 0;
-       bool             isDefaultOut      = false;
-       bool             isDefaultIn       = false;
-       std::vector<int> sampleRates       = {};
-};
+       KernelAudio();
 
-int openDevice();
-int closeDevice();
-int startStream();
-int stopStream();
+       static void logCompiledAPIs();
 
-bool                       isReady();
-bool                       isInputEnabled();
-unsigned                   getRealBufSize();
-bool                       hasAPI(int API);
-int                        getAPI();
-void                       logCompiledAPIs();
-Device                     getDevice(const char* name);
-const std::vector<Device>& getDevices();
+       int  openDevice(const Conf::Data& conf);
+       void closeDevice();
+       int  startStream();
+       int  stopStream();
 
+       bool                       isReady() const;
+       bool                       isInputEnabled() const;
+       int                        getBufferSize() const;
+       int                        getSampleRate() const;
+       int                        getChannelsOutCount() const;
+       int                        getChannelsInCount() const;
+       bool                       hasAPI(int API) const;
+       int                        getAPI() const;
+       Device                     getDevice(const char* name) const;
+       const std::vector<Device>& getDevices() const;
 #ifdef WITH_AUDIO_JACK
+       jack_client_t* getJackHandle() const;
+#endif
+
+       /* onAudioCallback
+       Main callback invoked on each audio block. */
+
+       std::function<int(CallbackInfo)> onAudioCallback;
 
-void      jackStart();
-void      jackStop();
-void      jackSetPosition(uint32_t frame);
-void      jackSetBpm(double bpm);
-JackState jackTransportQuery();
+private:
+       static int audioCallback(void*, void*, unsigned, double, RtAudioStreamStatus, void*);
 
+       Device              fetchDevice(size_t deviceIndex) const;
+       std::vector<Device> fetchDevices() const;
+       void                printDevices(const std::vector<Device>& devices) const;
+
+#ifdef WITH_AUDIO_JACK
+       JackTransport m_jackTransport;
 #endif
-} // namespace giada::m::kernelAudio
+       std::vector<Device>      m_devices;
+       std::unique_ptr<RtAudio> m_rtAudio;
+       CallbackInfo             m_callbackInfo;
+       bool                     m_ready;
+       bool                     m_inputEnabled;
+       unsigned                 m_realBufferSize; // Real buffer size from the soundcard
+       int                      m_realSampleRate; // Sample rate might differ if JACK in use
+       int                      m_channelsOutCount;
+       int                      m_channelsInCount;
+       int                      m_api;
+};
+} // namespace giada::m
 
 #endif
index 93306b0d38de43bd899fcca800304fee284548e5..254cee8a40085256bce68a650ee3b331275e3990 100644 (file)
  *
  * -------------------------------------------------------------------------- */
 
-#include "kernelMidi.h"
-#include "const.h"
-#include "midiDispatcher.h"
-#include "midiMapConf.h"
+#include "core/kernelMidi.h"
+#include "core/const.h"
 #include "utils/log.h"
-#include <RtMidi.h>
+#include <cassert>
 
-namespace giada
-{
-namespace m
-{
-namespace kernelMidi
+namespace giada::m
 {
 namespace
 {
-bool       status_      = false;
-int        api_         = 0;
-RtMidiOut* midiOut_     = nullptr;
-RtMidiIn*  midiIn_      = nullptr;
-unsigned   numOutPorts_ = 0;
-unsigned   numInPorts_  = 0;
-
-static void callback_(double /*t*/, std::vector<unsigned char>* msg, void* /*data*/)
+constexpr auto OUTPUT_NAME = "Giada MIDI output";
+constexpr auto INPUT_NAME  = "Giada MIDI input";
+
+/* -------------------------------------------------------------------------- */
+
+std::vector<unsigned char> split_(uint32_t iValue)
 {
-       if (msg->size() < 3)
-       {
-               //u::log::print("[KM] MIDI received - unknown signal - size=%d, value=0x", (int) msg->size());
-               //for (unsigned i=0; i<msg->size(); i++)
-               //      u::log::print("%X", (int) msg->at(i));
-               //u::log::print("\n");
-               return;
-       }
-       midiDispatcher::dispatch(msg->at(0), msg->at(1), msg->at(2));
+       return {
+           static_cast<unsigned char>((iValue >> 24) & 0xFF),
+           static_cast<unsigned char>((iValue >> 16) & 0xFF),
+           static_cast<unsigned char>((iValue >> 8) & 0xFF)};
 }
 
 /* -------------------------------------------------------------------------- */
 
-void sendMidiLightningInitMsgs_()
+uint32_t join_(int b1, int b2, int b3)
 {
-       for (const midimap::Message& m : midimap::midimap.initCommands)
-       {
-               if (m.value != 0x0 && m.channel != -1)
-               {
-                       u::log::print("[KM] MIDI send (init) - Channel %x - Event 0x%X\n", m.channel, m.value);
-                       MidiEvent e(m.value);
-                       e.setChannel(m.channel);
-                       send(e.getRaw());
-               }
-       }
+       return (b1 << 24) | (b2 << 16) | (b3 << 8) | (0x00);
 }
 } // namespace
 
@@ -80,110 +58,52 @@ void sendMidiLightningInitMsgs_()
 /* -------------------------------------------------------------------------- */
 /* -------------------------------------------------------------------------- */
 
-void setApi(int api)
+KernelMidi::KernelMidi()
+: onMidiReceived(nullptr)
 {
-       api_ = api;
-       u::log::print("[KM] using system 0x%x\n", api_);
 }
 
 /* -------------------------------------------------------------------------- */
 
-int openOutDevice(int port)
+bool KernelMidi::openOutDevice(int api, int port)
 {
-       try
-       {
-               midiOut_ = new RtMidiOut((RtMidi::Api)api_, "Giada MIDI Output");
-               status_  = true;
-       }
-       catch (RtMidiError& error)
-       {
-               u::log::print("[KM] MIDI out device error: %s\n", error.getMessage());
-               status_ = false;
-               return 0;
-       }
-
-       /* print output ports */
-
-       numOutPorts_ = midiOut_->getPortCount();
-       u::log::print("[KM] %d output MIDI ports found\n", numOutPorts_);
-       for (unsigned i = 0; i < numOutPorts_; i++)
-               u::log::print("  %d) %s\n", i, getOutPortName(i));
+       if (port == -1)
+               return false;
 
-       /* try to open a port, if enabled */
-
-       if (port != -1 && numOutPorts_ > 0)
-       {
-               try
-               {
-                       midiOut_->openPort(port, getOutPortName(port));
-                       u::log::print("[KM] MIDI out port %d open\n", port);
-
-                       /* TODO - it shold send midiLightning message only if there is a map loaded
-                       and available in midimap:: */
-
-                       sendMidiLightningInitMsgs_();
-                       return 1;
-               }
-               catch (RtMidiError& error)
-               {
-                       u::log::print("[KM] unable to open MIDI out port %d: %s\n", port, error.getMessage());
-                       status_ = false;
-                       return 0;
-               }
-       }
-       else
-               return 2;
+       m_midiOut = makeDevice<RtMidiOut>(api, port, OUTPUT_NAME);
+       return m_midiOut != nullptr;
 }
 
 /* -------------------------------------------------------------------------- */
 
-int openInDevice(int port)
+bool KernelMidi::openInDevice(int api, int port)
 {
-       try
-       {
-               midiIn_ = new RtMidiIn((RtMidi::Api)api_, "Giada MIDI input");
-               status_ = true;
-       }
-       catch (RtMidiError& error)
-       {
-               u::log::print("[KM] MIDI in device error: %s\n", error.getMessage());
-               status_ = false;
-               return 0;
-       }
+       if (port == -1)
+               return false;
 
-       /* print input ports */
+       m_midiIn = makeDevice<RtMidiIn>(api, port, INPUT_NAME);
+       if (m_midiIn == nullptr)
+               return false;
 
-       numInPorts_ = midiIn_->getPortCount();
-       u::log::print("[KM] %d input MIDI ports found\n", numInPorts_);
-       for (unsigned i = 0; i < numInPorts_; i++)
-               u::log::print("  %d) %s\n", i, getInPortName(i));
+       m_midiIn->setCallback(&s_callback, this);
+       m_midiIn->ignoreTypes(true, false, true); // Ignore all system/time msgs, for now
 
-       /* try to open a port, if enabled */
+       return true;
+}
 
-       if (port != -1 && numInPorts_ > 0)
-       {
-               try
-               {
-                       midiIn_->openPort(port, getInPortName(port));
-                       midiIn_->ignoreTypes(true, false, true); // ignore all system/time msgs, for now
-                       u::log::print("[KM] MIDI in port %d open\n", port);
-                       midiIn_->setCallback(&callback_);
-                       return 1;
-               }
-               catch (RtMidiError& error)
-               {
-                       u::log::print("[KM] unable to open MIDI in port %d: %s\n", port, error.getMessage());
-                       status_ = false;
-                       return 0;
-               }
-       }
-       else
-               return 2;
+/* -------------------------------------------------------------------------- */
+
+void KernelMidi::logPorts()
+{
+       if (m_midiOut != nullptr)
+               logPorts(*m_midiOut, OUTPUT_NAME);
+       if (m_midiIn != nullptr)
+               logPorts(*m_midiIn, INPUT_NAME);
 }
 
 /* -------------------------------------------------------------------------- */
 
-bool hasAPI(int API)
+bool KernelMidi::hasAPI(int API) const
 {
        std::vector<RtMidi::Api> APIs;
        RtMidi::getCompiledApi(APIs);
@@ -195,50 +115,27 @@ bool hasAPI(int API)
 
 /* -------------------------------------------------------------------------- */
 
-std::string getOutPortName(unsigned p)
-{
-       try
-       {
-               return midiOut_->getPortName(p);
-       }
-       catch (RtMidiError& /*error*/)
-       {
-               return "";
-       }
-}
-
-std::string getInPortName(unsigned p)
-{
-       try
-       {
-               return midiIn_->getPortName(p);
-       }
-       catch (RtMidiError& /*error*/)
-       {
-               return "";
-       }
-}
+std::string KernelMidi::getOutPortName(unsigned p) const { return getPortName(*m_midiOut, p); }
+std::string KernelMidi::getInPortName(unsigned p) const { return getPortName(*m_midiIn, p); }
 
 /* -------------------------------------------------------------------------- */
 
-void send(uint32_t data)
+void KernelMidi::send(uint32_t data)
 {
-       if (!status_)
+       if (m_midiOut == nullptr)
                return;
 
-       std::vector<unsigned char> msg(1, getB1(data));
-       msg.push_back(getB2(data));
-       msg.push_back(getB3(data));
+       std::vector<unsigned char> msg = split_(data);
 
-       midiOut_->sendMessage(&msg);
+       m_midiOut->sendMessage(&msg);
        u::log::print("[KM::send] send msg=0x%X (%X %X %X)\n", data, msg[0], msg[1], msg[2]);
 }
 
 /* -------------------------------------------------------------------------- */
 
-void send(int b1, int b2, int b3)
+void KernelMidi::send(int b1, int b2, int b3)
 {
-       if (!status_)
+       if (m_midiOut == nullptr)
                return;
 
        std::vector<unsigned char> msg(1, b1);
@@ -248,53 +145,78 @@ void send(int b1, int b2, int b3)
        if (b3 != -1)
                msg.push_back(b3);
 
-       midiOut_->sendMessage(&msg);
+       m_midiOut->sendMessage(&msg);
        u::log::print("[KM::send] send msg=(%X %X %X)\n", b1, b2, b3);
 }
 
 /* -------------------------------------------------------------------------- */
 
-void sendMidiLightning(uint32_t learnt, const midimap::Message& m)
+unsigned KernelMidi::countOutPorts() const { return m_midiOut != nullptr ? m_midiOut->getPortCount() : 0; }
+unsigned KernelMidi::countInPorts() const { return m_midiIn != nullptr ? m_midiIn->getPortCount() : 0; }
+
+/* -------------------------------------------------------------------------- */
+
+void KernelMidi::s_callback(double /*t*/, std::vector<unsigned char>* msg, void* data)
 {
-       // Skip lightning message if not defined in midi map
+       static_cast<KernelMidi*>(data)->callback(msg);
+}
+
+/* -------------------------------------------------------------------------- */
 
-       if (!midimap::isDefined(m))
+void KernelMidi::callback(std::vector<unsigned char>* msg)
+{
+       assert(onMidiReceived != nullptr);
+
+       if (msg->size() < 3)
        {
-               u::log::print("[KM::sendMidiLightning] message skipped (not defined in midimap)");
+               G_DEBUG("Received unknown MIDI signal - bytes=" << msg->size());
                return;
        }
 
-       u::log::print("[KM::sendMidiLightning] learnt=0x%X, chan=%d, msg=0x%X, offset=%d\n",
-           learnt, m.channel, m.value, m.offset);
-
-       /* Isolate 'channel' from learnt message and offset it as requested by 'nn' in 
-       the midimap configuration file. */
-
-       uint32_t out = ((learnt & 0x00FF0000) >> 16) << m.offset;
+       onMidiReceived(join_(msg->at(0), msg->at(1), msg->at(2)));
+}
 
-       /* Merge the previously prepared channel into final message, and finally send 
-       it. */
+/* -------------------------------------------------------------------------- */
 
-       out |= m.value | (m.channel << 24);
-       send(out);
+template <typename Device>
+std::unique_ptr<Device> KernelMidi::makeDevice(int api, int port, std::string name) const
+{
+       try
+       {
+               auto device = std::make_unique<Device>(static_cast<RtMidi::Api>(api), name);
+               device->openPort(port, device->getPortName(port));
+               return device;
+       }
+       catch (RtMidiError& error)
+       {
+               u::log::print("[KM] Device '%s' error on open: %s\n", name.c_str(), error.getMessage());
+               return nullptr;
+       }
 }
 
+template std::unique_ptr<RtMidiOut> KernelMidi::makeDevice(int, int, std::string) const;
+template std::unique_ptr<RtMidiIn>  KernelMidi::makeDevice(int, int, std::string) const;
+
 /* -------------------------------------------------------------------------- */
 
-unsigned countInPorts() { return numInPorts_; }
-unsigned countOutPorts() { return numOutPorts_; }
-bool     getStatus() { return status_; }
+std::string KernelMidi::getPortName(RtMidi& device, int port) const
+{
+       try
+       {
+               return device.getPortName(port);
+       }
+       catch (RtMidiError& /*error*/)
+       {
+               return "";
+       }
+}
 
 /* -------------------------------------------------------------------------- */
 
-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)
+void KernelMidi::logPorts(RtMidi& device, std::string name) const
 {
-       return (b1 << 24) | (b2 << 16) | (b3 << 8) | (0x00);
+       u::log::print("[KM] Device '%s': %d MIDI ports found\n", name.c_str(), device.getPortCount());
+       for (unsigned i = 0; i < device.getPortCount(); i++)
+               u::log::print("  %d) %s\n", i, device.getPortName(i));
 }
-} // namespace kernelMidi
-} // namespace m
-} // namespace giada
+} // namespace giada::m
index e3f2dfd81d6d0fa3be5ef741b003c5bcb7636a70..5489a22e446007a179e1c7bccab39b39fe40ab31 100644 (file)
 #ifndef G_KERNELMIDI_H
 #define G_KERNELMIDI_H
 
-#include "midiMapConf.h"
+#include "midiMapper.h"
+#include <RtMidi.h>
 #include <cstdint>
+#include <functional>
+#include <memory>
 #include <string>
 
-namespace giada
+namespace giada::m
 {
-namespace m
+class KernelMidi final
 {
-namespace kernelMidi
-{
-int getB1(uint32_t iValue);
-int getB2(uint32_t iValue);
-int getB3(uint32_t iValue);
-
-uint32_t getIValue(int b1, int b2, int b3);
+public:
+       KernelMidi();
 
-/* send
-Sends a MIDI message 's' as uint32_t or as separate bytes. */
+       unsigned countOutPorts() const;
+       unsigned countInPorts() const;
 
-void send(uint32_t s);
-void send(int b1, int b2 = -1, int b3 = -1);
+       /* getOut/InPortName
+    Returns the name of the port 'p'. */
 
-/* sendMidiLightning
-Sends a MIDI lightning message defined by 'msg'. */
+       std::string getOutPortName(unsigned p) const;
+       std::string getInPortName(unsigned p) const;
 
-void sendMidiLightning(uint32_t learnt, const midimap::Message& msg);
+       bool hasAPI(int API) const;
 
-/* setApi
-Sets the Api in use for both in & out messages. */
+       /* send
+    Sends a MIDI message 's' as uint32_t or as separate bytes. */
 
-void setApi(int api);
+       void send(uint32_t s);
+       void send(int b1, int b2 = -1, int b3 = -1);
 
-/* getStatus
-Returns current engine status. */
+       /* setApi
+    Sets the Api in use for both in & out messages. */
 
-bool getStatus();
+       void setApi(int api);
 
-/* open/close/in/outDevice */
+       bool openOutDevice(int api, int port);
+       bool openInDevice(int api, int port);
 
-int openOutDevice(int port);
-int openInDevice(int port);
-int closeInDevice();
-int closeOutDevice();
+       void logPorts();
 
-/* getIn/OutPortName
-Returns the name of the port 'p'. */
+       std::function<void(uint32_t)> onMidiReceived;
 
-std::string getInPortName(unsigned p);
-std::string getOutPortName(unsigned p);
+private:
+       template <typename Device>
+       std::unique_ptr<Device> makeDevice(int api, int port, std::string name) const;
 
-unsigned countInPorts();
-unsigned countOutPorts();
+       static void s_callback(double, std::vector<unsigned char>*, void*);
+       void        callback(std::vector<unsigned char>*);
 
-bool hasAPI(int API);
+       std::string getPortName(RtMidi&, int port) const;
+       void        logPorts(RtMidi&, std::string name) const;
 
-} // namespace kernelMidi
-} // namespace m
-} // namespace giada
+       std::unique_ptr<RtMidiOut> m_midiOut;
+       std::unique_ptr<RtMidiIn>  m_midiIn;
+};
+} // namespace giada::m
 
 #endif
index 422077704634c7edea04d9b2571d1613dc347710..1ed5cd75217f4c570b7e9d166980c5727d8e5104 100644 (file)
@@ -25,7 +25,7 @@
  * -------------------------------------------------------------------------- */
 
 #include "metronome.h"
-#include "core/audioBuffer.h"
+#include "deps/mcl-audio-buffer/src/audioBuffer.hpp"
 
 namespace giada::m
 {
@@ -38,7 +38,7 @@ void Metronome::trigger(Click c, Frame o)
 
 /* -------------------------------------------------------------------------- */
 
-void Metronome::render(AudioBuffer& outBuf)
+void Metronome::render(mcl::AudioBuffer& outBuf)
 {
        const float* data = m_click == Click::BEAT ? beat : bar;
        for (Frame f = m_offset; f < outBuf.countFrames() && m_rendering; f++)
index 9a6986d6d66b3ea83be53774421bde0a6a32d6f5..7c8c2a7f149117d0cef0dad72b5af750f9ad471b 100644 (file)
 
 #include "core/types.h"
 
-namespace giada::m
+namespace mcl
 {
 class AudioBuffer;
+}
+namespace giada::m
+{
 class Metronome
 {
 public:
@@ -41,12 +44,12 @@ public:
                BAR
        };
 
-       void render(AudioBuffer& outBuf);
+       void render(mcl::AudioBuffer& outBuf);
        void trigger(Click c, Frame o);
 
        bool running = false;
 
-  private:
+private:
        static constexpr Frame CLICK_SIZE = 38;
 
        static constexpr float beat[CLICK_SIZE] = {
index 559894abc8f7244f1656f11f39b76df574e5230e..239e2348bdfe6845c2836e414fe31bd9e46acff1 100644 (file)
@@ -31,7 +31,7 @@
 #include "core/model/model.h"
 #include "core/plugins/plugin.h"
 #include "core/plugins/pluginHost.h"
-#include "core/recManager.h"
+#include "core/recorder.h"
 #include "core/types.h"
 #include "glue/events.h"
 #include "glue/plugin.h"
 #include <cassert>
 #include <vector>
 
-namespace giada::m::midiDispatcher
+namespace giada::m
 {
-namespace
+MidiDispatcher::MidiDispatcher(model::Model& m)
+: onDispatch(nullptr)
+, m_learnCb(nullptr)
+, m_model(m)
 {
-/* cb_midiLearn, cb_data_
-Callback prepared by the gdMidiGrabber window and called by midiDispatcher. It 
-contains things to do once the midi message has been stored. */
+}
+
+/* -------------------------------------------------------------------------- */
+
+void MidiDispatcher::startChannelLearn(int param, ID channelId, std::function<void()> f)
+{
+       m_learnCb = [=](m::MidiEvent e) { learnChannel(e, param, channelId, f); };
+}
+
+void MidiDispatcher::startMasterLearn(int param, std::function<void()> f)
+{
+       m_learnCb = [=](m::MidiEvent e) { learnMaster(e, param, f); };
+}
 
-std::function<void()>          signalCb_ = nullptr;
-std::function<void(MidiEvent)> learnCb_  = nullptr;
+#ifdef WITH_VST
+
+void MidiDispatcher::startPluginLearn(std::size_t paramIndex, ID pluginId, std::function<void()> f)
+{
+       m_learnCb = [=](m::MidiEvent e) { learnPlugin(e, paramIndex, pluginId, f); };
+}
+
+#endif
+
+void MidiDispatcher::stopLearn()
+{
+       m_learnCb = nullptr;
+}
+
+/* -------------------------------------------------------------------------- */
+
+void MidiDispatcher::clearMasterLearn(int param, std::function<void()> f)
+{
+       learnMaster(MidiEvent(), param, f); // Empty event (0x0)
+}
+
+void MidiDispatcher::clearChannelLearn(int param, ID channelId, std::function<void()> f)
+{
+       learnChannel(MidiEvent(), param, channelId, f); // Empty event (0x0)
+}
+
+#ifdef WITH_VST
+
+void MidiDispatcher::clearPluginLearn(std::size_t paramIndex, ID pluginId, std::function<void()> f)
+{
+       learnPlugin(MidiEvent(), paramIndex, pluginId, f); // Empty event (0x0)
+}
+
+#endif
 
 /* -------------------------------------------------------------------------- */
 
-bool isMasterMidiInAllowed_(int c)
+void MidiDispatcher::dispatch(uint32_t msg)
 {
-       int  filter  = model::get().midiIn.filter;
-       bool enabled = model::get().midiIn.enabled;
+       assert(onDispatch != nullptr);
+
+       /* Here we want to catch two things: a) note on/note off from a MIDI keyboard 
+       and b) knob/wheel/slider movements from a MIDI controller. 
+       We must also fix the velocity zero issue for those devices that sends NOTE
+       OFF events as NOTE ON + velocity zero. Let's make it a real NOTE OFF event. */
+
+       MidiEvent midiEvent(msg);
+       midiEvent.fixVelocityZero();
+
+       u::log::print("[midiDispatcher] MIDI received - 0x%X (chan %d)\n", midiEvent.getRaw(),
+           midiEvent.getChannel());
+
+       /* Start dispatcher. Don't parse channels if MIDI learn is ON, just learn 
+       the incoming MIDI signal. The action is not invoked directly, but scheduled 
+       to be perfomed by the Event Dispatcher. */
+
+       Action action = {0, 0, 0, midiEvent};
+       auto   event  = m_learnCb != nullptr ? EventDispatcher::EventType::MIDI_DISPATCHER_LEARN : EventDispatcher::EventType::MIDI_DISPATCHER_PROCESS;
+
+       onDispatch(event, action);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void MidiDispatcher::learn(const MidiEvent& e)
+{
+       assert(m_learnCb != nullptr);
+       m_learnCb(e);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void MidiDispatcher::process(const MidiEvent& e)
+{
+       assert(onEventReceived != nullptr);
+
+       processMaster(e);
+       processChannels(e);
+       onEventReceived();
+}
+
+/* -------------------------------------------------------------------------- */
+
+bool MidiDispatcher::isMasterMidiInAllowed(int c)
+{
+       int  filter  = m_model.get().midiIn.filter;
+       bool enabled = m_model.get().midiIn.enabled;
        return enabled && (filter == -1 || filter == c);
 }
 
 /* -------------------------------------------------------------------------- */
 
-bool isChannelMidiInAllowed_(ID channelId, int c)
+bool MidiDispatcher::isChannelMidiInAllowed(ID channelId, int c)
 {
-       return model::get().getChannel(channelId).midiLearner.isAllowed(c);
+       return m_model.get().getChannel(channelId).midiLearner.isAllowed(c);
 }
 
 /* -------------------------------------------------------------------------- */
 
 #ifdef WITH_VST
 
-void processPlugins_(const std::vector<Plugin*>& plugins, const MidiEvent& midiEvent)
+void MidiDispatcher::processPlugins(const std::vector<Plugin*>& plugins, const MidiEvent& midiEvent)
 {
        uint32_t pure = midiEvent.getRawNoVelocity();
        float    vf   = u::math::map(midiEvent.getVelocity(), G_MAX_VELOCITY, 1.0f);
@@ -98,11 +189,11 @@ void processPlugins_(const std::vector<Plugin*>& plugins, const MidiEvent& midiE
 
 /* -------------------------------------------------------------------------- */
 
-void processChannels_(const MidiEvent& midiEvent)
+void MidiDispatcher::processChannels(const MidiEvent& midiEvent)
 {
        uint32_t pure = midiEvent.getRawNoVelocity();
 
-       for (const channel::Data& c : model::get().channels)
+       for (const Channel& c : m_model.get().channels)
        {
 
                /* Do nothing on this channel if MIDI in is disabled or filtered out for
@@ -162,7 +253,7 @@ void processChannels_(const MidiEvent& midiEvent)
 
 #ifdef WITH_VST
                /* Process learned plugins parameters. */
-               processPlugins_(c.plugins, midiEvent);
+               processPlugins(c.plugins, midiEvent);
 #endif
 
                /* Redirect raw MIDI message (pure + velocity) to plug-ins in armed
@@ -174,10 +265,10 @@ void processChannels_(const MidiEvent& midiEvent)
 
 /* -------------------------------------------------------------------------- */
 
-void processMaster_(const MidiEvent& midiEvent)
+void MidiDispatcher::processMaster(const MidiEvent& midiEvent)
 {
        const uint32_t       pure   = midiEvent.getRawNoVelocity();
-       const model::MidiIn& midiIn = model::get().midiIn;
+       const model::MidiIn& midiIn = m_model.get().midiIn;
 
        if (pure == midiIn.rewind)
        {
@@ -232,14 +323,14 @@ void processMaster_(const MidiEvent& midiEvent)
 
 /* -------------------------------------------------------------------------- */
 
-void learnChannel_(MidiEvent e, int param, ID channelId, std::function<void()> doneCb)
+void MidiDispatcher::learnChannel(MidiEvent e, int param, ID channelId, std::function<void()> doneCb)
 {
-       if (!isChannelMidiInAllowed_(channelId, e.getChannel()))
+       if (!isChannelMidiInAllowed(channelId, e.getChannel()))
                return;
 
        uint32_t raw = e.getRawNoVelocity();
 
-       channel::Data& ch = model::get().getChannel(channelId);
+       Channel& ch = m_model.get().getChannel(channelId);
 
        switch (param)
        {
@@ -281,15 +372,17 @@ void learnChannel_(MidiEvent e, int param, ID channelId, std::function<void()> d
                break;
        }
 
-       model::swap(model::SwapType::SOFT);
+       m_model.swap(model::SwapType::SOFT);
 
        stopLearn();
        doneCb();
 }
 
-void learnMaster_(MidiEvent e, int param, std::function<void()> doneCb)
+/* -------------------------------------------------------------------------- */
+
+void MidiDispatcher::learnMaster(MidiEvent e, int param, std::function<void()> doneCb)
 {
-       if (!isMasterMidiInAllowed_(e.getChannel()))
+       if (!isMasterMidiInAllowed(e.getChannel()))
                return;
 
        uint32_t raw = e.getRawNoVelocity();
@@ -297,47 +390,48 @@ void learnMaster_(MidiEvent e, int param, std::function<void()> doneCb)
        switch (param)
        {
        case G_MIDI_IN_REWIND:
-               model::get().midiIn.rewind = raw;
+               m_model.get().midiIn.rewind = raw;
                break;
        case G_MIDI_IN_START_STOP:
-               model::get().midiIn.startStop = raw;
+               m_model.get().midiIn.startStop = raw;
                break;
        case G_MIDI_IN_ACTION_REC:
-               model::get().midiIn.actionRec = raw;
+               m_model.get().midiIn.actionRec = raw;
                break;
        case G_MIDI_IN_INPUT_REC:
-               model::get().midiIn.inputRec = raw;
+               m_model.get().midiIn.inputRec = raw;
                break;
        case G_MIDI_IN_METRONOME:
-               model::get().midiIn.metronome = raw;
+               m_model.get().midiIn.metronome = raw;
                break;
        case G_MIDI_IN_VOLUME_IN:
-               model::get().midiIn.volumeIn = raw;
+               m_model.get().midiIn.volumeIn = raw;
                break;
        case G_MIDI_IN_VOLUME_OUT:
-               model::get().midiIn.volumeOut = raw;
+               m_model.get().midiIn.volumeOut = raw;
                break;
        case G_MIDI_IN_BEAT_DOUBLE:
-               model::get().midiIn.beatDouble = raw;
+               m_model.get().midiIn.beatDouble = raw;
                break;
        case G_MIDI_IN_BEAT_HALF:
-               model::get().midiIn.beatHalf = raw;
+               m_model.get().midiIn.beatHalf = raw;
                break;
        }
 
-       model::swap(model::SwapType::SOFT);
+       m_model.swap(model::SwapType::SOFT);
 
        stopLearn();
        doneCb();
 }
 
+/* -------------------------------------------------------------------------- */
+
 #ifdef WITH_VST
 
-void learnPlugin_(MidiEvent e, std::size_t paramIndex, ID pluginId, std::function<void()> doneCb)
+void MidiDispatcher::learnPlugin(MidiEvent e, std::size_t paramIndex, ID pluginId, std::function<void()> doneCb)
 {
-       model::DataLock lock(model::SwapType::NONE);
-
-       Plugin* plugin = model::find<Plugin>(pluginId);
+       model::DataLock lock   = m_model.lockData(model::SwapType::NONE);
+       Plugin*         plugin = m_model.findShared<Plugin>(pluginId);
 
        assert(plugin != nullptr);
        assert(paramIndex < plugin->midiInParams.size());
@@ -349,108 +443,4 @@ void learnPlugin_(MidiEvent e, std::size_t paramIndex, ID pluginId, std::functio
 }
 
 #endif
-
-/* -------------------------------------------------------------------------- */
-
-void triggerSignalCb_()
-{
-       if (signalCb_ == nullptr)
-               return;
-       signalCb_();
-       signalCb_ = nullptr;
-}
-} // namespace
-
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-
-void startChannelLearn(int param, ID channelId, std::function<void()> f)
-{
-       learnCb_ = [=](m::MidiEvent e) { learnChannel_(e, param, channelId, f); };
-}
-
-void startMasterLearn(int param, std::function<void()> f)
-{
-       learnCb_ = [=](m::MidiEvent e) { learnMaster_(e, param, f); };
-}
-
-#ifdef WITH_VST
-
-void startPluginLearn(std::size_t paramIndex, ID pluginId, std::function<void()> f)
-{
-       learnCb_ = [=](m::MidiEvent e) { learnPlugin_(e, paramIndex, pluginId, f); };
-}
-
-#endif
-
-void stopLearn()
-{
-       learnCb_ = nullptr;
-}
-
-/* -------------------------------------------------------------------------- */
-
-void clearMasterLearn(int param, std::function<void()> f)
-{
-       learnMaster_(MidiEvent(), param, f); // Empty event (0x0)
-}
-
-void clearChannelLearn(int param, ID channelId, std::function<void()> f)
-{
-       learnChannel_(MidiEvent(), param, channelId, f); // Empty event (0x0)
-}
-
-#ifdef WITH_VST
-
-void clearPluginLearn(std::size_t paramIndex, ID pluginId, std::function<void()> f)
-{
-       learnPlugin_(MidiEvent(), paramIndex, pluginId, f); // Empty event (0x0)
-}
-
-#endif
-
-/* -------------------------------------------------------------------------- */
-
-void dispatch(int byte1, int byte2, int byte3)
-{
-       /* Here we want to catch two things: a) note on/note off from a keyboard and 
-       b) knob/wheel/slider movements from a controller. 
-       We must also fix the velocity zero issue for those devices that sends NOTE
-       OFF events as NOTE ON + velocity zero. Let's make it a real NOTE OFF event. */
-
-       MidiEvent midiEvent(byte1, byte2, byte3);
-       midiEvent.fixVelocityZero();
-
-       u::log::print("[midiDispatcher] MIDI received - 0x%X (chan %d)\n", midiEvent.getRaw(),
-           midiEvent.getChannel());
-
-       /* Start dispatcher. If midi learn is on don't parse channels, just learn 
-       incoming MIDI signal. Learn callback wants 'pure' MIDI event, i.e. with
-       velocity value stripped off. If midi learn is off process master events first, 
-       then each channel in the stack. This way incoming signals don't get processed 
-       by glue_* when MIDI learning is on. */
-
-       std::function<void()> f = [midiEvent]() {
-               if (learnCb_ != nullptr)
-               {
-                       learnCb_(midiEvent);
-               }
-               else
-               {
-                       processMaster_(midiEvent);
-                       processChannels_(midiEvent);
-                       triggerSignalCb_();
-               }
-       };
-
-       eventDispatcher::pumpMidiEvent({eventDispatcher::EventType::FUNCTION, 0, 0, f});
-}
-
-/* -------------------------------------------------------------------------- */
-
-void setSignalCallback(std::function<void()> f)
-{
-       signalCb_ = f;
-}
-} // namespace giada::m::midiDispatcher
+} // namespace giada::m
index 531aef7371448dfd384335b9b7a9fa0412be561f..b7d0a1d16f5bb510f843b253ae3ee75ef0163927 100644 (file)
 #ifndef G_MIDI_DISPATCHER_H
 #define G_MIDI_DISPATCHER_H
 
+#include "core/actions/action.h"
 #include "core/midiEvent.h"
 #include "core/model/model.h"
 #include "core/types.h"
 #include <cstdint>
 #include <functional>
 
-namespace giada::m::midiDispatcher
+namespace giada::m
 {
-void startChannelLearn(int param, ID channelId, std::function<void()> f);
-void startMasterLearn(int param, std::function<void()> f);
-void stopLearn();
-void clearMasterLearn(int param, std::function<void()> f);
-void clearChannelLearn(int param, ID channelId, std::function<void()> f);
+class MidiDispatcher
+{
+public:
+       MidiDispatcher(model::Model&);
+
+       void startChannelLearn(int param, ID channelId, std::function<void()> f);
+       void startMasterLearn(int param, std::function<void()> f);
+       void stopLearn();
+       void clearMasterLearn(int param, std::function<void()> f);
+       void clearChannelLearn(int param, ID channelId, std::function<void()> f);
 #ifdef WITH_VST
-void startPluginLearn(std::size_t paramIndex, ID pluginId, std::function<void()> f);
-void clearPluginLearn(std::size_t paramIndex, ID pluginId, std::function<void()> f);
+       void startPluginLearn(std::size_t paramIndex, ID pluginId, std::function<void()> f);
+       void clearPluginLearn(std::size_t paramIndex, ID pluginId, std::function<void()> f);
 #endif
 
-void dispatch(int byte1, int byte2, int byte3);
+       /* dispatch
+    Main callback invoked by kernelMidi whenever a new MIDI data comes in. */
+
+       void dispatch(uint32_t msg);
+
+       /* learn
+    Learns event 'e'. Called by the Event Dispatcher. */
+
+       void learn(const MidiEvent& e);
+
+       /* process
+    Sends event 'e' to channels (masters and keyboard). Called by the Event 
+    Dispatcher. */
+
+       void process(const MidiEvent& e);
+
+       /* onDispatch
+       Callback fired when the dispatch() method is invoked by KernelMidi. */
+
+       std::function<void(EventDispatcher::EventType, Action)> onDispatch;
+
+       /* onEventReceived
+       Callback fired when a MIDI event has been received (passed in by the Event
+       Dispatcher). */
+
+       std::function<void()> onEventReceived;
+
+private:
+       bool isMasterMidiInAllowed(int c);
+       bool isChannelMidiInAllowed(ID channelId, int c);
+
+       void processChannels(const MidiEvent& midiEvent);
+       void processMaster(const MidiEvent& midiEvent);
+
+       void learnChannel(MidiEvent e, int param, ID channelId, std::function<void()> doneCb);
+       void learnMaster(MidiEvent e, int param, std::function<void()> doneCb);
+
+#ifdef WITH_VST
+       void processPlugins(const std::vector<Plugin*>& plugins, const MidiEvent& midiEvent);
+       void learnPlugin(MidiEvent e, std::size_t paramIndex, ID pluginId, std::function<void()> doneCb);
+#endif
+
+       /* cb_midiLearn
+    Callback prepared by the gdMidiGrabber window and called by midiDispatcher. 
+    It contains things to do once the midi message has been stored. */
+
+       std::function<void(MidiEvent)> m_learnCb;
 
-void setSignalCallback(std::function<void()> f);
-} // namespace giada::m::midiDispatcher
+       model::Model& m_model;
+};
+} // namespace giada::m
 
 #endif
index 4607111485a6ec700ae088d2674c47af77f74155..22aef5616ad7f82c76610765dce979eda3c0b523 100644 (file)
@@ -42,27 +42,27 @@ constexpr int FLOAT_FACTOR = 10000;
 /* -------------------------------------------------------------------------- */
 /* -------------------------------------------------------------------------- */
 
-MidiEvent::MidiEvent(uint32_t raw)
+MidiEvent::MidiEvent(uint32_t raw, int delta)
 : m_status((raw & 0xF0000000) >> 24)
 , m_channel((raw & 0x0F000000) >> 24)
 , m_note((raw & 0x00FF0000) >> 16)
 , m_velocity((raw & 0x0000FF00) >> 8)
-, m_delta(0) // not used
+, m_delta(delta)
 {
 }
 
 /* -------------------------------------------------------------------------- */
 
 /* static_cast to avoid ambiguity with MidiEvent(float). */
-MidiEvent::MidiEvent(int byte1, int byte2, int byte3)
-: MidiEvent(static_cast<uint32_t>((byte1 << 24) | (byte2 << 16) | (byte3 << 8) | (0x00)))
+MidiEvent::MidiEvent(int byte1, int byte2, int byte3, int delta)
+: MidiEvent(static_cast<uint32_t>((byte1 << 24) | (byte2 << 16) | (byte3 << 8) | (0x00)), delta)
 {
 }
 
 /* -------------------------------------------------------------------------- */
 
-MidiEvent::MidiEvent(float v)
-: MidiEvent(ENVELOPE, 0, 0)
+MidiEvent::MidiEvent(float v, int delta)
+: MidiEvent(ENVELOPE, 0, 0, delta)
 {
        m_velocity = static_cast<int>(v * FLOAT_FACTOR);
 }
index dfdede8044c7d0369259850896b5df4364486fa6..48e967ad862820550fd3a22efc9096d331478be0 100644 (file)
@@ -46,14 +46,14 @@ public:
 
        MidiEvent() = default;
 
-       MidiEvent(uint32_t raw);
-       MidiEvent(int byte1, int byte2, int byte3);
+       MidiEvent(uint32_t raw, int delta = 0);
+       MidiEvent(int byte1, int byte2, int byte3, int delta = 0);
 
        /* MidiEvent (4)
        A constructor that takes a float parameter. Useful to build ENVELOPE events 
        for automations, volume and pitch. */
 
-       MidiEvent(float v);
+       MidiEvent(float v, int delta = 0);
 
        int   getStatus() const;
        int   getChannel() const;
@@ -83,7 +83,7 @@ public:
 
        void fixVelocityZero();
 
-  private:
+private:
        int m_status;
        int m_channel;
        int m_note;
diff --git a/src/core/midiMapConf.cpp b/src/core/midiMapConf.cpp
deleted file mode 100644 (file)
index 240a8de..0000000
+++ /dev/null
@@ -1,217 +0,0 @@
-/* -----------------------------------------------------------------------------
- *
- * Giada - Your Hardcore Loopmachine
- *
- * -----------------------------------------------------------------------------
- *
- * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
- *
- * This file is part of Giada - Your Hardcore Loopmachine.
- *
- * Giada - Your Hardcore Loopmachine is free software: you can
- * redistribute it and/or modify it under the terms of the GNU General
- * Public License as published by the Free Software Foundation, either
- * version 3 of the License, or (at your option) any later version.
- *
- * Giada - Your Hardcore Loopmachine is distributed in the hope that it
- * will be useful, but WITHOUT ANY WARRANTY; without even the implied
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Giada - Your Hardcore Loopmachine. If not, see
- * <http://www.gnu.org/licenses/>.
- *
- * -------------------------------------------------------------------------- */
-
-#include "midiMapConf.h"
-#include "const.h"
-#include "deps/json/single_include/nlohmann/json.hpp"
-#include "utils/fs.h"
-#include "utils/log.h"
-#include "utils/string.h"
-#include <cstring>
-#include <filesystem>
-#include <fstream>
-#include <string>
-#include <vector>
-
-namespace nl = nlohmann;
-
-namespace giada
-{
-namespace m
-{
-namespace midimap
-{
-namespace
-{
-bool readInitCommands_(const nl::json& j)
-{
-       if (j.find(MIDIMAP_KEY_INIT_COMMANDS) == j.end())
-               return false;
-
-       for (const auto& jc : j[MIDIMAP_KEY_INIT_COMMANDS])
-       {
-               Message m;
-               m.channel  = jc[MIDIMAP_KEY_CHANNEL];
-               m.valueStr = jc[MIDIMAP_KEY_MESSAGE];
-               m.value    = strtoul(m.valueStr.c_str(), nullptr, 16);
-
-               midimap.initCommands.push_back(m);
-       }
-
-       return true;
-}
-
-/* -------------------------------------------------------------------------- */
-
-bool readCommand_(const nl::json& j, Message& m, const std::string& key)
-{
-       if (j.find(key) == j.end())
-               return false;
-
-       const nl::json& jc = j[key];
-
-       m.channel  = jc[MIDIMAP_KEY_CHANNEL];
-       m.valueStr = jc[MIDIMAP_KEY_MESSAGE];
-
-       return true;
-}
-
-/* -------------------------------------------------------------------------- */
-
-void parse_(Message& message)
-{
-       /* Remove '0x' part from the original string. */
-
-       std::string input = message.valueStr;
-
-       std::size_t f = input.find("0x"); // check if "0x" is there
-       if (f != std::string::npos)
-               input = message.valueStr.replace(f, 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. */
-
-       std::string output;
-       for (unsigned i = 0, p = 24; i < input.length(); i++, p -= 4)
-       {
-               if (input[i] == 'n')
-               {
-                       output += '0';
-                       if (message.offset == -1) // do it once
-                               message.offset = p;
-               }
-               else
-                       output += input[i];
-       }
-
-       /* From string to uint32_t */
-
-       message.value = strtoul(output.c_str(), nullptr, 16);
-
-       u::log::print("[parse] parsed chan=%d valueStr=%s value=%#x, offset=%d\n",
-           message.channel, message.valueStr, message.value, message.offset);
-}
-} // namespace
-
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-
-MidiMap                  midimap;
-std::string              midimapsPath;
-std::vector<std::string> maps;
-
-/* -------------------------------------------------------------------------- */
-
-void init()
-{
-       midimapsPath = u::fs::getHomePath() + G_SLASH + "midimaps" + G_SLASH;
-
-       /* scan dir of midi maps and load the filenames into <>maps. */
-
-       u::log::print("[midiMapConf::init] scanning midimaps directory '%s'...\n",
-           midimapsPath);
-
-       if (!std::filesystem::exists(midimapsPath))
-       {
-               u::log::print("[midiMapConf::init] unable to scan midimaps directory!\n");
-               return;
-       }
-
-       for (const auto& d : std::filesystem::directory_iterator(midimapsPath))
-       {
-               // TODO - check if is a valid midimap file (verify headers)
-               if (!d.is_regular_file())
-                       continue;
-               u::log::print("[midiMapConf::init] found midimap '%s'\n", d.path().filename().string());
-               maps.push_back(d.path().filename().string());
-       }
-
-       u::log::print("[midiMapConf::init] total midimaps found: %d\n", maps.size());
-}
-
-/* -------------------------------------------------------------------------- */
-
-void setDefault()
-{
-       midimap = MidiMap();
-}
-
-/* -------------------------------------------------------------------------- */
-
-bool isDefined(const Message& m)
-{
-       return m.offset != -1;
-}
-
-/* -------------------------------------------------------------------------- */
-
-int read(const std::string& file)
-{
-       if (file.empty())
-       {
-               u::log::print("[midiMapConf::read] midimap not specified, nothing to do\n");
-               return MIDIMAP_NOT_SPECIFIED;
-       }
-
-       u::log::print("[midiMapConf::read] reading midimap file '%s'\n", file);
-
-       std::ifstream ifs(midimapsPath + file);
-       if (!ifs.good())
-               return MIDIMAP_UNREADABLE;
-
-       nl::json j = nl::json::parse(ifs);
-
-       midimap.brand  = j[MIDIMAP_KEY_BRAND];
-       midimap.device = j[MIDIMAP_KEY_DEVICE];
-
-       if (!readInitCommands_(j))
-               return MIDIMAP_UNREADABLE;
-       if (readCommand_(j, midimap.muteOn, MIDIMAP_KEY_MUTE_ON))
-               parse_(midimap.muteOn);
-       if (readCommand_(j, midimap.muteOff, MIDIMAP_KEY_MUTE_OFF))
-               parse_(midimap.muteOff);
-       if (readCommand_(j, midimap.soloOn, MIDIMAP_KEY_SOLO_ON))
-               parse_(midimap.soloOn);
-       if (readCommand_(j, midimap.soloOff, MIDIMAP_KEY_SOLO_OFF))
-               parse_(midimap.soloOff);
-       if (readCommand_(j, midimap.waiting, MIDIMAP_KEY_WAITING))
-               parse_(midimap.waiting);
-       if (readCommand_(j, midimap.playing, MIDIMAP_KEY_PLAYING))
-               parse_(midimap.playing);
-       if (readCommand_(j, midimap.stopping, MIDIMAP_KEY_STOPPING))
-               parse_(midimap.stopping);
-       if (readCommand_(j, midimap.stopped, MIDIMAP_KEY_STOPPED))
-               parse_(midimap.stopped);
-       if (readCommand_(j, midimap.playingInaudible, MIDIMAP_KEY_PLAYING_INAUDIBLE))
-               parse_(midimap.playingInaudible);
-
-       return MIDIMAP_READ_OK;
-}
-} // namespace midimap
-} // namespace m
-} // namespace giada
diff --git a/src/core/midiMapConf.h b/src/core/midiMapConf.h
deleted file mode 100644 (file)
index 546d2a0..0000000
+++ /dev/null
@@ -1,106 +0,0 @@
-/* -----------------------------------------------------------------------------
- *
- * Giada - Your Hardcore Loopmachine
- *
- * -----------------------------------------------------------------------------
- *
- * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
- *
- * This file is part of Giada - Your Hardcore Loopmachine.
- *
- * Giada - Your Hardcore Loopmachine is free software: you can
- * redistribute it and/or modify it under the terms of the GNU General
- * Public License as published by the Free Software Foundation, either
- * version 3 of the License, or (at your option) any later version.
- *
- * Giada - Your Hardcore Loopmachine is distributed in the hope that it
- * will be useful, but WITHOUT ANY WARRANTY; without even the implied
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Giada - Your Hardcore Loopmachine. If not, see
- * <http://www.gnu.org/licenses/>.
- *
- * -------------------------------------------------------------------------- */
-
-#ifndef G_MIDIMAPCONF_H
-#define G_MIDIMAPCONF_H
-
-#include <string>
-#include <vector>
-
-namespace giada
-{
-namespace m
-{
-namespace midimap
-{
-struct Message
-{
-       int         channel  = 0;
-       std::string valueStr = "";
-       int         offset   = -1;
-       uint32_t    value    = 0;
-};
-
-struct MidiMap
-{
-       std::string          brand;
-       std::string          device;
-       std::vector<Message> initCommands;
-       Message              muteOn;
-       Message              muteOff;
-       Message              soloOn;
-       Message              soloOff;
-       Message              waiting;
-       Message              playing;
-       Message              stopping;
-       Message              stopped;
-       Message              playingInaudible;
-};
-
-/* -------------------------------------------------------------------------- */
-
-/* midimap
-The actual MidiMap struct with data. */
-
-extern MidiMap midimap;
-
-/* midimapsPath
-Path of midimap files, different between OSes. */
-
-extern std::string midimapsPath;
-
-/* maps
-Maps are the available .giadamap files. Each element of the std::vector 
-represents a .giadamap filename. */
-
-extern std::vector<std::string> maps;
-
-/* -------------------------------------------------------------------------- */
-
-/* init
-Parses the midi maps folders and find the available maps. */
-
-void init();
-
-/* setDefault
-Sets default values in case no maps are available/chosen. */
-
-void setDefault();
-
-/* isDefined
-Checks whether a specific message has been defined within midi map file. */
-
-bool isDefined(const Message& msg);
-
-/* read
-Reads a midi map from file 'file'. */
-
-int read(const std::string& file);
-} // namespace midimap
-} // namespace m
-} // namespace giada
-
-#endif
diff --git a/src/core/midiMapper.cpp b/src/core/midiMapper.cpp
new file mode 100644 (file)
index 0000000..a11bd38
--- /dev/null
@@ -0,0 +1,278 @@
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+#include "core/midiMapper.h"
+#include "core/const.h"
+#include "core/kernelMidi.h"
+#include "core/midiEvent.h"
+#include "utils/fs.h"
+#include "utils/log.h"
+#include "utils/string.h"
+#include <cstring>
+#include <filesystem>
+#include <fstream>
+#include <string>
+#include <vector>
+#ifdef WITH_TESTS
+#include "tests/mocks/kernelMidiMock.h"
+#endif
+
+namespace nl = nlohmann;
+
+namespace giada::m
+{
+bool MidiMap::isValid() const
+{
+       return !(brand.empty() || device.empty());
+}
+
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+
+template <typename KernelMidiI>
+MidiMapper<KernelMidiI>::MidiMapper(KernelMidiI& k)
+: m_kernelMidi(k)
+{
+}
+
+/* -------------------------------------------------------------------------- */
+
+template <typename KernelMidiI>
+const std::vector<std::string>& MidiMapper<KernelMidiI>::getMapFilesFound() const
+{
+       return m_mapFiles;
+}
+
+/* -------------------------------------------------------------------------- */
+
+template <typename KernelMidiI>
+void MidiMapper<KernelMidiI>::init()
+{
+       m_mapsPath = u::fs::getHomePath() + G_SLASH + "midimaps" + G_SLASH;
+
+       /* scan dir of midi maps and load the filenames into m_mapFiles vector. */
+
+       u::log::print("[MidiMapper::init] scanning midimaps directory '%s'...\n",
+           m_mapsPath);
+
+       if (!std::filesystem::exists(m_mapsPath))
+       {
+               u::log::print("[MidiMapper::init] unable to scan midimaps directory!\n");
+               return;
+       }
+
+       for (const auto& d : std::filesystem::directory_iterator(m_mapsPath))
+       {
+               // TODO - check if is a valid midiMap file (verify headers)
+               if (!d.is_regular_file())
+                       continue;
+               u::log::print("[MidiMapper::init] found midiMap '%s'\n", d.path().filename().string());
+               m_mapFiles.push_back(d.path().filename().string());
+       }
+
+       u::log::print("[MidiMapper::init] total midimaps found: %d\n", m_mapFiles.size());
+}
+
+/* -------------------------------------------------------------------------- */
+
+template <typename KernelMidiI>
+int MidiMapper<KernelMidiI>::read(const std::string& file)
+{
+       if (file.empty())
+       {
+               u::log::print("[MidiMapper::read] midiMap not specified, nothing to do\n");
+               return MIDIMAP_NOT_SPECIFIED;
+       }
+
+       u::log::print("[MidiMapper::read] reading midiMap file '%s'\n", file);
+
+       std::ifstream ifs(m_mapsPath + file);
+       if (!ifs.good())
+               return MIDIMAP_UNREADABLE;
+
+       nl::json j = nl::json::parse(ifs);
+
+       currentMap.brand  = j[MIDIMAP_KEY_BRAND];
+       currentMap.device = j[MIDIMAP_KEY_DEVICE];
+
+       if (!readInitCommands(currentMap, j))
+               return MIDIMAP_UNREADABLE;
+       if (readCommand(j, currentMap.muteOn, MIDIMAP_KEY_MUTE_ON))
+               parse(currentMap.muteOn);
+       if (readCommand(j, currentMap.muteOff, MIDIMAP_KEY_MUTE_OFF))
+               parse(currentMap.muteOff);
+       if (readCommand(j, currentMap.soloOn, MIDIMAP_KEY_SOLO_ON))
+               parse(currentMap.soloOn);
+       if (readCommand(j, currentMap.soloOff, MIDIMAP_KEY_SOLO_OFF))
+               parse(currentMap.soloOff);
+       if (readCommand(j, currentMap.waiting, MIDIMAP_KEY_WAITING))
+               parse(currentMap.waiting);
+       if (readCommand(j, currentMap.playing, MIDIMAP_KEY_PLAYING))
+               parse(currentMap.playing);
+       if (readCommand(j, currentMap.stopping, MIDIMAP_KEY_STOPPING))
+               parse(currentMap.stopping);
+       if (readCommand(j, currentMap.stopped, MIDIMAP_KEY_STOPPED))
+               parse(currentMap.stopped);
+       if (readCommand(j, currentMap.playingInaudible, MIDIMAP_KEY_PLAYING_INAUDIBLE))
+               parse(currentMap.playingInaudible);
+
+       return MIDIMAP_READ_OK;
+}
+
+/* -------------------------------------------------------------------------- */
+
+template <typename KernelMidiI>
+bool MidiMapper<KernelMidiI>::isMessageDefined(const MidiMap::Message& m) const
+{
+       return m.offset != -1;
+}
+
+/* -------------------------------------------------------------------------- */
+
+template <typename KernelMidiI>
+void MidiMapper<KernelMidiI>::sendInitMessages(const MidiMap& midiMap)
+{
+       if (!midiMap.isValid())
+               return;
+
+       for (const MidiMap::Message& m : midiMap.initCommands)
+       {
+               if (m.value == 0x0 || m.channel == -1)
+                       continue;
+               MidiEvent e(m.value);
+               e.setChannel(m.channel);
+               m_kernelMidi.send(e.getRaw());
+       }
+}
+
+/* -------------------------------------------------------------------------- */
+
+template <typename KernelMidiI>
+void MidiMapper<KernelMidiI>::sendMidiLightning(uint32_t learnt, const MidiMap::Message& m)
+{
+       // Skip lightning message if not defined in midi map
+
+       if (!isMessageDefined(m))
+       {
+               u::log::print("[MidiMapper::sendMidiLightning] message skipped (not defined in midiMap)");
+               return;
+       }
+
+       u::log::print("[MidiMapper::sendMidiLightning] learnt=0x%X, chan=%d, msg=0x%X, offset=%d\n",
+           learnt, m.channel, m.value, m.offset);
+
+       /* Isolate 'channel' from learnt message and offset it as requested by 'nn' in 
+       the midiMap configuration file. */
+
+       uint32_t out = ((learnt & 0x00FF0000) >> 16) << m.offset;
+
+       /* Merge the previously prepared channel into final message, and finally send 
+       it. */
+
+       out |= m.value | (m.channel << 24);
+       m_kernelMidi.send(out);
+}
+
+/* -------------------------------------------------------------------------- */
+
+template <typename KernelMidiI>
+void MidiMapper<KernelMidiI>::parse(MidiMap::Message& message) const
+{
+       /* Remove '0x' part from the original string. */
+
+       std::string input = message.valueStr;
+
+       std::size_t f = input.find("0x"); // check if "0x" is there
+       if (f != std::string::npos)
+               input = message.valueStr.replace(f, 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. */
+
+       std::string output;
+       for (unsigned i = 0, p = 24; i < input.length(); i++, p -= 4)
+       {
+               if (input[i] == 'n')
+               {
+                       output += '0';
+                       if (message.offset == -1) // do it once
+                               message.offset = p;
+               }
+               else
+                       output += input[i];
+       }
+
+       /* From string to uint32_t */
+
+       message.value = strtoul(output.c_str(), nullptr, 16);
+
+       u::log::print("[MidiMapper::parse] parsed chan=%d valueStr=%s value=%#x, offset=%d\n",
+           message.channel, message.valueStr, message.value, message.offset);
+}
+
+/* -------------------------------------------------------------------------- */
+
+template <typename KernelMidiI>
+bool MidiMapper<KernelMidiI>::readCommand(const nl::json& j, MidiMap::Message& m, const std::string& key) const
+{
+       if (j.find(key) == j.end())
+               return false;
+
+       const nl::json& jc = j[key];
+
+       m.channel  = jc[MIDIMAP_KEY_CHANNEL];
+       m.valueStr = jc[MIDIMAP_KEY_MESSAGE];
+
+       return true;
+}
+/* -------------------------------------------------------------------------- */
+
+template <typename KernelMidiI>
+bool MidiMapper<KernelMidiI>::readInitCommands(MidiMap& midiMap, const nl::json& j)
+{
+       if (j.find(MIDIMAP_KEY_INIT_COMMANDS) == j.end())
+               return false;
+
+       for (const auto& jc : j[MIDIMAP_KEY_INIT_COMMANDS])
+       {
+               MidiMap::Message m;
+               m.channel  = jc[MIDIMAP_KEY_CHANNEL];
+               m.valueStr = jc[MIDIMAP_KEY_MESSAGE];
+               m.value    = strtoul(m.valueStr.c_str(), nullptr, 16);
+
+               midiMap.initCommands.push_back(m);
+       }
+
+       return true;
+}
+
+template class MidiMapper<KernelMidi>;
+#ifdef WITH_TESTS
+template class MidiMapper<KernelMidiMock>;
+#endif
+} // namespace giada::m
\ No newline at end of file
diff --git a/src/core/midiMapper.h b/src/core/midiMapper.h
new file mode 100644 (file)
index 0000000..694ffe5
--- /dev/null
@@ -0,0 +1,148 @@
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+#ifndef G_MIDIMAPPER_H
+#define G_MIDIMAPPER_H
+
+#include "deps/json/single_include/nlohmann/json.hpp"
+#include <string>
+#include <vector>
+
+namespace giada::m
+{
+class KernelMidi;
+#ifdef WITH_TESTS
+class KernelMidiMock;
+#endif
+} // namespace giada::m
+
+namespace giada::m
+{
+struct MidiMap
+{
+       struct Message
+       {
+               int         channel  = 0;
+               std::string valueStr = "";
+               int         offset   = -1;
+               uint32_t    value    = 0;
+       };
+
+       /* isValid
+       A valid MidiMap must have the brand and the device defined. */
+
+       bool isValid() const;
+
+       std::string          brand;
+       std::string          device;
+       std::vector<Message> initCommands;
+       Message              muteOn;
+       Message              muteOff;
+       Message              soloOn;
+       Message              soloOff;
+       Message              waiting;
+       Message              playing;
+       Message              stopping;
+       Message              stopped;
+       Message              playingInaudible;
+};
+
+template <typename KernelMidiI>
+class MidiMapper final
+{
+public:
+       MidiMapper(KernelMidiI&);
+
+       /* getMapFilesFound
+       Returns a reference to the list of midimaps found. */
+
+       const std::vector<std::string>& getMapFilesFound() const;
+
+       /* init
+       Parses the midimap folders and find the available midimaps. */
+
+       void init();
+
+       /* read
+       Reads a midimap from file 'file' and sets it as the current one. */
+
+       int read(const std::string& file);
+
+       /* sendInitMessages
+       Sends initialization messages from the midimap to the connected MIDI devices. */
+
+       void sendInitMessages(const MidiMap& midiMap);
+
+       /* sendMidiLightning
+       Sends a MIDI lightning message defined by 'msg'. */
+
+       void sendMidiLightning(uint32_t learnt, const MidiMap::Message& msg);
+
+       /* currentMap
+       The current MidiMap selected and loaded. It might be invalid if no midimaps
+       have been found. */
+
+       MidiMap currentMap;
+
+private:
+       KernelMidiI& m_kernelMidi;
+
+       /* isMessageDefined
+       Checks whether a specific message has been defined within a midimap file. */
+
+       bool isMessageDefined(const MidiMap::Message& m) const;
+
+       /* parse
+       Takes a string message with 'nn' in it and turns it into a real MIDI value.
+       TODO - don't edit message in place! */
+
+       void parse(MidiMap::Message& message) const;
+
+       /* TODO - don't edit midiMap in place! */
+       bool readInitCommands(MidiMap& midiMap, const nlohmann::json& j);
+
+       /* TODO - don't edit message in place! */
+       bool readCommand(const nlohmann::json& j, MidiMap::Message& m, const std::string& key) const;
+
+       /* m_mapsPath
+       Path to folder containing midimap files, different between OSes. */
+
+       std::string m_mapsPath;
+
+       /* m_mapFiles
+       The available .giadamap files. Each element of the vector represents 
+       a .giadamap file found in the midimap folder. */
+
+       std::vector<std::string> m_mapFiles;
+};
+
+extern template class MidiMapper<KernelMidi>;
+#ifdef WITH_TESTS
+extern template class MidiMapper<KernelMidiMock>;
+#endif
+} // namespace giada::m
+
+#endif
index 6e292eef22340431ae61ab794721dc5370ab7603..8c5f81db205ddc33957f5af1edefa8028dd49c77 100644 (file)
  * -------------------------------------------------------------------------- */
 
 #include "core/mixer.h"
-#include "core/audioBuffer.h"
 #include "core/const.h"
 #include "core/model/model.h"
-#include "core/sequencer.h"
 #include "utils/log.h"
 #include "utils/math.h"
 
-namespace giada::m::mixer
+namespace giada::m
 {
 namespace
 {
-/* recBuffer_
-Working buffer for audio recording. */
+/* CH_LEFT, CH_RIGHT
+Channels identifiers. */
 
-AudioBuffer recBuffer_;
-
-/* inBuffer_
-Working buffer for input channel. Used for the in->out bridge. */
-
-AudioBuffer inBuffer_;
+constexpr int CH_LEFT  = 0;
+constexpr int CH_RIGHT = 1;
+} // namespace
 
-/* inputTracker_
-Frame position while recording. */
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
 
-Frame inputTracker_ = 0;
+Mixer::Mixer(model::Model& m)
+: onSignalTresholdReached(nullptr)
+, onEndOfRecording(nullptr)
+, m_model(m)
+, m_signalCbFired(false)
+, m_endOfRecCbFired(false)
+{
+}
 
-/* signalCb_
-Callback triggered when the input signal level reaches a threshold. */
+/* -------------------------------------------------------------------------- */
 
-std::function<void()> signalCb_ = nullptr;
+void Mixer::reset(Frame maxFramesInLoop, Frame framesInBuffer)
+{
+       /* Allocate working buffers. rec buffer has variable size: it depends on how
+       many frames there are in the current loop. */
 
-/* endOfRecCb_
-Callback triggered when the end of the internal recording buffer has been 
-reached.*/
+       m_model.get().mixer.getRecBuffer().alloc(maxFramesInLoop, G_MAX_IO_CHANS);
+       m_model.get().mixer.getInBuffer().alloc(framesInBuffer, G_MAX_IO_CHANS);
 
-std::function<void()> endOfRecCb_ = nullptr;
+       u::log::print("[mixer::reset] buffers ready - maxFramesInLoop=%d, framesInBuffer=%d\n",
+           maxFramesInLoop, framesInBuffer);
+}
 
-/* signalCbFired_
-Boolean guard to determine whether the signal callback has been fired or not.
-Checking if signalCb_ != null (i.e. a callback is still present, so not fired
-yet) is not enough, as the actual firing takes place on a different thread in
-a slightly different moment (see fireSignalCb_() below). */
+/* -------------------------------------------------------------------------- */
 
-bool signalCbFired_ = false;
+bool Mixer::isActive() const { return m_model.get().mixer.a_isActive(); }
 
 /* -------------------------------------------------------------------------- */
 
-/* fireSignalCb_
-Invokes the signal callback. This is done by pumping a FUNCTION event to the
-event dispatcher, rather than invoking the callback directly. This is done on
-purpose: the callback might (and surely will) contain blocking stuff from 
-model:: that the realtime thread cannot perform directly. */
-
-void fireSignalCb_()
+void Mixer::enable()
 {
-       eventDispatcher::pumpUIevent({eventDispatcher::EventType::FUNCTION, 0, 0, []() {
-                                             signalCb_();
-                                             signalCb_ = nullptr;
-                                     }});
+       m_model.get().mixer.a_setActive(true);
+       u::log::print("[mixer::enable] enabled\n");
 }
 
-/* -------------------------------------------------------------------------- */
-
-/* fireEndOfRecCb_
-Same rationale of fireSignalCb_, for the endOfRecCb_ callback. */
-
-void fireEndOfRecCb_()
+void Mixer::disable()
 {
-       eventDispatcher::pumpUIevent({eventDispatcher::EventType::FUNCTION, 0, 0, []() {
-                                             endOfRecCb_();
-                                             endOfRecCb_ = nullptr;
-                                     }});
+       m_model.get().mixer.a_setActive(false);
+       while (m_model.isLocked())
+               ;
+       u::log::print("[mixer::disable] disabled\n");
 }
 
 /* -------------------------------------------------------------------------- */
 
-/* lineInRec
-Records from line in. 'maxFrames' determines how many frames to record before
-the internal tracker loops over. The value changes whether you are recording
-in RIGID or FREE mode. */
-
-void lineInRec_(const AudioBuffer& inBuf, Frame maxFrames, float inVol)
+void Mixer::allocRecBuffer(Frame frames)
 {
-       assert(maxFrames <= recBuffer_.countFrames());
+       m_model.get().mixer.getRecBuffer().alloc(frames, G_MAX_IO_CHANS);
+}
 
-       if (inputTracker_ >= maxFrames && endOfRecCb_ != nullptr)
-       {
-               fireEndOfRecCb_();
-               return;
-       }
+void Mixer::clearRecBuffer()
+{
+       m_model.get().mixer.getRecBuffer().clear();
+}
 
-       const Frame framesToCopy = -1; // copy everything
-       const Frame srcOffset    = 0;
-       const Frame destOffset   = inputTracker_ % maxFrames; // loop over at maxFrames
+const mcl::AudioBuffer& Mixer::getRecBuffer()
+{
+       return m_model.get().mixer.getRecBuffer();
+}
 
-       recBuffer_.sum(inBuf, framesToCopy, srcOffset, destOffset, inVol);
+/* -------------------------------------------------------------------------- */
 
-       inputTracker_ += inBuf.countFrames();
+void Mixer::advanceChannels(const Sequencer::EventBuffer& events, const model::Layout& rtLayout)
+{
+       for (const Channel& c : rtLayout.channels)
+               if (!c.isInternal())
+                       c.advance(events);
 }
 
 /* -------------------------------------------------------------------------- */
 
-/* processLineIn
-Computes line in peaks and prepares the internal working buffer for input
-recording. */
-
-void processLineIn_(const model::Mixer& mixer, const AudioBuffer& inBuf,
-    float inVol, float recTriggerLevel)
+void Mixer::render(mcl::AudioBuffer& out, const mcl::AudioBuffer& in, const model::Layout& layout_RT) const
 {
-       float peak = inBuf.getPeak();
+       const model::Mixer&     mixer     = layout_RT.mixer;
+       const model::Sequencer& sequencer = layout_RT.sequencer;
+       const model::Recorder&  recorder  = layout_RT.recorder;
 
-       if (signalCb_ != nullptr && u::math::linearToDB(peak) > recTriggerLevel && !signalCbFired_)
-       {
-               G_DEBUG("Signal > threshold!");
-               fireSignalCb_();
-               signalCbFired_ = true;
-       }
+       const Channel& masterOutCh = layout_RT.getChannel(Mixer::MASTER_OUT_CHANNEL_ID);
+       const Channel& masterInCh  = layout_RT.getChannel(Mixer::MASTER_IN_CHANNEL_ID);
+       const Channel& previewCh   = layout_RT.getChannel(Mixer::PREVIEW_CHANNEL_ID);
 
-       mixer.state->peakIn.store(peak);
+       const bool  hasInput        = in.isAllocd();
+       const bool  inToOut         = mixer.inToOut;
+       const bool  isSeqActive     = sequencer.isActive();
+       const bool  shouldLineInRec = isSeqActive && recorder.a_isRecordingInput() && hasInput;
+       const float recTriggerLevel = mixer.recTriggerLevel;
+       const Frame maxFramesToRec  = mixer.maxFramesToRec;
+       const bool  allowsOverdub   = mixer.allowsOverdub;
+       const bool  limitOutput     = mixer.limitOutput;
 
-       /* Prepare the working buffer for input stream, which will be processed 
-       later on by the Master Input Channel with plug-ins. */
+       mixer.getInBuffer().clear();
 
-       assert(inBuf.countChannels() <= inBuffer_.countChannels());
+       /* Reset peak computation. */
 
-       inBuffer_.set(inBuf, inVol);
-}
+       mixer.a_setPeakOut({0.0f, 0.0f});
+       mixer.a_setPeakIn({0.0f, 0.0f});
 
-/* -------------------------------------------------------------------------- */
+       if (hasInput)
+       {
+               processLineIn(mixer, in, masterInCh.volume, recTriggerLevel, isSeqActive);
+               renderMasterIn(masterInCh, mixer.getInBuffer());
+       }
 
-void processChannels_(const model::Layout& layout, AudioBuffer& out, AudioBuffer& in)
-{
-       for (const channel::Data& c : layout.channels)
-               if (!c.isInternal())
-                       channel::render(c, &out, &in, isChannelAudible(c));
-}
+       if (shouldLineInRec)
+       {
+               const Frame newTrackerPos = lineInRec(in, mixer.getRecBuffer(),
+                   mixer.a_getInputTracker(), maxFramesToRec, masterInCh.volume,
+                   allowsOverdub);
+               mixer.a_setInputTracker(newTrackerPos);
+       }
 
-/* -------------------------------------------------------------------------- */
+       /* Channel processing. Don't do it if layout is locked: another thread is 
+       changing data (e.g. Plugins or Waves). */
 
-void processSequencer_(const model::Layout& layout, AudioBuffer& out, const AudioBuffer& in)
-{
-       /* Advance sequencer first, then render it (rendering is just about
-       generating metronome audio). This way the metronome is aligned with 
-       everything else. */
+       if (!layout_RT.locked)
+               renderChannels(layout_RT.channels, out, mixer.getInBuffer());
 
-       const sequencer::EventBuffer& events = sequencer::advance(in.countFrames());
-       sequencer::render(out);
+       /* Render remaining internal channels. */
 
-       /* No channel processing if layout is locked: another thread is changing
-    data (e.g. Plugins or Waves). */
+       renderMasterOut(masterOutCh, out);
+       renderPreview(previewCh, out);
 
-       if (layout.locked)
-               return;
+       /* Post processing. */
 
-       for (const channel::Data& c : layout.channels)
-               if (!c.isInternal())
-                       channel::advance(c, events);
+       finalizeOutput(mixer, out, inToOut, limitOutput, masterOutCh.volume);
 }
 
 /* -------------------------------------------------------------------------- */
 
-void renderMasterIn_(const model::Layout& layout, AudioBuffer& in)
+void Mixer::startInputRec(Frame from)
 {
-       channel::render(layout.getChannel(mixer::MASTER_IN_CHANNEL_ID), nullptr, &in, true);
+       m_model.get().mixer.a_setInputTracker(from);
 }
 
-void renderMasterOut_(const model::Layout& layout, AudioBuffer& out)
+Frame Mixer::stopInputRec()
 {
-       channel::render(layout.getChannel(mixer::MASTER_OUT_CHANNEL_ID), &out, nullptr, true);
-}
-
-void renderPreview_(const model::Layout& layout, AudioBuffer& out)
-{
-       channel::render(layout.getChannel(mixer::PREVIEW_CHANNEL_ID), &out, nullptr, true);
+       const Frame ret = m_model.get().mixer.a_getInputTracker();
+       m_model.get().mixer.a_setInputTracker(0);
+       m_signalCbFired   = false;
+       m_endOfRecCbFired = false;
+       return ret;
 }
 
 /* -------------------------------------------------------------------------- */
 
-/* limit_
-Applies a very dumb hard limiter. */
-
-void limit_(AudioBuffer& outBuf)
+bool Mixer::isChannelAudible(const Channel& c) const
 {
-       for (int i = 0; i < outBuf.countFrames(); i++)
-               for (int j = 0; j < outBuf.countChannels(); j++)
-                       outBuf[i][j] = std::max(-1.0f, std::min(outBuf[i][j], 1.0f));
+       if (c.isInternal())
+               return true;
+       if (c.isMuted())
+               return false;
+       const bool hasSolos = m_model.get().mixer.hasSolos;
+       return !hasSolos || (hasSolos && c.isSoloed());
 }
 
 /* -------------------------------------------------------------------------- */
 
-/* finalizeOutput
-Last touches after the output has been rendered: apply inToOut if any, apply
-output volume, compute peak. */
+Peak Mixer::getPeakOut() const { return m_model.get().mixer.a_getPeakOut(); }
+Peak Mixer::getPeakIn() const { return m_model.get().mixer.a_getPeakIn(); }
 
-void finalizeOutput_(const model::Mixer& mixer, AudioBuffer& outBuf,
-    const RenderInfo& info)
-{
-       if (info.inToOut)
-               outBuf.sum(inBuffer_, info.outVol);
-       else
-               outBuf.applyGain(info.outVol);
-
-       if (info.limitOutput)
-               limit_(outBuf);
-
-       mixer.state->peakOut.store(outBuf.getPeak());
-}
-} // namespace
-
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
 /* -------------------------------------------------------------------------- */
 
-void init(Frame maxFramesInLoop, Frame framesInBuffer)
+Mixer::RecordInfo Mixer::getRecordInfo() const
 {
-       /* Allocate working buffers. recBuffer_ has variable size: it depends on how
-       many frames there are in the current loop. */
-
-       recBuffer_.alloc(maxFramesInLoop, G_MAX_IO_CHANS);
-       inBuffer_.alloc(framesInBuffer, G_MAX_IO_CHANS);
-
-       u::log::print("[mixer::init] buffers ready - maxFramesInLoop=%d, framesInBuffer=%d\n",
-           maxFramesInLoop, framesInBuffer);
+       return {
+           m_model.get().mixer.a_getInputTracker(),
+           m_model.get().mixer.getRecBuffer().countFrames()};
 }
 
 /* -------------------------------------------------------------------------- */
 
-void enable()
+bool Mixer::thresholdReached(Peak p, float threshold) const
 {
-       model::get().mixer.state->active.store(true);
-       u::log::print("[mixer::enable] enabled\n");
-}
-
-void disable()
-{
-       model::get().mixer.state->active.store(false);
-       while (model::isLocked())
-               ;
-       u::log::print("[mixer::disable] disabled\n");
+       return u::math::linearToDB(p.left) > threshold ||
+              u::math::linearToDB(p.right) > threshold;
 }
 
 /* -------------------------------------------------------------------------- */
 
-void allocRecBuffer(Frame frames)
-{
-       recBuffer_.alloc(frames, G_MAX_IO_CHANS);
-}
-
-void clearRecBuffer()
+Peak Mixer::makePeak(const mcl::AudioBuffer& b) const
 {
-       recBuffer_.clear();
-}
-
-const AudioBuffer& getRecBuffer()
-{
-       return recBuffer_;
+       if (!b.isAllocd())
+               return {0.0f, 0.0f};
+       return {b.getPeak(CH_LEFT), b.getPeak(b.countChannels() == 1 ? CH_LEFT : CH_RIGHT)};
 }
 
 /* -------------------------------------------------------------------------- */
 
-int render(AudioBuffer& out, const AudioBuffer& in, const RenderInfo& info)
+Frame Mixer::lineInRec(const mcl::AudioBuffer& inBuf, mcl::AudioBuffer& recBuf, Frame inputTracker,
+    Frame maxFrames, float inVol, bool allowsOverdub) const
 {
-       const model::Lock   rtLock = model::get_RT();
-       const model::Mixer& mixer  = rtLock.get().mixer;
+       assert(maxFrames > 0 && maxFrames <= recBuf.countFrames());
+       assert(onEndOfRecording != nullptr);
 
-       inBuffer_.clear();
+       if (inputTracker >= maxFrames && !allowsOverdub && !m_endOfRecCbFired)
+       {
+               onEndOfRecording();
+               m_endOfRecCbFired = true;
+               return 0;
+       }
 
-       /* Reset peak computation. */
+       const Frame framesToCopy = -1; // copy everything
+       const Frame srcOffset    = 0;
+       const Frame destOffset   = inputTracker % maxFrames; // loop over at maxFrames
 
-       mixer.state->peakOut.store(0.0);
-       mixer.state->peakIn.store(0.0);
+       recBuf.sum(inBuf, framesToCopy, srcOffset, destOffset, inVol);
 
-       /* Process line IN if input has been enabled in KernelAudio. */
+       return inputTracker + inBuf.countFrames();
+}
 
-       if (info.hasInput)
-       {
-               processLineIn_(mixer, in, info.inVol, info.recTriggerLevel);
-               renderMasterIn_(rtLock.get(), inBuffer_);
-       }
+/* -------------------------------------------------------------------------- */
 
-       /* Record input audio and advance the sequencer only if clock is active:
-       can't record stuff with the sequencer off. */
+void Mixer::processLineIn(const model::Mixer& mixer, const mcl::AudioBuffer& inBuf,
+    float inVol, float recTriggerLevel, bool isSeqActive) const
+{
+       const Peak peak = makePeak(inBuf);
 
-       if (info.isClockActive)
+       if (thresholdReached(peak, recTriggerLevel) && !m_signalCbFired && isSeqActive)
        {
-               if (info.canLineInRec)
-                       lineInRec_(in, info.maxFramesToRec, info.inVol);
-               if (info.isClockRunning)
-                       processSequencer_(rtLock.get(), out, inBuffer_);
+               m_signalCbFired = true;
+               onSignalTresholdReached();
+               G_DEBUG("Signal > threshold!");
        }
 
-       /* Channel processing. Don't do it if layout is locked: another thread is 
-       changing data (e.g. Plugins or Waves). */
-
-       if (!rtLock.get().locked)
-               processChannels_(rtLock.get(), out, inBuffer_);
+       mixer.a_setPeakIn(peak);
 
-       /* Render remaining internal channels. */
+       /* Prepare the working buffer for input stream, which will be processed 
+       later on by the Master Input Channel with plug-ins. */
 
-       renderMasterOut_(rtLock.get(), out);
-       renderPreview_(rtLock.get(), out);
+       assert(inBuf.countChannels() <= mixer.getInBuffer().countChannels());
 
-       /* Post processing. */
+       mixer.getInBuffer().set(inBuf, inVol);
+}
 
-       finalizeOutput_(mixer, out, info);
+/* -------------------------------------------------------------------------- */
 
-       return 0;
+void Mixer::renderChannels(const std::vector<Channel>& channels, mcl::AudioBuffer& out, mcl::AudioBuffer& in) const
+{
+       for (const Channel& c : channels)
+               if (!c.isInternal())
+                       c.render(&out, &in, isChannelAudible(c));
 }
 
 /* -------------------------------------------------------------------------- */
 
-void startInputRec(Frame from)
+void Mixer::renderMasterIn(const Channel& ch, mcl::AudioBuffer& in) const
 {
-       inputTracker_  = from;
-       signalCbFired_ = false;
+       ch.render(nullptr, &in, true);
 }
 
-Frame stopInputRec()
+void Mixer::renderMasterOut(const Channel& ch, mcl::AudioBuffer& out) const
 {
-       Frame ret      = inputTracker_;
-       inputTracker_  = 0;
-       signalCbFired_ = false;
-       return ret;
+       ch.render(&out, nullptr, true);
 }
 
-/* -------------------------------------------------------------------------- */
-
-void setSignalCallback(std::function<void()> f) { signalCb_ = f; }
-void setEndOfRecCallback(std::function<void()> f) { endOfRecCb_ = f; }
+void Mixer::renderPreview(const Channel& ch, mcl::AudioBuffer& out) const
+{
+       ch.render(&out, nullptr, true);
+}
 
 /* -------------------------------------------------------------------------- */
 
-bool isChannelAudible(const channel::Data& c)
+void Mixer::limit(mcl::AudioBuffer& outBuf) const
 {
-       if (c.isInternal())
-               return true;
-       if (c.mute)
-               return false;
-       bool hasSolos = model::get().mixer.hasSolos;
-       return !hasSolos || (hasSolos && c.solo);
+       for (int i = 0; i < outBuf.countFrames(); i++)
+               for (int j = 0; j < outBuf.countChannels(); j++)
+                       outBuf[i][j] = std::max(-1.0f, std::min(outBuf[i][j], 1.0f));
 }
 
 /* -------------------------------------------------------------------------- */
 
-float getPeakOut() { return m::model::get().mixer.state->peakOut.load(); }
-float getPeakIn() { return m::model::get().mixer.state->peakIn.load(); }
+void Mixer::finalizeOutput(const model::Mixer& mixer, mcl::AudioBuffer& buf,
+    bool inToOut, bool shouldLimit, float vol) const
+{
+       if (inToOut)
+               buf.sum(mixer.getInBuffer(), vol);
+       else
+               buf.applyGain(vol);
 
-/* -------------------------------------------------------------------------- */
+       if (shouldLimit)
+               limit(buf);
 
-RecordInfo getRecordInfo()
-{
-       return {inputTracker_, recBuffer_.countFrames()};
+       mixer.a_setPeakOut({buf.getPeak(CH_LEFT), buf.getPeak(CH_RIGHT)});
 }
-} // namespace giada::m::mixer
+} // namespace giada::m
index b143274b7a1d752eb5783c13df485cdcd95426da..204845cca3996abc939cb3d4bd66442f67ba0526 100644 (file)
 
 #include "core/midiEvent.h"
 #include "core/queue.h"
-#include "core/recorder.h"
 #include "core/ringBuffer.h"
+#include "core/sequencer.h"
 #include "core/types.h"
-#include "deps/rtaudio/RtAudio.h"
+#include "core/weakAtomic.h"
+#include "deps/mcl-audio-buffer/src/audioBuffer.hpp"
+#include "src/core/actions/actions.h"
 #include <functional>
 
-namespace giada::m
+namespace mcl
 {
-struct Action;
 class AudioBuffer;
-} // namespace giada::m
-namespace giada::m::channel
-{
-struct Data;
 }
-namespace giada::m::mixer
-{
-constexpr int MASTER_OUT_CHANNEL_ID = 1;
-constexpr int MASTER_IN_CHANNEL_ID  = 2;
-constexpr int PREVIEW_CHANNEL_ID    = 3;
 
-/* RenderInfo
-Struct of parameters passed to Mixer for rendering. */
+namespace giada::m::model
+{
+class Mixer;
+struct Layout;
+} // namespace giada::m::model
 
-struct RenderInfo
+namespace giada::m
 {
-       bool  isAudioReady;
-       bool  hasInput;
-       bool  isClockActive;
-       bool  isClockRunning;
-       bool  canLineInRec;
-       bool  limitOutput;
-       bool  inToOut;
-       Frame maxFramesToRec;
-       float outVol;
-       float inVol;
-       float recTriggerLevel;
-};
+struct Action;
+class Channel;
+class MixerHandler;
+class Mixer
+{
+public:
+       friend MixerHandler;
 
-/* RecordInfo
-Information regarding the input recording progress. */
+       static constexpr int MASTER_OUT_CHANNEL_ID = 1;
+       static constexpr int MASTER_IN_CHANNEL_ID  = 2;
+       static constexpr int PREVIEW_CHANNEL_ID    = 3;
 
-struct RecordInfo
-{
-       Frame position;
-       Frame maxLength;
-};
+       /* RecordInfo
+       Information regarding the input recording progress. */
+
+       struct RecordInfo
+       {
+               Frame position;
+               Frame maxLength;
+       };
+
+       Mixer(model::Model&);
+
+       /* isActive
+       Mixer might be inactive (not initialized or suspended). */
+
+       bool isActive() const;
+
+       /* isChannelAudible
+       True if the channel 'c' is currently audible: not muted or not included in a 
+       solo session. */
+
+       bool isChannelAudible(const Channel& c) const;
+
+       Peak getPeakOut() const;
+       Peak getPeakIn() const;
 
-void init(Frame framesInLoop, Frame framesInBuffer);
+       /* getRecordInfo
+       Returns information on the ongoing input recording. */
 
-/* enable, disable
-Toggles master callback processing. Useful to suspend the rendering. */
+       RecordInfo getRecordInfo() const;
 
-void enable();
-void disable();
+       /* render
+       Core rendering function. */
 
-/* allocRecBuffer
-Allocates new memory for the virtual input channel. */
+       void render(mcl::AudioBuffer& out, const mcl::AudioBuffer& in, const model::Layout&) const;
 
-void allocRecBuffer(Frame frames);
+       /* reset
+       Brings everything back to the initial state. */
 
-/* clearRecBuffer
-Clears internal virtual channel. */
+       void reset(Frame framesInLoop, Frame framesInBuffer);
 
-void clearRecBuffer();
+       /* enable, disable
+       Toggles master callback processing. Useful to suspend the rendering. */
 
-/* getRecBuffer
-Returns a read-only reference to the internal virtual channel. Use this to
-merge data into channel after an input recording session. */
+       void enable();
+       void disable();
 
-const AudioBuffer& getRecBuffer();
+       /* allocRecBuffer
+       Allocates new memory for the virtual input channel. */
 
-/* render
-Core rendering function. */
+       void allocRecBuffer(Frame frames);
 
-int render(AudioBuffer& out, const AudioBuffer& in, const RenderInfo& info);
+       /* clearRecBuffer
+       Clears internal virtual channel. */
 
-/* startInputRec, stopInputRec
-Starts/stops input recording on frame 'from'. The latter returns the number of
-recorded frames. */
+       void clearRecBuffer();
 
-void  startInputRec(Frame from);
-Frame stopInputRec();
+       /* getRecBuffer
+       Returns a read-only reference to the internal virtual channel. Use this to
+       merge data into channel after an input recording session. */
 
-/* setSignalCallback
-Registers the function to be called when the audio signal reaches a certain
-threshold (record-on-signal mode). */
+       const mcl::AudioBuffer& getRecBuffer();
 
-void setSignalCallback(std::function<void()> f);
+       /* advanceChannels
+       Processes Channels' static events (e.g. pre-recorded actions or sequencer 
+       events) in the current audio block. Called by the main audio thread when the 
+       sequencer is running. */
 
-/* setEndOfRecCallback
-Registers the function to be called when the end of the internal recording 
-buffer has been reached. */
+       void advanceChannels(const Sequencer::EventBuffer& events, const model::Layout&);
 
-void setEndOfRecCallback(std::function<void()> f);
+       /* onSignalTresholdReached
+       Callback fired when audio has reached a certain threshold (record-on-signal 
+       mode). */
 
-/* isChannelAudible
-True if the channel 'c' is currently audible: not muted or not included in a 
-solo session. */
+       std::function<void()> onSignalTresholdReached;
 
-bool isChannelAudible(const channel::Data& c);
+       /* onEndOfRecording
+       Callback fired when the audio recording session has ended. */
 
-float getPeakOut();
-float getPeakIn();
+       std::function<void()> onEndOfRecording;
 
-RecordInfo getRecordInfo();
-} // namespace giada::m::mixer
+private:
+       /* thresholdReached
+       Returns true if left or right channel's peak has reached a certain 
+       threshold. */
+
+       bool thresholdReached(Peak p, float threshold) const;
+
+       /* makePeak
+       Returns a Peak object given an audio buffer, taking number of channels into
+       account. */
+
+       Peak makePeak(const mcl::AudioBuffer& b) const;
+
+       /* lineInRec
+       Records from line in. 'maxFrames' determines how many frames to record 
+       before the internal tracker loops over. The value changes whether you are 
+       recording in RIGID or FREE mode. Returns the number of recorded frames. */
+
+       Frame lineInRec(const mcl::AudioBuffer& inBuf, mcl::AudioBuffer& recBuf,
+           Frame inputTracker, Frame maxFrames, float inVol, bool allowsOverdub) const;
+
+       /* processLineIn
+       Computes line in peaks and prepares the internal working buffer for input
+       recording. */
+
+       void processLineIn(const model::Mixer& mixer, const mcl::AudioBuffer& inBuf,
+           float inVol, float recTriggerLevel, bool isSeqActive) const;
+
+       void renderChannels(const std::vector<Channel>& channels, mcl::AudioBuffer& out, mcl::AudioBuffer& in) const;
+       void renderMasterIn(const Channel&, mcl::AudioBuffer& in) const;
+       void renderMasterOut(const Channel&, mcl::AudioBuffer& out) const;
+       void renderPreview(const Channel&, mcl::AudioBuffer& out) const;
+
+       /* limit
+       Applies a very dumb hard limiter. */
+
+       void limit(mcl::AudioBuffer& outBuf) const;
+
+       /* finalizeOutput
+       Last touches after the output has been rendered: apply inToOut if any, apply
+       output volume, compute peak. */
+
+       void finalizeOutput(const model::Mixer&, mcl::AudioBuffer&, bool inToOut,
+           bool limit, float vol) const;
+
+       /* startInputRec, stopInputRec
+       Starts/stops input recording on frame 'from'. The latter returns the number 
+       of recorded frames. */
+
+       void  startInputRec(Frame from);
+       Frame stopInputRec();
+
+       model::Model& m_model;
+
+       /* m_signalCbFired, m_endOfRecCbFired
+       Boolean guards to determine whether the callbacks have been fired or not, 
+       to avoid retriggering. Mutable: strictly for internal use only. */
+
+       mutable bool m_signalCbFired;
+       mutable bool m_endOfRecCbFired;
+};
+} // namespace giada::m
 
 #endif
index 057e70068b29fc75c98310b7a6417ad31e0fe06f..bbf46c93e90369f230cfa89ff1a955856b2f43d1 100644 (file)
 
 #include "core/mixerHandler.h"
 #include "core/channels/channelManager.h"
-#include "core/clock.h"
-#include "core/conf.h"
 #include "core/const.h"
-#include "core/init.h"
-#include "core/kernelAudio.h"
-#include "core/kernelMidi.h"
-#include "core/midiMapConf.h"
 #include "core/mixer.h"
 #include "core/model/model.h"
-#include "core/patch.h"
-#include "core/plugins/plugin.h"
-#include "core/plugins/pluginHost.h"
 #include "core/plugins/pluginManager.h"
-#include "core/recManager.h"
 #include "core/recorder.h"
-#include "core/recorderHandler.h"
-#include "core/wave.h"
-#include "core/waveFx.h"
-#include "core/waveManager.h"
 #include "glue/channel.h"
 #include "glue/main.h"
 #include "utils/fs.h"
 #include <cassert>
 #include <vector>
 
-namespace giada::m::mh
+namespace giada::m
 {
-namespace
+MixerHandler::MixerHandler(model::Model& model, Mixer& mixer)
+: onChannelsAltered(nullptr)
+, onChannelRecorded(nullptr)
+, m_model(model)
+, m_mixer(mixer)
 {
-channel::Data& addChannel_(ChannelType type, ID columnId)
-{
-       model::get().channels.push_back(channelManager::create(/*id=*/0, type, columnId));
-       model::swap(model::SwapType::HARD);
-
-       return model::get().channels.back();
-}
-
-/* -------------------------------------------------------------------------- */
-
-waveManager::Result createWave_(const std::string& fname)
-{
-       return waveManager::createFromFile(fname, /*id=*/0, conf::conf.samplerate,
-           conf::conf.rsmpQuality);
 }
 
 /* -------------------------------------------------------------------------- */
 
-bool anyChannel_(std::function<bool(const channel::Data&)> f)
+void MixerHandler::reset(Frame framesInLoop, Frame framesInBuffer, ChannelManager& channelManager)
 {
-       return std::any_of(model::get().channels.begin(), model::get().channels.end(), f);
-}
-
-/* -------------------------------------------------------------------------- */
+       m_mixer.reset(framesInLoop, framesInBuffer);
 
-template <typename F>
-std::vector<channel::Data*> getChannelsIf_(F f)
-{
-       std::vector<channel::Data*> out;
-       for (channel::Data& ch : model::get().channels)
-               if (f(ch))
-                       out.push_back(&ch);
-       return out;
-}
+       m_model.get().channels.clear();
 
-std::vector<channel::Data*> getRecordableChannels_()
-{
-       return getChannelsIf_([](const channel::Data& c) { return c.canInputRec() && !c.hasWave(); });
-}
+       m_model.get().channels.push_back(channelManager.create(
+           Mixer::MASTER_OUT_CHANNEL_ID, ChannelType::MASTER, /*columnId=*/0, framesInBuffer));
+       m_model.get().channels.push_back(channelManager.create(
+           Mixer::MASTER_IN_CHANNEL_ID, ChannelType::MASTER, /*columnId=*/0, framesInBuffer));
+       m_model.get().channels.push_back(channelManager.create(
+           Mixer::PREVIEW_CHANNEL_ID, ChannelType::PREVIEW, /*columnId=*/0, framesInBuffer));
 
-std::vector<channel::Data*> getOverdubbableChannels_()
-{
-       return getChannelsIf_([](const channel::Data& c) { return c.canInputRec() && c.hasWave(); });
+       m_model.swap(model::SwapType::NONE);
 }
 
 /* -------------------------------------------------------------------------- */
 
-void setupChannelPostRecording_(channel::Data& ch)
+Channel& MixerHandler::addChannel(ChannelType type, ID columnId, int bufferSize,
+    ChannelManager& channelManager)
 {
-       /* Start sample channels in loop mode right away. */
-       if (ch.samplePlayer->isAnyLoopMode())
-               samplePlayer::kickIn(ch, clock::getCurrentFrame());
-       /* Disable 'arm' button if overdub protection is on. */
-       if (ch.audioReceiver->overdubProtection == true)
-               ch.armed = false;
+       m_model.get().channels.push_back(channelManager.create(/*id=*/0, type, columnId, bufferSize));
+       m_model.swap(model::SwapType::HARD);
+
+       return m_model.get().channels.back();
 }
 
 /* -------------------------------------------------------------------------- */
 
-/* recordChannel_
-Records the current Mixer audio input data into an empty channel. */
-
-void recordChannel_(channel::Data& ch, Frame recordedFrames)
+void MixerHandler::loadChannel(ID channelId, std::unique_ptr<Wave> w)
 {
-       /* Create a new Wave with audio coming from Mixer's input buffer. */
+       assert(onChannelsAltered != nullptr);
 
-       std::string           filename = "TAKE-" + std::to_string(patch::patch.lastTakeId++) + ".wav";
-       std::unique_ptr<Wave> wave     = waveManager::createEmpty(recordedFrames, G_MAX_IO_CHANS,
-        conf::conf.samplerate, filename);
+       m_model.addShared(std::move(w));
 
-       G_DEBUG("Created new Wave, size=" << wave->getBuffer().countFrames());
-
-       /* Copy up to wave.getSize() from the mixer's input buffer into wave's. */
+       Channel& channel = m_model.get().getChannel(channelId);
+       Wave&    wave    = m_model.backShared<Wave>();
+       Wave*    old     = channel.samplePlayer->getWave();
 
-       wave->getBuffer().set(mixer::getRecBuffer(), wave->getBuffer().countFrames());
+       channel.samplePlayer->loadWave(channel, &wave);
+       m_model.swap(model::SwapType::HARD);
 
-       /* Update channel with the new Wave. */
+       /* Remove old wave, if any. It is safe to do it now: the audio thread is
+       already processing the new layout. */
 
-       model::add(std::move(wave));
-       samplePlayer::loadWave(ch, &model::back<Wave>());
-       setupChannelPostRecording_(ch);
+       if (old != nullptr)
+               m_model.removeShared<Wave>(*old);
 
-       model::swap(model::SwapType::HARD);
+       onChannelsAltered();
 }
 
 /* -------------------------------------------------------------------------- */
 
-/* overdubChannel_
-Records the current Mixer audio input data into a channel with an existing
-Wave, overdub mode. */
-
-void overdubChannel_(channel::Data& ch)
+void MixerHandler::addAndLoadChannel(ID columnId, std::unique_ptr<Wave> w, int bufferSize,
+    ChannelManager& channelManager)
 {
-       Wave* wave = ch.samplePlayer->getWave();
+       assert(onChannelsAltered != nullptr);
 
-       /* Need model::DataLock here, as data might be being read by the audio
-       thread at the same time. */
+       m_model.addShared(std::move(w));
 
-       model::DataLock lock;
-       wave->getBuffer().sum(mixer::getRecBuffer(), /*gain=*/1.0f);
-       wave->setLogical(true);
+       Wave&    wave    = m_model.backShared<Wave>();
+       Channel& channel = addChannel(ChannelType::SAMPLE, columnId, bufferSize, channelManager);
 
-       setupChannelPostRecording_(ch);
+       channel.samplePlayer->loadWave(channel, &wave);
+       m_model.swap(model::SwapType::HARD);
+
+       onChannelsAltered();
 }
-} // namespace
 
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
 /* -------------------------------------------------------------------------- */
 
-void init()
+#ifdef WITH_VST
+void MixerHandler::cloneChannel(ID channelId, int sampleRate, int bufferSize,
+    ChannelManager& channelManager, WaveManager& waveManager, const Sequencer& sequencer,
+    PluginManager& pluginManager)
+#else
+void MixerHandler::cloneChannel(ID channelId, int bufferSize, ChannelManager& channelManager,
+    WaveManager& waveManager)
+#endif
 {
-       mixer::init(clock::getMaxFramesInLoop(), kernelAudio::getRealBufSize());
-
-       model::get().channels.clear();
-
-       model::get().channels.push_back(channelManager::create(
-           mixer::MASTER_OUT_CHANNEL_ID, ChannelType::MASTER, /*columnId=*/0));
-       model::get().channels.push_back(channelManager::create(
-           mixer::MASTER_IN_CHANNEL_ID, ChannelType::MASTER, /*columnId=*/0));
-       model::get().channels.push_back(channelManager::create(
-           mixer::PREVIEW_CHANNEL_ID, ChannelType::PREVIEW, /*columnId=*/0));
+       const Channel& oldChannel = m_model.get().getChannel(channelId);
+       Channel        newChannel = channelManager.create(oldChannel, bufferSize);
 
-       model::swap(model::SwapType::NONE);
-}
+       /* Clone waves and plugins first in their own lists. */
 
-/* -------------------------------------------------------------------------- */
+       if (oldChannel.samplePlayer && oldChannel.samplePlayer->hasWave())
+       {
+               const Wave& oldWave = *oldChannel.samplePlayer->getWave();
+               m_model.addShared(waveManager.createFromWave(oldWave, 0, oldWave.getBuffer().countFrames()));
+               newChannel.samplePlayer->loadWave(newChannel, &m_model.backShared<Wave>());
+       }
 
-void close()
-{
-       mixer::disable();
-}
+#ifdef WITH_VST
+       for (const Plugin* plugin : oldChannel.plugins)
+       {
+               m_model.addShared(pluginManager.makePlugin(*plugin, sampleRate, bufferSize, sequencer));
+               newChannel.plugins.push_back(&m_model.backShared<Plugin>());
+       }
+#endif
 
-/* -------------------------------------------------------------------------- */
+       /* Then push the new channel in the channels vector. */
 
-void addChannel(ChannelType type, ID columnId)
-{
-       addChannel_(type, columnId);
+       m_model.get().channels.push_back(newChannel);
+       m_model.swap(model::SwapType::HARD);
 }
 
 /* -------------------------------------------------------------------------- */
 
-int loadChannel(ID channelId, const std::string& fname)
+void MixerHandler::freeChannel(ID channelId)
 {
-       waveManager::Result res = createWave_(fname);
-
-       if (res.status != G_RES_OK)
-               return res.status;
+       assert(onChannelsAltered != nullptr);
 
-       model::add(std::move(res.wave));
+       Channel& ch = m_model.get().getChannel(channelId);
 
-       Wave& wave = model::back<Wave>();
-       Wave* old  = model::get().getChannel(channelId).samplePlayer->getWave();
-
-       samplePlayer::loadWave(model::get().getChannel(channelId), &wave);
-       model::swap(model::SwapType::HARD);
+       assert(ch.samplePlayer);
 
-       /* Remove old wave, if any. It is safe to do it now: the audio thread is
-       already processing the new layout. */
+       const Wave* wave = ch.samplePlayer->getWave();
 
-       if (old != nullptr)
-               model::remove<Wave>(*old);
+       ch.samplePlayer->loadWave(ch, nullptr);
+       m_model.swap(model::SwapType::HARD);
 
-       recManager::refreshInputRecMode();
+       if (wave != nullptr)
+               m_model.removeShared<Wave>(*wave);
 
-       return res.status;
+       onChannelsAltered();
 }
 
 /* -------------------------------------------------------------------------- */
 
-int addAndLoadChannel(ID columnId, const std::string& fname)
-{
-       waveManager::Result res = createWave_(fname);
-       if (res.status == G_RES_OK)
-               addAndLoadChannel(columnId, std::move(res.wave));
-       return res.status;
-}
-
-void addAndLoadChannel(ID columnId, std::unique_ptr<Wave>&& w)
+void MixerHandler::freeAllChannels()
 {
-       model::add(std::move(w));
+       assert(onChannelsAltered != nullptr);
 
-       Wave&          wave    = model::back<Wave>();
-       channel::Data& channel = addChannel_(ChannelType::SAMPLE, columnId);
+       for (Channel& ch : m_model.get().channels)
+               if (ch.samplePlayer)
+                       ch.samplePlayer->loadWave(ch, nullptr);
 
-       samplePlayer::loadWave(channel, &wave);
-       model::swap(model::SwapType::HARD);
+       m_model.swap(model::SwapType::HARD);
+       m_model.clearShared<model::WavePtrs>();
 
-       recManager::refreshInputRecMode();
+       onChannelsAltered();
 }
 
 /* -------------------------------------------------------------------------- */
 
-void cloneChannel(ID channelId)
+void MixerHandler::deleteChannel(ID channelId)
 {
-       channel::Data& oldChannel = model::get().getChannel(channelId);
-       channel::Data  newChannel = channelManager::create(oldChannel);
-
-       /* Clone plugins, actions and wave first in their own lists. */
+       assert(onChannelsAltered != nullptr);
 
+       const Channel& ch   = m_model.get().getChannel(channelId);
+       const Wave*    wave = ch.samplePlayer ? ch.samplePlayer->getWave() : nullptr;
 #ifdef WITH_VST
-       newChannel.plugins = pluginHost::clonePlugins(oldChannel.plugins);
+       const std::vector<Plugin*> plugins = ch.plugins;
 #endif
-       recorderHandler::cloneActions(channelId, newChannel.id);
 
-       if (newChannel.samplePlayer && newChannel.samplePlayer->hasWave())
-       {
-               Wave* wave = newChannel.samplePlayer->getWave();
-               model::add(waveManager::createFromWave(*wave, 0, wave->getBuffer().countFrames()));
-       }
+       u::vector::removeIf(m_model.get().channels, [channelId](const Channel& c) {
+               return c.id == channelId;
+       });
+       m_model.swap(model::SwapType::HARD);
 
-       /* Then push the new channel in the channels vector. */
+       if (wave != nullptr)
+               m_model.removeShared<Wave>(*wave);
 
-       model::get().channels.push_back(newChannel);
-       model::swap(model::SwapType::HARD);
+       onChannelsAltered();
 }
 
 /* -------------------------------------------------------------------------- */
 
-void freeChannel(ID channelId)
+void MixerHandler::renameChannel(ID channelId, const std::string& name)
 {
-       channel::Data& ch = model::get().getChannel(channelId);
-
-       assert(ch.samplePlayer);
-
-       const Wave* wave = ch.samplePlayer->getWave();
+       m_model.get().getChannel(channelId).name = name;
+       m_model.swap(model::SwapType::HARD);
+}
 
-       samplePlayer::loadWave(ch, nullptr);
-       model::swap(model::SwapType::HARD);
+/* -------------------------------------------------------------------------- */
 
-       if (wave != nullptr)
-               model::remove<Wave>(*wave);
+void MixerHandler::updateSoloCount()
+{
+       bool hasSolos = forAnyChannel([](const Channel& ch) {
+               return ch.isSoloed();
+       });
 
-       recManager::refreshInputRecMode();
+       m_model.get().mixer.hasSolos = hasSolos;
+       m_model.swap(model::SwapType::NONE);
 }
 
 /* -------------------------------------------------------------------------- */
 
-void freeAllChannels()
+void MixerHandler::setInToOut(bool v)
 {
-       for (channel::Data& ch : model::get().channels)
-               if (ch.samplePlayer)
-                       samplePlayer::loadWave(ch, nullptr);
-
-       model::swap(model::SwapType::HARD);
-       model::clear<model::WavePtrs>();
-
-       recManager::refreshInputRecMode();
+       m_model.get().mixer.inToOut = v;
+       m_model.swap(model::SwapType::NONE);
 }
 
 /* -------------------------------------------------------------------------- */
 
-void deleteChannel(ID channelId)
+float MixerHandler::getInVol() const
 {
-       const channel::Data& ch   = model::get().getChannel(channelId);
-       const Wave*          wave = ch.samplePlayer ? ch.samplePlayer->getWave() : nullptr;
-#ifdef WITH_VST
-       const std::vector<Plugin*> plugins = ch.plugins;
-#endif
+       return m_model.get().getChannel(Mixer::MASTER_IN_CHANNEL_ID).volume;
+}
 
-       u::vector::removeIf(model::get().channels, [channelId](const channel::Data& c) {
-               return c.id == channelId;
-       });
-       model::swap(model::SwapType::HARD);
+float MixerHandler::getOutVol() const
+{
+       return m_model.get().getChannel(Mixer::MASTER_OUT_CHANNEL_ID).volume;
+}
 
-       if (wave != nullptr)
-               model::remove<Wave>(*wave);
+bool MixerHandler::getInToOut() const
+{
+       return m_model.get().mixer.inToOut;
+}
 
-#ifdef WITH_VST
-       pluginHost::freePlugins(plugins);
-#endif
+/* -------------------------------------------------------------------------- */
 
-       recManager::refreshInputRecMode();
+void MixerHandler::startInputRec(Frame currentFrame)
+{
+       m_mixer.startInputRec(currentFrame);
 }
 
 /* -------------------------------------------------------------------------- */
 
-void renameChannel(ID channelId, const std::string& name)
+Frame MixerHandler::stopInputRec()
 {
-       model::get().getChannel(channelId).name = name;
-       model::swap(model::SwapType::HARD);
+       return m_mixer.stopInputRec();
 }
 
 /* -------------------------------------------------------------------------- */
 
-void updateSoloCount()
+void MixerHandler::finalizeInputRec(Frame recordedFrames, Frame currentFrame)
 {
-       bool hasSolos = anyChannel_([](const channel::Data& ch) {
-               return !ch.isInternal() && ch.solo;
-       });
+       for (Channel* ch : getRecordableChannels())
+               recordChannel(*ch, recordedFrames, currentFrame);
+       for (Channel* ch : getOverdubbableChannels())
+               overdubChannel(*ch, currentFrame);
+
+       m_mixer.clearRecBuffer();
 
-       model::get().mixer.hasSolos = hasSolos;
-       model::swap(model::SwapType::NONE);
+       onChannelsAltered();
 }
 
 /* -------------------------------------------------------------------------- */
 
-void setInToOut(bool v)
+bool MixerHandler::hasInputRecordableChannels() const
 {
-       model::get().mixer.inToOut = v;
-       model::swap(model::SwapType::NONE);
+       return forAnyChannel([](const Channel& ch) { return ch.canInputRec(); });
 }
 
-/* -------------------------------------------------------------------------- */
+bool MixerHandler::hasActionRecordableChannels() const
+{
+       return forAnyChannel([](const Channel& ch) { return ch.canActionRec(); });
+}
 
-float getInVol()
+bool MixerHandler::hasLogicalSamples() const
 {
-       return model::get().getChannel(mixer::MASTER_IN_CHANNEL_ID).volume;
+       return forAnyChannel([](const Channel& ch) { return ch.samplePlayer && ch.samplePlayer->hasLogicalWave(); });
 }
 
-float getOutVol()
+bool MixerHandler::hasEditedSamples() const
 {
-       return model::get().getChannel(mixer::MASTER_OUT_CHANNEL_ID).volume;
+       return forAnyChannel([](const Channel& ch) {
+               return ch.samplePlayer && ch.samplePlayer->hasEditedWave();
+       });
 }
 
-bool getInToOut()
+bool MixerHandler::hasActions() const
 {
-       return model::get().mixer.inToOut;
+       return forAnyChannel([](const Channel& ch) { return ch.hasActions; });
+}
+
+bool MixerHandler::hasAudioData() const
+{
+       return forAnyChannel([](const Channel& ch) {
+               return ch.samplePlayer && ch.samplePlayer->hasWave();
+       });
 }
 
 /* -------------------------------------------------------------------------- */
 
-void finalizeInputRec(Frame recordedFrames)
+bool MixerHandler::forAnyChannel(std::function<bool(const Channel&)> f) const
 {
-       for (channel::Data* ch : getRecordableChannels_())
-               recordChannel_(*ch, recordedFrames);
-       for (channel::Data* ch : getOverdubbableChannels_())
-               overdubChannel_(*ch);
-
-       mixer::clearRecBuffer();
+       return std::any_of(m_model.get().channels.begin(), m_model.get().channels.end(), f);
 }
 
 /* -------------------------------------------------------------------------- */
 
-bool hasInputRecordableChannels()
+std::vector<Channel*> MixerHandler::getChannelsIf(std::function<bool(const Channel&)> f)
 {
-       return anyChannel_([](const channel::Data& ch) { return ch.canInputRec(); });
+       std::vector<Channel*> out;
+       for (Channel& ch : m_model.get().channels)
+               if (f(ch))
+                       out.push_back(&ch);
+       return out;
 }
 
-bool hasActionRecordableChannels()
+std::vector<Channel*> MixerHandler::getRecordableChannels()
 {
-       return anyChannel_([](const channel::Data& ch) { return ch.canActionRec(); });
+       return getChannelsIf([](const Channel& c) { return c.canInputRec() && !c.hasWave(); });
 }
 
-bool hasLogicalSamples()
+std::vector<Channel*> MixerHandler::getOverdubbableChannels()
 {
-       return anyChannel_([](const channel::Data& ch) { return ch.samplePlayer && ch.samplePlayer->hasLogicalWave(); });
+       return getChannelsIf([](const Channel& c) { return c.canInputRec() && c.hasWave(); });
 }
 
-bool hasEditedSamples()
+/* -------------------------------------------------------------------------- */
+
+void MixerHandler::setupChannelPostRecording(Channel& ch, Frame currentFrame)
 {
-       return anyChannel_([](const channel::Data& ch) {
-               return ch.samplePlayer && ch.samplePlayer->hasEditedWave();
-       });
+       /* Start sample channels in loop mode right away. */
+       if (ch.samplePlayer->isAnyLoopMode())
+               ch.samplePlayer->kickIn(ch, currentFrame);
+       /* Disable 'arm' button if overdub protection is on. */
+       if (ch.audioReceiver->overdubProtection == true)
+               ch.armed = false;
 }
 
-bool hasActions()
+/* -------------------------------------------------------------------------- */
+
+void MixerHandler::recordChannel(Channel& ch, Frame recordedFrames, Frame currentFrame)
 {
-       return anyChannel_([](const channel::Data& ch) { return ch.hasActions; });
+       assert(onChannelRecorded != nullptr);
+
+       std::unique_ptr<Wave> wave = onChannelRecorded(recordedFrames);
+
+       G_DEBUG("Created new Wave, size=" << wave->getBuffer().countFrames());
+
+       /* Copy up to wave.getSize() from the mixer's input buffer into wave's. */
+
+       wave->getBuffer().set(m_mixer.getRecBuffer(), wave->getBuffer().countFrames());
+
+       /* Update channel with the new Wave. */
+
+       m_model.addShared(std::move(wave));
+       ch.samplePlayer->loadWave(ch, &m_model.backShared<Wave>());
+       setupChannelPostRecording(ch, currentFrame);
+
+       m_model.swap(model::SwapType::HARD);
 }
 
-bool hasAudioData()
+/* -------------------------------------------------------------------------- */
+
+void MixerHandler::overdubChannel(Channel& ch, Frame currentFrame)
 {
-       return anyChannel_([](const channel::Data& ch) {
-               return ch.samplePlayer && ch.samplePlayer->hasWave();
-       });
+       Wave* wave = ch.samplePlayer->getWave();
+
+       /* Need model::DataLock here, as data might be being read by the audio
+       thread at the same time. */
+
+       model::DataLock lock = m_model.lockData();
+
+       wave->getBuffer().sum(m_mixer.getRecBuffer(), /*gain=*/1.0f);
+       wave->setLogical(true);
+
+       setupChannelPostRecording(ch, currentFrame);
 }
-} // namespace giada::m::mh
+} // namespace giada::m
index 4da3053a19cb1849ddc740ea2b909f0c6304ff6b..92a9de58451df5d4a3cf80187d399cba9a4d00c2 100644 (file)
 #ifndef G_MIXER_HANDLER_H
 #define G_MIXER_HANDLER_H
 
+#include "core/plugins/pluginManager.h"
+#include "core/waveManager.h"
 #include "types.h"
+#include <functional>
 #include <memory>
 #include <string>
 
+namespace giada::m::model
+{
+class Model;
+}
+
 namespace giada::m
 {
-class Wave;
 class Channel;
-class SampleChannel;
-} // namespace giada::m
-namespace giada::m::mh
+class Wave;
+class Mixer;
+class Plugin;
+class ChannelManager;
+class Sequencer;
+class MixerHandler final
 {
-/* init
-Initializes mixer. */
+public:
+       MixerHandler(model::Model&, Mixer&);
+
+       /* hasLogicalSamples
+    True if 1 or more samples are logical (memory only, such as takes). */
+
+       bool hasLogicalSamples() const;
+
+       /* hasEditedSamples
+    True if 1 or more samples have been edited via Sample Editor. */
+
+       bool hasEditedSamples() const;
+
+       /* has(Input|Action)RecordableChannels
+    Tells whether Mixer has one or more input or action recordable channels. */
 
-void init();
+       bool hasInputRecordableChannels() const;
+       bool hasActionRecordableChannels() const;
 
-/* close
-Closes mixer and frees resources. */
+       /* hasActions
+    True if at least one Channel has actions recorded in it. */
 
-void close();
+       bool hasActions() const;
 
-/* addChannel
-Adds a new channel of type 'type' into the channels stack. Returns the new
-channel ID. */
+       /* hasAudioData
+    True if at least one Sample Channel has some audio recorded in it. */
 
-void addChannel(ChannelType type, ID columnId);
+       bool hasAudioData() const;
 
-/* loadChannel
-Loads a new Wave inside a Sample Channel. */
+       float getInVol() const;
+       float getOutVol() const;
+       bool  getInToOut() const;
 
-int loadChannel(ID channelId, const std::string& fname);
+       /* reset
+       Brings everything back to the initial state. */
 
-/* addAndLoadChannel (1)
-Creates a new channels, fills it with a Wave and then add it to the stack. */
+       void reset(Frame framesInLoop, Frame framesInBuffer, ChannelManager&);
 
-int addAndLoadChannel(ID columnId, const std::string& fname);
+       /* addChannel
+    Adds a new channel of type 'type' into the channels stack. Returns the new
+    channel ID. */
 
-/* addAndLoadChannel (2)
-Same as (1), but Wave is already provided. */
+       Channel& addChannel(ChannelType type, ID columnId, int bufferSize, ChannelManager&);
 
-void addAndLoadChannel(ID columnId, std::unique_ptr<Wave>&& w);
+       /* loadChannel
+    Loads a new Wave inside a Sample Channel. */
 
-/* freeChannel
-Unloads existing Wave from a Sample Channel. */
+       void loadChannel(ID channelId, std::unique_ptr<Wave> w);
 
-void freeChannel(ID channelId);
+       /* addAndLoadChannel
+    Creates a new channels, fills it with a Wave and then add it to the stack. */
 
-/* deleteChannel
-Completely removes a channel from the stack. */
+       void addAndLoadChannel(ID columnId, std::unique_ptr<Wave> w, int bufferSize,
+           ChannelManager&);
 
-void deleteChannel(ID channelId);
+       /* freeChannel
+    Unloads existing Wave from a Sample Channel. */
 
-void cloneChannel(ID channelId);
-void renameChannel(ID channelId, const std::string& name);
-void freeAllChannels();
+       void freeChannel(ID channelId);
 
-void setInToOut(bool v);
+       /* deleteChannel
+    Completely removes a channel from the stack. */
 
-/* updateSoloCount
-Updates the number of solo-ed channels in mixer. */
+       void deleteChannel(ID channelId);
 
-void updateSoloCount();
+#ifdef WITH_VST
+       void cloneChannel(ID channelId, int sampleRate, int bufferSize, ChannelManager&,
+           WaveManager&, const Sequencer&, PluginManager&);
+#else
+       void cloneChannel(ID channelId, int bufferSize, ChannelManager&, WaveManager&);
+#endif
+       void renameChannel(ID channelId, const std::string& name);
+       void freeAllChannels();
+
+       void setInToOut(bool v);
+
+       /* updateSoloCount
+    Updates the number of solo-ed channels in mixer. */
+
+       void updateSoloCount();
+
+       /* startInputRec
+       Initializes Mixer for input recording. */
 
-/* finalizeInputRec
-Fills armed Sample Channels with audio data coming from an input recording
-session. */
+       void startInputRec(Frame currentFrame);
 
-void finalizeInputRec(Frame recordedFrames);
+       /* stopInputRec
+       Terminates input recording in Mixer. Returns the number of recorded frames. 
+       Call finalizeInputRec() if you really want to finish the input recording 
+       operation. */
 
-/* hasLogicalSamples
-True if 1 or more samples are logical (memory only, such as takes) */
+       Frame stopInputRec();
 
-bool hasLogicalSamples();
+       /* finalizeInputRec
+    Fills armed Sample Channels with audio data coming from an input recording
+    session. */
 
-/* hasEditedSamples
-True if 1 or more samples was edited via gEditor */
+       void finalizeInputRec(Frame recordedFrames, Frame currentFrame);
 
-bool hasEditedSamples();
+       /* onChannelsAltered
+       Fired when something is done on channels (added, removed, loaded, ...). */
 
-/* has(Input|Action)RecordableChannels
-Tells whether Mixer has one or more input or action recordable channels. */
+       std::function<void()> onChannelsAltered;
 
-bool hasInputRecordableChannels();
-bool hasActionRecordableChannels();
+       /* onChannelRecorded
+       Fired during the input recording finalization, when a new empty Wave must
+       be added to each armed channel in order to store recorded audio coming from
+       Mixer. */
 
-/* hasActions
-True if at least one Channel has actions recorded in it. */
+       std::function<std::unique_ptr<Wave>(Frame)> onChannelRecorded;
 
-bool hasActions();
+private:
+       bool forAnyChannel(std::function<bool(const Channel&)> f) const;
 
-/* hasAudioData
-True if at least one Sample Channel has some audio recorded in it. */
+       std::vector<Channel*> getChannelsIf(std::function<bool(const Channel&)> f);
+       std::vector<Channel*> getRecordableChannels();
+       std::vector<Channel*> getOverdubbableChannels();
 
-bool hasAudioData();
+       void setupChannelPostRecording(Channel& ch, Frame currentFrame);
 
-float getInVol();
-float getOutVol();
-bool  getInToOut();
-} // namespace giada::m::mh
+       /* recordChannel
+       Records the current Mixer audio input data into an empty channel. */
+
+       void recordChannel(Channel& ch, Frame recordedFrames, Frame currentFrame);
+
+       /* overdubChannel
+       Records the current Mixer audio input data into a channel with an existing
+       Wave, overdub mode. */
+
+       void overdubChannel(Channel& ch, Frame currentFrame);
+
+       model::Model& m_model;
+       Mixer&        m_mixer;
+};
+} // namespace giada::m
 
 #endif
diff --git a/src/core/model/mixer.cpp b/src/core/model/mixer.cpp
new file mode 100644 (file)
index 0000000..e667c3f
--- /dev/null
@@ -0,0 +1,102 @@
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+#include "core/model/mixer.h"
+
+namespace giada::m::model
+{
+Mixer::Shared& Mixer::Shared::operator=(const Mixer::Shared& o)
+{
+       if (this == &o)
+               return *this;
+       active.store(o.active.load());
+       peakOutL.store(0.0f);
+       peakOutR.store(0.0f);
+       peakInL.store(0.0f);
+       peakInR.store(0.0f);
+       inputTracker.store(0);
+       return *this;
+}
+
+/* -------------------------------------------------------------------------- */
+
+bool Mixer::a_isActive() const
+{
+       return shared->active.load() == true;
+}
+
+/* -------------------------------------------------------------------------- */
+
+Frame Mixer::a_getInputTracker() const
+{
+       return shared->inputTracker.load();
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Mixer::a_setActive(bool isActive) const
+{
+       shared->active.store(isActive);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Mixer::a_setInputTracker(Frame f) const
+{
+       shared->inputTracker.store(f);
+}
+
+/* -------------------------------------------------------------------------- */
+
+Peak Mixer::a_getPeakOut() const
+{
+       return {shared->peakOutL.load(), shared->peakOutR.load()};
+}
+
+Peak Mixer::a_getPeakIn() const
+{
+       return {shared->peakInL.load(), shared->peakInR.load()};
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Mixer::a_setPeakOut(Peak p) const
+{
+       shared->peakOutL.store(p.left);
+       shared->peakOutR.store(p.right);
+}
+
+void Mixer::a_setPeakIn(Peak p) const
+{
+       shared->peakInL.store(p.left);
+       shared->peakInR.store(p.right);
+}
+
+/* -------------------------------------------------------------------------- */
+
+mcl::AudioBuffer& Mixer::getRecBuffer() const { return shared->recBuffer; }
+mcl::AudioBuffer& Mixer::getInBuffer() const { return shared->inBuffer; }
+} // namespace giada::m::model
diff --git a/src/core/model/mixer.h b/src/core/model/mixer.h
new file mode 100644 (file)
index 0000000..a1377b1
--- /dev/null
@@ -0,0 +1,90 @@
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+#ifndef G_MODEL_MIXER_H
+#define G_MODEL_MIXER_H
+
+#include "core/const.h"
+#include "core/types.h"
+#include "core/weakAtomic.h"
+#include "deps/mcl-audio-buffer/src/audioBuffer.hpp"
+#include <atomic>
+
+namespace giada::m::model
+{
+class Mixer
+{
+       friend class Model;
+
+public:
+       bool  a_isActive() const;
+       Frame a_getInputTracker() const;
+       Peak  a_getPeakOut() const;
+       Peak  a_getPeakIn() const;
+
+       void a_setActive(bool) const;
+       void a_setInputTracker(Frame) const;
+       void a_setPeakOut(Peak) const;
+       void a_setPeakIn(Peak) const;
+
+       mcl::AudioBuffer& getRecBuffer() const;
+       mcl::AudioBuffer& getInBuffer() const;
+
+       bool  hasSolos        = false;
+       bool  inToOut         = false;
+       bool  limitOutput     = false;
+       bool  allowsOverdub   = false;
+       Frame maxFramesToRec  = 0;
+       float recTriggerLevel = 0.0f;
+
+private:
+       struct Shared
+       {
+               Shared& operator=(const Shared&);
+
+               std::atomic<bool> active       = false;
+               WeakAtomic<float> peakOutL     = 0.0f;
+               WeakAtomic<float> peakOutR     = 0.0f;
+               WeakAtomic<float> peakInL      = 0.0f;
+               WeakAtomic<float> peakInR      = 0.0f;
+               WeakAtomic<Frame> inputTracker = 0;
+
+               /* recBuffer
+               Working buffer for audio recording. */
+
+               mcl::AudioBuffer recBuffer;
+
+               /* inBuffer
+               Working buffer for input channel. Used for the in->out bridge. */
+
+               mcl::AudioBuffer inBuffer;
+       };
+
+       Shared* shared = nullptr;
+};
+} // namespace giada::m::model
+
+#endif
index 5a69aece2aee89e5fc9f3122ed18728a706d47ee..3d203bbc845cbf2ba2cc7f948f1f6d12ae45b5d7 100644 (file)
 #include "core/channels/channelManager.h"
 #endif
 
+using namespace mcl;
+
 namespace giada::m::model
 {
 namespace
 {
-struct State
-{
-       Clock::State                                 clock;
-       Mixer::State                                 mixer;
-       std::vector<std::unique_ptr<channel::State>> channels;
-};
-
-struct Data
-{
-       std::vector<std::unique_ptr<channel::Buffer>> channels;
-       std::vector<std::unique_ptr<Wave>>            waves;
-       recorder::ActionMap                           actions;
-#ifdef WITH_VST
-       std::vector<std::unique_ptr<Plugin>> plugins;
-#endif
-};
-
-/* -------------------------------------------------------------------------- */
-
 template <typename T>
 auto getIter_(const std::vector<std::unique_ptr<T>>& source, ID id)
 {
@@ -81,231 +64,231 @@ void remove_(D& dest, T& ref)
 /* -------------------------------------------------------------------------- */
 /* -------------------------------------------------------------------------- */
 
-std::function<void(SwapType)> onSwap_ = nullptr;
-
-Swapper<Layout> layout;
-State           state;
-Data            data;
-
-/* -------------------------------------------------------------------------- */
-
-DataLock::DataLock(SwapType t)
-: m_swapType(t)
+DataLock::DataLock(Model& m, SwapType t)
+: m_model(m)
+, m_swapType(t)
 {
-       get().locked = true;
-       swap(SwapType::NONE);
+       m_model.get().locked = true;
+       m_model.swap(SwapType::NONE);
 }
 
 DataLock::~DataLock()
 {
-       get().locked = false;
-       swap(m_swapType);
+       m_model.get().locked = false;
+       m_model.swap(m_swapType);
 }
 
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
 /* -------------------------------------------------------------------------- */
 
-channel::Data& Layout::getChannel(ID id)
+Channel& Layout::getChannel(ID id)
 {
-       return const_cast<channel::Data&>(const_cast<const Layout*>(this)->getChannel(id));
+       return const_cast<Channel&>(const_cast<const Layout*>(this)->getChannel(id));
 }
 
-const channel::Data& Layout::getChannel(ID id) const
+const Channel& Layout::getChannel(ID id) const
 {
-       auto it = std::find_if(channels.begin(), channels.end(), [id](const channel::Data& c) {
+       auto it = std::find_if(channels.begin(), channels.end(), [id](const Channel& c) {
                return c.id == id;
        });
        assert(it != channels.end());
        return *it;
 }
 
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
 /* -------------------------------------------------------------------------- */
 
-void init()
+Model::Model()
+: onSwap(nullptr)
 {
-       get().clock.state = &state.clock;
-       get().mixer.state = &state.mixer;
-       swap(SwapType::NONE);
+       reset();
 }
 
 /* -------------------------------------------------------------------------- */
 
-Layout& get()
+void Model::reset()
 {
-       return layout.get();
+       m_shared = {};
+
+       get().sequencer.shared = &m_shared.sequencerShared;
+       get().mixer.shared     = &m_shared.mixerShared;
+       get().recorder.shared  = &m_shared.recorderShared;
+
+       swap(SwapType::NONE);
 }
 
-Lock get_RT()
+/* -------------------------------------------------------------------------- */
+
+Layout&       Model::get() { return m_layout.get(); }
+const Layout& Model::get() const { return m_layout.get(); }
+
+LayoutLock Model::get_RT()
 {
-       return Lock(layout);
+       return LayoutLock(m_layout);
 }
 
-void swap(SwapType t)
+void Model::swap(SwapType t)
 {
-       layout.swap();
-       if (onSwap_)
-               onSwap_(t);
+       m_layout.swap();
+       if (onSwap)
+               onSwap(t);
 }
 
-void onSwap(std::function<void(SwapType)> f)
+/* -------------------------------------------------------------------------- */
+
+DataLock Model::lockData(SwapType t)
 {
-       onSwap_ = f;
+       return DataLock(*this, t);
 }
 
 /* -------------------------------------------------------------------------- */
 
-bool isLocked()
+bool Model::isLocked() const
 {
-       return layout.isLocked();
+       return m_layout.isLocked();
 }
 
 /* -------------------------------------------------------------------------- */
 
 template <typename T>
-T& getAll()
+T& Model::getAllShared()
 {
 #ifdef WITH_VST
        if constexpr (std::is_same_v<T, PluginPtrs>)
-               return data.plugins;
+               return m_shared.plugins;
 #endif
        if constexpr (std::is_same_v<T, WavePtrs>)
-               return data.waves;
-       if constexpr (std::is_same_v<T, Actions>)
-               return data.actions;
-       if constexpr (std::is_same_v<T, ChannelBufferPtrs>)
-               return data.channels;
-       if constexpr (std::is_same_v<T, ChannelStatePtrs>)
-               return state.channels;
+               return m_shared.waves;
+       if constexpr (std::is_same_v<T, Actions::Map>)
+               return m_shared.actions;
+       if constexpr (std::is_same_v<T, ChannelSharedPtrs>)
+               return m_shared.channelsShared;
 
        assert(false);
 }
 
 #ifdef WITH_VST
-template PluginPtrs& getAll<PluginPtrs>();
+template PluginPtrs& Model::getAllShared<PluginPtrs>();
 #endif
-template WavePtrs&          getAll<WavePtrs>();
-template Actions&           getAll<Actions>();
-template ChannelBufferPtrs& getAll<ChannelBufferPtrs>();
-template ChannelStatePtrs&  getAll<ChannelStatePtrs>();
+template WavePtrs&          Model::getAllShared<WavePtrs>();
+template Actions::Map&      Model::getAllShared<Actions::Map>();
+template ChannelSharedPtrs& Model::getAllShared<ChannelSharedPtrs>();
 
 /* -------------------------------------------------------------------------- */
 
 template <typename T>
-T* find(ID id)
+T* Model::findShared(ID id)
 {
 #ifdef WITH_VST
        if constexpr (std::is_same_v<T, Plugin>)
-               return get_(data.plugins, id);
+               return get_(m_shared.plugins, id);
 #endif
        if constexpr (std::is_same_v<T, Wave>)
-               return get_(data.waves, id);
+               return get_(m_shared.waves, id);
 
        assert(false);
 }
 
 #ifdef WITH_VST
-template Plugin* find<Plugin>(ID id);
+template Plugin* Model::findShared<Plugin>(ID id);
 #endif
-template Wave* find<Wave>(ID id);
+template Wave* Model::findShared<Wave>(ID id);
 
 /* -------------------------------------------------------------------------- */
 
 template <typename T>
-void add(T obj)
+void Model::addShared(T obj)
 {
 #ifdef WITH_VST
        if constexpr (std::is_same_v<T, PluginPtr>)
-               data.plugins.push_back(std::move(obj));
+               m_shared.plugins.push_back(std::move(obj));
 #endif
        if constexpr (std::is_same_v<T, WavePtr>)
-               data.waves.push_back(std::move(obj));
-       if constexpr (std::is_same_v<T, ChannelBufferPtr>)
-               data.channels.push_back(std::move(obj));
-       if constexpr (std::is_same_v<T, ChannelStatePtr>)
-               state.channels.push_back(std::move(obj));
+               m_shared.waves.push_back(std::move(obj));
+       if constexpr (std::is_same_v<T, ChannelSharedPtr>)
+               m_shared.channelsShared.push_back(std::move(obj));
 }
 
 #ifdef WITH_VST
-template void add<PluginPtr>(PluginPtr p);
+template void Model::addShared<PluginPtr>(PluginPtr p);
 #endif
-template void add<WavePtr>(WavePtr p);
-template void add<ChannelBufferPtr>(ChannelBufferPtr p);
-template void add<ChannelStatePtr>(ChannelStatePtr p);
+template void Model::addShared<WavePtr>(WavePtr p);
+template void Model::addShared<ChannelSharedPtr>(ChannelSharedPtr p);
 
 /* -------------------------------------------------------------------------- */
 
 template <typename T>
-void remove(const T& ref)
+void Model::removeShared(const T& ref)
 {
 #ifdef WITH_VST
        if constexpr (std::is_same_v<T, Plugin>)
-               remove_(data.plugins, ref);
+               remove_(m_shared.plugins, ref);
 #endif
        if constexpr (std::is_same_v<T, Wave>)
-               remove_(data.waves, ref);
+               remove_(m_shared.waves, ref);
 }
 
 #ifdef WITH_VST
-template void remove<Plugin>(const Plugin& t);
+template void Model::removeShared<Plugin>(const Plugin& t);
 #endif
-template void remove<Wave>(const Wave& t);
+template void Model::removeShared<Wave>(const Wave& t);
 
 /* -------------------------------------------------------------------------- */
 
 template <typename T>
-T& back()
+T& Model::backShared()
 {
 #ifdef WITH_VST
        if constexpr (std::is_same_v<T, Plugin>)
-               return *data.plugins.back().get();
+               return *m_shared.plugins.back().get();
 #endif
        if constexpr (std::is_same_v<T, Wave>)
-               return *data.waves.back().get();
-       if constexpr (std::is_same_v<T, channel::State>)
-               return *state.channels.back().get();
-       if constexpr (std::is_same_v<T, channel::Buffer>)
-               return *data.channels.back().get();
+               return *m_shared.waves.back().get();
+       if constexpr (std::is_same_v<T, Channel::Shared>)
+               return *m_shared.channelsShared.back().get();
 }
 
 #ifdef WITH_VST
-template Plugin& back<Plugin>();
+template Plugin& Model::backShared<Plugin>();
 #endif
-template Wave&            back<Wave>();
-template channel::State&  back<channel::State>();
-template channel::Buffer& back<channel::Buffer>();
+template Wave&            Model::backShared<Wave>();
+template Channel::Shared& Model::backShared<Channel::Shared>();
 
 /* -------------------------------------------------------------------------- */
 
 template <typename T>
-void clear()
+void Model::clearShared()
 {
 #ifdef WITH_VST
        if constexpr (std::is_same_v<T, PluginPtrs>)
-               data.plugins.clear();
+               m_shared.plugins.clear();
 #endif
        if constexpr (std::is_same_v<T, WavePtrs>)
-               data.waves.clear();
+               m_shared.waves.clear();
 }
 
 #ifdef WITH_VST
-template void clear<PluginPtrs>();
+template void Model::clearShared<PluginPtrs>();
 #endif
-template void clear<WavePtrs>();
+template void Model::clearShared<WavePtrs>();
 
 /* -------------------------------------------------------------------------- */
 
 #ifdef G_DEBUG_MODE
 
-void debug()
+void Model::debug()
 {
        puts("======== SYSTEM STATUS ========");
 
-       puts("model::layout");
+       puts("model::layout.channels");
 
        int i = 0;
-       for (const channel::Data& c : get().channels)
+       for (const Channel& c : get().channels)
        {
-               printf("\t%d) - ID=%d name='%s' type=%d columnID=%d state=%p\n",
-                   i++, c.id, c.name.c_str(), (int)c.type, c.columnId, (void*)&c.state);
+               printf("\t%d) - ID=%d name='%s' type=%d columnID=%d shared=%p\n",
+                   i++, c.id, c.name.c_str(), (int)c.type, c.columnId, (void*)&c.shared);
 #ifdef WITH_VST
                if (c.plugins.size() > 0)
                {
@@ -319,7 +302,7 @@ void debug()
        puts("model::state.channels");
 
        i = 0;
-       for (const auto& c : state.channels)
+       for (const auto& c : m_shared.channelsShared)
        {
                printf("\t%d) - %p\n", i++, (void*)c.get());
        }
@@ -327,12 +310,12 @@ void debug()
        puts("model::data.waves");
 
        i = 0;
-       for (const auto& w : data.waves)
+       for (const auto& w : m_shared.waves)
                printf("\t%d) %p - ID=%d name='%s'\n", i++, (void*)w.get(), w->id, w->getPath().c_str());
 
        puts("model::data.actions");
 
-       for (const auto& [frame, actions] : getAll<Actions>())
+       for (const auto& [frame, actions] : getAllShared<Actions::Map>())
        {
                printf("\tframe: %d\n", frame);
                for (const Action& a : actions)
@@ -345,7 +328,7 @@ void debug()
        puts("model::data.plugins");
 
        i = 0;
-       for (const auto& p : data.plugins)
+       for (const auto& p : m_shared.plugins)
                printf("\t%d) %p - ID=%d\n", i++, (void*)p.get(), p->id);
 
 #endif
index d6dcfd090fd73252c78d08046edb72bac9cbf315..6b7fe33a30236550678102c7c1548fe0c9b0cdbd 100644 (file)
  *
  * -------------------------------------------------------------------------- */
 
-#ifndef G_RENDER_MODEL_H
-#define G_RENDER_MODEL_H
+#ifndef G_MODEL_H
+#define G_MODEL_H
 
 #include "core/channels/channel.h"
 #include "core/const.h"
+#include "core/model/mixer.h"
+#include "core/model/recorder.h"
+#include "core/model/sequencer.h"
 #include "core/plugins/plugin.h"
 #include "core/recorder.h"
-#include "core/swapper.h"
 #include "core/wave.h"
+#include "deps/mcl-atomic-swapper/src/atomic-swapper.hpp"
+#include "deps/mcl-audio-buffer/src/audioBuffer.hpp"
+#include "src/core/actions/actions.h"
 #include "utils/vector.h"
-#include <algorithm>
 
 namespace giada::m::model
 {
-struct Kernel
-{
-       bool audioReady = false;
-       bool midiReady  = false;
-};
-
-struct Recorder
-{
-       bool isRecordingAction = false;
-       bool isRecordingInput  = false;
-};
-
 struct MidiIn
 {
        bool     enabled    = false;
@@ -65,53 +57,16 @@ struct MidiIn
        uint32_t metronome  = 0x0;
 };
 
-struct Clock
-{
-       struct State
-       {
-               WeakAtomic<int> currentFrameWait = 0;
-               WeakAtomic<int> currentFrame     = 0;
-               WeakAtomic<int> currentBeat      = 0;
-       };
-
-       State*      state        = nullptr;
-       ClockStatus status       = ClockStatus::STOPPED;
-       int         framesInLoop = 0;
-       int         framesInBar  = 0;
-       int         framesInBeat = 0;
-       int         framesInSeq  = 0;
-       int         bars         = G_DEFAULT_BARS;
-       int         beats        = G_DEFAULT_BEATS;
-       float       bpm          = G_DEFAULT_BPM;
-       int         quantize     = G_DEFAULT_QUANTIZE;
-};
-
-struct Mixer
-{
-       struct State
-       {
-               std::atomic<bool> active  = false;
-               WeakAtomic<float> peakOut = 0.0f;
-               WeakAtomic<float> peakIn  = 0.0f;
-       };
-
-       State* state    = nullptr;
-       bool   hasSolos = false;
-       bool   inToOut  = false;
-};
-
 struct Layout
 {
-       channel::Data&       getChannel(ID id);
-       const channel::Data& getChannel(ID id) const;
-
-       Clock    clock;
-       Mixer    mixer;
-       Kernel   kernel;
-       Recorder recorder;
-       MidiIn   midiIn;
+       Channel&       getChannel(ID id);
+       const Channel& getChannel(ID id) const;
 
-       std::vector<channel::Data> channels;
+       Sequencer            sequencer;
+       Mixer                mixer;
+       Recorder             recorder;
+       MidiIn               midiIn;
+       std::vector<Channel> channels;
 
        /* locked
        If locked, Mixer won't process channels. This is used to allow editing the 
@@ -120,18 +75,18 @@ struct Layout
        bool locked = false;
 };
 
-/* Lock
+/* LayoutLock
 Alias for a REALTIME scoped lock provided by the Swapper class. Use this in the
 real-time thread to lock the Layout. */
 
-using Lock = Swapper<Layout>::RtLock;
+using LayoutLock = mcl::AtomicSwapper<Layout>::RtLock;
 
 /* SwapType
 Type of Layout change. 
        Hard: the structure has changed (e.g. add a new channel);
        Soft: a property has changed (e.g. change volume);
        None: something has changed but we don't care. 
-Used by model listeners to determine the type of change that occured in the 
+Used by model listeners to determine the type of change that occurred in the 
 layout. */
 
 enum class SwapType
@@ -141,95 +96,125 @@ enum class SwapType
        NONE
 };
 
+#ifdef WITH_VST
+using PluginPtr = std::unique_ptr<Plugin>;
+#endif
+using WavePtr          = std::unique_ptr<Wave>;
+using ChannelSharedPtr = std::unique_ptr<Channel::Shared>;
+
+#ifdef WITH_VST
+using PluginPtrs = std::vector<PluginPtr>;
+#endif
+using WavePtrs          = std::vector<WavePtr>;
+using ChannelSharedPtrs = std::vector<ChannelSharedPtr>;
+
 /* -------------------------------------------------------------------------- */
 
-class DataLock
+class DataLock;
+class Model
 {
 public:
-       DataLock(SwapType t = SwapType::HARD);
-       ~DataLock();
+       Model();
 
-private:
-       SwapType m_swapType;
-};
-
-/* -------------------------------------------------------------------------- */
+       bool isLocked() const;
 
-/* init
-Initializes the internal layout. */
+       /* lockData
+       Returns a scoped locker DataLock object. Use this when you want to lock
+       the model: a locked model won't be processed by Mixer. */
 
-void init();
+       [[nodiscard]] DataLock lockData(SwapType t = SwapType::HARD);
 
-/* get
-Returns a reference to the NON-REALTIME layout structure. */
+       /* reser
+       Resets the internal layout to default. */
 
-Layout& get();
+       void reset();
 
-/* get_RT
-Returns a Lock object for REALTIME processing. Access layout by calling 
-Lock::get() method (returns ready-only Layout). */
+       /* get_RT
+       Returns a LayoutLock object for REALTIME processing. Access layout by 
+       calling LayoutLock::get() method (returns ready-only Layout). */
 
-Lock get_RT();
+       LayoutLock get_RT();
 
-/* swap
-Swap non-rt layout with the rt one. See 'SwapType' notes above. */
+       /* get
+       Returns a reference to the NON-REALTIME layout structure. */
 
-void swap(SwapType t);
+       Layout&       get();
+       const Layout& get() const;
 
-/* onSwap
-Registers an optional callback fired when the layout has been swapped. Useful 
-for listening to model changes. */
+       /* swap
+       Swap non-rt layout with the rt one. See 'SwapType' notes above. */
 
-void onSwap(std::function<void(SwapType)> f);
+       void swap(SwapType t);
 
-bool isLocked();
+       template <typename T>
+       T& getAllShared();
 
-/* -------------------------------------------------------------------------- */
+       /* findShared
+       Finds something in the shared data given an ID. Returns nullptr if the
+       object is not found. */
 
-/* Model utilities */
+       template <typename T>
+       T* findShared(ID id);
 
-#ifdef WITH_VST
-using PluginPtr = std::unique_ptr<Plugin>;
-#endif
-using WavePtr          = std::unique_ptr<Wave>;
-using ChannelBufferPtr = std::unique_ptr<channel::Buffer>;
-using ChannelStatePtr  = std::unique_ptr<channel::State>;
+       /* addShared
+       Adds some shared data (by moving it). */
 
-#ifdef WITH_VST
-using PluginPtrs = std::vector<PluginPtr>;
-#endif
-using WavePtrs          = std::vector<WavePtr>;
-using Actions           = recorder::ActionMap;
-using ChannelBufferPtrs = std::vector<ChannelBufferPtr>;
-using ChannelStatePtrs  = std::vector<ChannelStatePtr>;
+       template <typename T>
+       void addShared(T);
 
-// TODO - are ID-based objects still necessary?
+       template <typename T>
+       void removeShared(const T&);
 
-template <typename T>
-T& getAll();
+       /* backShared
+       Returns a reference to the last added shared item. */
 
-/* find
-Finds something (Plugins or Waves) given an ID. Returns nullptr if the object is
-not found. */
+       template <typename T>
+       T& backShared();
 
-template <typename T>
-T* find(ID id);
+       template <typename T>
+       void clearShared();
 
-template <typename T>
-void add(T);
+#ifdef G_DEBUG_MODE
+       void debug();
+#endif
 
-template <typename T>
-void remove(const T&);
+       /* onSwap
+       Optional callback fired when the layout has been swapped. Useful for 
+       listening to model changes. */
 
-template <typename T>
-T& back();
+       std::function<void(SwapType)> onSwap = nullptr;
 
-template <typename T>
-void clear();
+private:
+       struct Shared
+       {
+               Sequencer::Shared                             sequencerShared;
+               Mixer::Shared                                 mixerShared;
+               Recorder::Shared                              recorderShared;
+               std::vector<std::unique_ptr<Channel::Shared>> channelsShared;
 
-#ifdef G_DEBUG_MODE
-void debug();
+               std::vector<std::unique_ptr<Wave>> waves;
+               Actions::Map                       actions;
+#ifdef WITH_VST
+               std::vector<std::unique_ptr<Plugin>> plugins;
 #endif
+       };
+
+       mcl::AtomicSwapper<Layout> m_layout;
+       Shared                     m_shared;
+};
+
+/* -------------------------------------------------------------------------- */
+
+class DataLock
+{
+public:
+       DataLock(Model&, SwapType t);
+       ~DataLock();
+
+private:
+       Model&   m_model;
+       SwapType m_swapType;
+};
 } // namespace giada::m::model
 
 #endif
diff --git a/src/core/model/recorder.cpp b/src/core/model/recorder.cpp
new file mode 100644 (file)
index 0000000..d7835b6
--- /dev/null
@@ -0,0 +1,50 @@
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+#include "core/model/recorder.h"
+
+namespace giada::m::model
+{
+bool Recorder::a_isRecordingAction() const
+{
+       return shared->isRecordingAction.load();
+}
+
+bool Recorder::a_isRecordingInput() const
+{
+       return shared->isRecordingInput.load();
+}
+
+void Recorder::a_setRecordingAction(bool b) const
+{
+       shared->isRecordingAction.store(b);
+}
+
+void Recorder::a_setRecordingInput(bool b) const
+{
+       shared->isRecordingInput.store(b);
+}
+} // namespace giada::m::model
diff --git a/src/core/model/recorder.h b/src/core/model/recorder.h
new file mode 100644 (file)
index 0000000..d4112ce
--- /dev/null
@@ -0,0 +1,55 @@
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+#ifndef G_MODEL_RECORDER_H
+#define G_MODEL_RECORDER_H
+
+#include "core/weakAtomic.h"
+
+namespace giada::m::model
+{
+class Recorder
+{
+       friend class Model;
+
+public:
+       bool a_isRecordingAction() const;
+       bool a_isRecordingInput() const;
+       void a_setRecordingAction(bool) const;
+       void a_setRecordingInput(bool) const;
+
+private:
+       struct Shared
+       {
+               WeakAtomic<bool> isRecordingAction = false;
+               WeakAtomic<bool> isRecordingInput  = false;
+       };
+
+       Shared* shared = nullptr;
+};
+} // namespace giada::m::model
+
+#endif
diff --git a/src/core/model/sequencer.cpp b/src/core/model/sequencer.cpp
new file mode 100644 (file)
index 0000000..d2a5b2f
--- /dev/null
@@ -0,0 +1,84 @@
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+#include "core/model/sequencer.h"
+
+namespace giada::m::model
+{
+bool Sequencer::isActive() const
+{
+       return status == SeqStatus::RUNNING || status == SeqStatus::WAITING;
+}
+
+/* -------------------------------------------------------------------------- */
+
+bool Sequencer::canQuantize() const
+{
+       return quantize > 0 && status == SeqStatus::RUNNING;
+}
+
+/* -------------------------------------------------------------------------- */
+
+bool Sequencer::isRunning() const
+{
+       return status == SeqStatus::RUNNING;
+}
+
+/* -------------------------------------------------------------------------- */
+
+bool Sequencer::a_isOnBar() const
+{
+       const int currentFrame = shared->currentFrame.load();
+
+       if (status == SeqStatus::WAITING || currentFrame == 0)
+               return false;
+       return currentFrame % framesInBar == 0;
+}
+
+/* -------------------------------------------------------------------------- */
+
+bool Sequencer::a_isOnBeat() const
+{
+       return shared->currentFrame.load() % framesInBeat == 0;
+}
+
+/* -------------------------------------------------------------------------- */
+
+bool Sequencer::a_isOnFirstBeat() const
+{
+       return shared->currentFrame.load() == 0;
+}
+
+/* -------------------------------------------------------------------------- */
+
+Frame Sequencer::a_getCurrentFrame() const { return shared->currentFrame.load(); }
+Frame Sequencer::a_getCurrentBeat() const { return shared->currentBeat.load(); }
+
+/* -------------------------------------------------------------------------- */
+
+void Sequencer::a_setCurrentFrame(Frame f) const { shared->currentFrame.store(f); }
+void Sequencer::a_setCurrentBeat(Frame f) const { shared->currentBeat.store(f); }
+} // namespace giada::m::model
diff --git a/src/core/model/sequencer.h b/src/core/model/sequencer.h
new file mode 100644 (file)
index 0000000..5526f3b
--- /dev/null
@@ -0,0 +1,89 @@
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+#ifndef G_MODEL_SEQUENCER_H
+#define G_MODEL_SEQUENCER_H
+
+#include "core/const.h"
+#include "core/types.h"
+#include "core/weakAtomic.h"
+#include "deps/mcl-audio-buffer/src/audioBuffer.hpp"
+
+namespace giada::m::model
+{
+class Sequencer
+{
+       friend class Model;
+
+public:
+       /* isRunning
+    When sequencer is actually moving forward, i.e. SeqStatus == RUNNING. */
+
+       bool isRunning() const;
+
+       /* isActive
+    Sequencer is enabled, but might be in wait mode, i.e. SeqStatus == RUNNING or
+    SeqStatus == WAITING. */
+
+       bool isActive() const;
+
+       /* canQuantize
+       Sequencer can quantize only if it's running and quantizer is enabled. */
+
+       bool canQuantize() const;
+
+       bool a_isOnBar() const;
+       bool a_isOnBeat() const;
+       bool a_isOnFirstBeat() const;
+
+       Frame a_getCurrentFrame() const;
+       Frame a_getCurrentBeat() const;
+
+       void a_setCurrentFrame(Frame) const;
+       void a_setCurrentBeat(Frame) const;
+
+       SeqStatus status       = SeqStatus::STOPPED;
+       int       framesInLoop = 0;
+       int       framesInBar  = 0;
+       int       framesInBeat = 0;
+       int       framesInSeq  = 0;
+       int       bars         = G_DEFAULT_BARS;
+       int       beats        = G_DEFAULT_BEATS;
+       float     bpm          = G_DEFAULT_BPM;
+       int       quantize     = G_DEFAULT_QUANTIZE;
+
+private:
+       struct Shared
+       {
+               WeakAtomic<Frame> currentFrame = 0;
+               WeakAtomic<Frame> currentBeat  = 0;
+       };
+
+       Shared* shared = nullptr;
+};
+} // namespace giada::m::model
+
+#endif
index 15eb15be8c784b3f95a95a2282f41e07206658e4..d2e5e7366a28922d4668a8e539f5d69673bdf6b4 100644 (file)
 #include "core/model/storage.h"
 #include "core/channels/channelManager.h"
 #include "core/conf.h"
+#include "core/engine.h"
 #include "core/kernelAudio.h"
 #include "core/model/model.h"
 #include "core/patch.h"
 #include "core/plugins/pluginManager.h"
-#include "core/recorderHandler.h"
 #include "core/sequencer.h"
 #include "core/waveManager.h"
+#include "src/core/actions/actionRecorder.h"
 #include <cassert>
 
+extern giada::m::Engine g_engine;
+
 namespace giada::m::model
 {
 namespace
 {
-void loadChannels_(const std::vector<patch::Channel>& channels, int samplerate)
+void loadChannels_(const std::vector<Patch::Channel>& channels, int samplerate)
 {
-       float samplerateRatio = conf::conf.samplerate / static_cast<float>(samplerate);
+       float samplerateRatio = g_engine.kernelAudio.getSampleRate() / static_cast<float>(samplerate);
 
-       for (const patch::Channel& pchannel : channels)
-               get().channels.push_back(channelManager::deserializeChannel(pchannel, samplerateRatio));
+       for (const Patch::Channel& pchannel : channels)
+               g_engine.model.get().channels.push_back(
+                   g_engine.channelManager.deserializeChannel(pchannel, samplerateRatio, g_engine.kernelAudio.getBufferSize()));
 }
 
 /* -------------------------------------------------------------------------- */
 
-void loadActions_(const std::vector<patch::Action>& pactions)
+void loadActions_(const std::vector<Patch::Action>& pactions)
 {
-       getAll<Actions>() = std::move(recorderHandler::deserializeActions(pactions));
+       g_engine.model.getAllShared<Actions::Map>() = std::move(g_engine.actionRecorder.deserializeActions(pactions));
 }
 } // namespace
 
@@ -60,36 +64,39 @@ void loadActions_(const std::vector<patch::Action>& pactions)
 /* -------------------------------------------------------------------------- */
 /* -------------------------------------------------------------------------- */
 
-void store(patch::Patch& patch)
+void store(Patch::Data& patch)
 {
-       const Layout& layout = get();
+       const Layout& layout = g_engine.model.get();
 
-       patch.bars       = layout.clock.bars;
-       patch.beats      = layout.clock.beats;
-       patch.bpm        = layout.clock.bpm;
-       patch.quantize   = layout.clock.quantize;
-       patch.metronome  = sequencer::isMetronomeOn(); // TODO - add bool metronome to Layout
-       patch.samplerate = conf::conf.samplerate;
+       patch.bars       = layout.sequencer.bars;
+       patch.beats      = layout.sequencer.beats;
+       patch.bpm        = layout.sequencer.bpm;
+       patch.quantize   = layout.sequencer.quantize;
+       patch.metronome  = g_engine.sequencer.isMetronomeOn(); // TODO - addShared bool metronome to Layout
+       patch.samplerate = g_engine.kernelAudio.getSampleRate();
 
 #ifdef WITH_VST
-       for (const auto& p : getAll<PluginPtrs>())
-               patch.plugins.push_back(pluginManager::serializePlugin(*p));
+       patch.plugins.clear();
+       for (const auto& p : g_engine.model.getAllShared<PluginPtrs>())
+               patch.plugins.push_back(g_engine.pluginManager.serializePlugin(*p));
 #endif
 
-       patch.actions = recorderHandler::serializeActions(getAll<Actions>());
+       patch.actions = g_engine.actionRecorder.serializeActions(g_engine.model.getAllShared<Actions::Map>());
 
-       for (const auto& w : getAll<WavePtrs>())
-               patch.waves.push_back(waveManager::serializeWave(*w));
+       patch.waves.clear();
+       for (const auto& w : g_engine.model.getAllShared<WavePtrs>())
+               patch.waves.push_back(g_engine.waveManager.serializeWave(*w));
 
-       for (const channel::Data& c : layout.channels)
-               patch.channels.push_back(channelManager::serializeChannel(c));
+       patch.channels.clear();
+       for (const Channel& c : layout.channels)
+               patch.channels.push_back(g_engine.channelManager.serializeChannel(c));
 }
 
 /* -------------------------------------------------------------------------- */
 
-void store(conf::Conf& conf)
+void store(Conf::Data& conf)
 {
-       const Layout& layout = get();
+       const Layout& layout = g_engine.model.get();
 
        conf.midiInEnabled    = layout.midiIn.enabled;
        conf.midiInFilter     = layout.midiIn.filter;
@@ -106,61 +113,61 @@ void store(conf::Conf& conf)
 
 /* -------------------------------------------------------------------------- */
 
-void load(const patch::Patch& patch)
+void load(const Patch::Data& patch)
 {
-       DataLock lock;
+       DataLock lock = g_engine.model.lockData();
 
        /* Clear and re-initialize channels first. */
 
-       get().channels = {};
-       getAll<ChannelBufferPtrs>().clear();
-       getAll<ChannelStatePtrs>().clear();
+       g_engine.model.get().channels = {};
+       g_engine.model.getAllShared<ChannelSharedPtrs>().clear();
 
        /* Load external data first: plug-ins and waves. */
 
 #ifdef WITH_VST
-       getAll<PluginPtrs>().clear();
-       for (const patch::Plugin& pplugin : patch.plugins)
-               getAll<PluginPtrs>().push_back(pluginManager::deserializePlugin(pplugin, patch.version));
+       g_engine.model.getAllShared<PluginPtrs>().clear();
+       for (const Patch::Plugin& pplugin : patch.plugins)
+               g_engine.model.getAllShared<PluginPtrs>().push_back(g_engine.pluginManager.deserializePlugin(
+                   pplugin, patch.version, g_engine.kernelAudio.getSampleRate(), g_engine.kernelAudio.getBufferSize(), g_engine.sequencer));
 #endif
 
-       getAll<WavePtrs>().clear();
-       for (const patch::Wave& pwave : patch.waves)
+       g_engine.model.getAllShared<WavePtrs>().clear();
+       for (const Patch::Wave& pwave : patch.waves)
        {
-               std::unique_ptr<Wave> w = waveManager::deserializeWave(pwave, conf::conf.samplerate,
-                   conf::conf.rsmpQuality);
+               std::unique_ptr<Wave> w = g_engine.waveManager.deserializeWave(pwave, g_engine.kernelAudio.getSampleRate(),
+                   g_engine.conf.data.rsmpQuality);
                if (w != nullptr)
-                       getAll<WavePtrs>().push_back(std::move(w));
+                       g_engine.model.getAllShared<WavePtrs>().push_back(std::move(w));
        }
 
        /* Then load up channels, actions and global properties. */
 
-       loadChannels_(patch.channels, patch::patch.samplerate);
+       loadChannels_(patch.channels, g_engine.patch.data.samplerate);
        loadActions_(patch.actions);
 
-       get().clock.status   = ClockStatus::STOPPED;
-       get().clock.bars     = patch.bars;
-       get().clock.beats    = patch.beats;
-       get().clock.bpm      = patch.bpm;
-       get().clock.quantize = patch.quantize;
+       g_engine.model.get().sequencer.status   = SeqStatus::STOPPED;
+       g_engine.model.get().sequencer.bars     = patch.bars;
+       g_engine.model.get().sequencer.beats    = patch.beats;
+       g_engine.model.get().sequencer.bpm      = patch.bpm;
+       g_engine.model.get().sequencer.quantize = patch.quantize;
 }
 
 /* -------------------------------------------------------------------------- */
 
-void load(const conf::Conf& c)
+void load(const Conf::Data& c)
 {
-       get().midiIn.enabled    = c.midiInEnabled;
-       get().midiIn.filter     = c.midiInFilter;
-       get().midiIn.rewind     = c.midiInRewind;
-       get().midiIn.startStop  = c.midiInStartStop;
-       get().midiIn.actionRec  = c.midiInActionRec;
-       get().midiIn.inputRec   = c.midiInInputRec;
-       get().midiIn.volumeIn   = c.midiInVolumeIn;
-       get().midiIn.volumeOut  = c.midiInVolumeOut;
-       get().midiIn.beatDouble = c.midiInBeatDouble;
-       get().midiIn.beatHalf   = c.midiInBeatHalf;
-       get().midiIn.metronome  = c.midiInMetronome;
-
-       swap(SwapType::NONE);
+       g_engine.model.get().midiIn.enabled    = c.midiInEnabled;
+       g_engine.model.get().midiIn.filter     = c.midiInFilter;
+       g_engine.model.get().midiIn.rewind     = c.midiInRewind;
+       g_engine.model.get().midiIn.startStop  = c.midiInStartStop;
+       g_engine.model.get().midiIn.actionRec  = c.midiInActionRec;
+       g_engine.model.get().midiIn.inputRec   = c.midiInInputRec;
+       g_engine.model.get().midiIn.volumeIn   = c.midiInVolumeIn;
+       g_engine.model.get().midiIn.volumeOut  = c.midiInVolumeOut;
+       g_engine.model.get().midiIn.beatDouble = c.midiInBeatDouble;
+       g_engine.model.get().midiIn.beatHalf   = c.midiInBeatHalf;
+       g_engine.model.get().midiIn.metronome  = c.midiInMetronome;
+
+       g_engine.model.swap(SwapType::NONE);
 }
 } // namespace giada::m::model
index c163abed5f5186d5daf0135460c797e1a7772ba0..e69d14095b0f31869c7ca1b5e518432a28ce5273 100644 (file)
 #ifndef G_MODEL_STORAGE_H
 #define G_MODEL_STORAGE_H
 
-namespace giada::m::patch
-{
-struct Patch;
-}
-namespace giada::m::conf
-{
-struct Conf;
-}
+#include "core/patch.h"
+#include "core/conf.h"
+
 namespace giada::m::model
 {
-void store(conf::Conf& c);
-void store(patch::Patch& p);
-void load(const patch::Patch& p);
-void load(const conf::Conf& c);
+void store(Conf::Data& c);
+void store(Patch::Data& p);
+void load(const Patch::Data& p);
+void load(const Conf::Data& c);
 } // namespace giada::m::model
 
 #endif
index 4ababcda88fee8a9f35e1bd0b88b197119ba5115..2db30e4d71bf6a3918d7f006d494c8164b6d4248 100644 (file)
 
 namespace nl = nlohmann;
 
-namespace giada
-{
-namespace m
-{
-namespace patch
+namespace giada::m
 {
 namespace
 {
-void readCommons_(const nl::json& j)
+void readCommons_(Patch::Data& patch, const nl::json& j)
 {
        patch.name       = j.value(PATCH_KEY_NAME, G_DEFAULT_PATCH_NAME);
        patch.bars       = j.value(PATCH_KEY_BARS, G_DEFAULT_BARS);
@@ -55,12 +51,12 @@ void readCommons_(const nl::json& j)
 
 /* -------------------------------------------------------------------------- */
 
-void readColumns_(const nl::json& j)
+void readColumns_(Patch::Data& patch, const nl::json& j)
 {
        ID id = 0;
        for (const auto& jcol : j[PATCH_KEY_COLUMNS])
        {
-               Column c;
+               Patch::Column c;
                c.id    = jcol.value(PATCH_KEY_COLUMN_ID, ++id);
                c.width = jcol.value(PATCH_KEY_COLUMN_WIDTH, G_DEFAULT_COLUMN_WIDTH);
                patch.columns.push_back(c);
@@ -71,7 +67,7 @@ void readColumns_(const nl::json& j)
 
 #ifdef WITH_VST
 
-void readPlugins_(const nl::json& j)
+void readPlugins_(Patch::Data& patch, const nl::json& j)
 {
        if (!j.contains(PATCH_KEY_PLUGINS))
                return;
@@ -79,12 +75,12 @@ void readPlugins_(const nl::json& j)
        ID id = 0;
        for (const auto& jplugin : j[PATCH_KEY_PLUGINS])
        {
-               Plugin p;
+               Patch::Plugin p;
                p.id     = jplugin.value(PATCH_KEY_PLUGIN_ID, ++id);
                p.path   = jplugin.value(PATCH_KEY_PLUGIN_PATH, "");
                p.bypass = jplugin.value(PATCH_KEY_PLUGIN_BYPASS, false);
 
-               if (patch.version < Version{0, 17, 0})
+               if (patch.version < Patch::Version{0, 17, 0})
                        for (const auto& jparam : jplugin[PATCH_KEY_PLUGIN_PARAMS])
                                p.params.push_back(jparam);
                else
@@ -101,7 +97,7 @@ void readPlugins_(const nl::json& j)
 
 /* -------------------------------------------------------------------------- */
 
-void readWaves_(const nl::json& j, const std::string& basePath)
+void readWaves_(Patch::Data& patch, const nl::json& j, const std::string& basePath)
 {
        if (!j.contains(PATCH_KEY_WAVES))
                return;
@@ -109,16 +105,16 @@ void readWaves_(const nl::json& j, const std::string& basePath)
        ID id = 0;
        for (const auto& jwave : j[PATCH_KEY_WAVES])
        {
-               Wave w;
+               Patch::Wave w;
                w.id   = jwave.value(PATCH_KEY_WAVE_ID, ++id);
-               w.path = basePath + jwave.value(PATCH_KEY_WAVE_PATH, "");
+               w.path = basePath + G_SLASH + jwave.value(PATCH_KEY_WAVE_PATH, "");
                patch.waves.push_back(w);
        }
 }
 
 /* -------------------------------------------------------------------------- */
 
-void readActions_(const nl::json& j)
+void readActions_(Patch::Data& patch, const nl::json& j)
 {
        if (!j.contains(PATCH_KEY_ACTIONS))
                return;
@@ -126,7 +122,7 @@ void readActions_(const nl::json& j)
        ID id = 0;
        for (const auto& jaction : j[PATCH_KEY_ACTIONS])
        {
-               Action a;
+               Patch::Action a;
                a.id        = jaction.value(G_PATCH_KEY_ACTION_ID, ++id);
                a.channelId = jaction.value(G_PATCH_KEY_ACTION_CHANNEL, 0);
                a.frame     = jaction.value(G_PATCH_KEY_ACTION_FRAME, 0);
@@ -139,16 +135,16 @@ void readActions_(const nl::json& j)
 
 /* -------------------------------------------------------------------------- */
 
-void readChannels_(const nl::json& j)
+void readChannels_(Patch::Data& patch, const nl::json& j)
 {
        if (!j.contains(PATCH_KEY_CHANNELS))
                return;
 
-       ID defaultId = mixer::PREVIEW_CHANNEL_ID;
+       ID defaultId = Mixer::PREVIEW_CHANNEL_ID;
 
        for (const auto& jchannel : j[PATCH_KEY_CHANNELS])
        {
-               Channel c;
+               Patch::Channel c;
                c.id                = jchannel.value(PATCH_KEY_CHANNEL_ID, ++defaultId);
                c.type              = static_cast<ChannelType>(jchannel.value(PATCH_KEY_CHANNEL_TYPE, 1));
                c.volume            = jchannel.value(PATCH_KEY_CHANNEL_VOLUME, G_DEFAULT_VOL);
@@ -203,11 +199,11 @@ void readChannels_(const nl::json& j)
 
 #ifdef WITH_VST
 
-void writePlugins_(nl::json& j)
+void writePlugins_(const Patch::Data& patch, nl::json& j)
 {
        j[PATCH_KEY_PLUGINS] = nl::json::array();
 
-       for (const Plugin& p : patch.plugins)
+       for (const Patch::Plugin& p : patch.plugins)
        {
 
                nl::json jplugin;
@@ -229,11 +225,11 @@ void writePlugins_(nl::json& j)
 
 /* -------------------------------------------------------------------------- */
 
-void writeColumns_(nl::json& j)
+void writeColumns_(const Patch::Data& patch, nl::json& j)
 {
        j[PATCH_KEY_COLUMNS] = nl::json::array();
 
-       for (const Column& column : patch.columns)
+       for (const Patch::Column& column : patch.columns)
        {
                nl::json jcolumn;
                jcolumn[PATCH_KEY_COLUMN_ID]    = column.id;
@@ -244,11 +240,11 @@ void writeColumns_(nl::json& j)
 
 /* -------------------------------------------------------------------------- */
 
-void writeActions_(nl::json& j)
+void writeActions_(const Patch::Data& patch, nl::json& j)
 {
        j[PATCH_KEY_ACTIONS] = nl::json::array();
 
-       for (const Action& a : patch.actions)
+       for (const Patch::Action& a : patch.actions)
        {
                nl::json jaction;
                jaction[G_PATCH_KEY_ACTION_ID]      = a.id;
@@ -263,11 +259,11 @@ void writeActions_(nl::json& j)
 
 /* -------------------------------------------------------------------------- */
 
-void writeWaves_(nl::json& j)
+void writeWaves_(const Patch::Data& patch, nl::json& j)
 {
        j[PATCH_KEY_WAVES] = nl::json::array();
 
-       for (const Wave& w : patch.waves)
+       for (const Patch::Wave& w : patch.waves)
        {
                nl::json jwave;
                jwave[PATCH_KEY_WAVE_ID]   = w.id;
@@ -279,7 +275,7 @@ void writeWaves_(nl::json& j)
 
 /* -------------------------------------------------------------------------- */
 
-void writeCommons_(nl::json& j)
+void writeCommons_(const Patch::Data& patch, nl::json& j)
 {
        j[PATCH_KEY_HEADER]        = "GIADAPTC";
        j[PATCH_KEY_VERSION_MAJOR] = G_VERSION_MAJOR;
@@ -297,13 +293,12 @@ void writeCommons_(nl::json& j)
 
 /* -------------------------------------------------------------------------- */
 
-void writeChannels_(nl::json& j)
+void writeChannels_(const Patch::Data& patch, nl::json& j)
 {
        j[PATCH_KEY_CHANNELS] = nl::json::array();
 
-       for (const Channel& c : patch.channels)
+       for (const Patch::Channel& c : patch.channels)
        {
-
                nl::json jchannel;
 
                jchannel[PATCH_KEY_CHANNEL_ID]                   = c.id;
@@ -358,16 +353,16 @@ void writeChannels_(nl::json& j)
 
 /* -------------------------------------------------------------------------- */
 
-void modernize_()
+void modernize_(Patch::Data& patch)
 {
-       for (Channel& c : patch.channels)
+       for (Patch::Channel& c : patch.channels)
        {
                /* 0.16.3
                Make sure that ChannelType is correct: ID 1, 2 are MASTER channels, ID 3 
                is PREVIEW channel. */
-               if (c.id == mixer::MASTER_OUT_CHANNEL_ID || c.id == mixer::MASTER_IN_CHANNEL_ID)
+               if (c.id == Mixer::MASTER_OUT_CHANNEL_ID || c.id == Mixer::MASTER_IN_CHANNEL_ID)
                        c.type = ChannelType::MASTER;
-               else if (c.id == mixer::PREVIEW_CHANNEL_ID)
+               else if (c.id == Mixer::PREVIEW_CHANNEL_ID)
                        c.type = ChannelType::PREVIEW;
 
                /* 0.16.4
@@ -390,16 +385,12 @@ void modernize_()
 /* -------------------------------------------------------------------------- */
 /* -------------------------------------------------------------------------- */
 
-Patch patch;
-
-/* -------------------------------------------------------------------------- */
-
-bool Version::operator==(const Version& o) const
+bool Patch::Version::operator==(const Version& o) const
 {
        return major == o.major && minor == o.minor && patch == o.patch;
 }
 
-bool Version::operator<(const Version& o) const
+bool Patch::Version::operator<(const Version& o) const
 {
        if (major < o.major)
                return true;
@@ -410,26 +401,28 @@ bool Version::operator<(const Version& o) const
        return false;
 }
 
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
 /* -------------------------------------------------------------------------- */
 
-void init()
+void Patch::reset()
 {
-       patch = Patch();
+       data = Data();
 }
 
 /* -------------------------------------------------------------------------- */
 
-bool write(const std::string& file)
+bool Patch::write(const std::string& file) const
 {
        nl::json j;
 
-       writeCommons_(j);
-       writeColumns_(j);
-       writeChannels_(j);
-       writeActions_(j);
-       writeWaves_(j);
+       writeCommons_(data, j);
+       writeColumns_(data, j);
+       writeChannels_(data, j);
+       writeActions_(data, j);
+       writeWaves_(data, j);
 #ifdef WITH_VST
-       writePlugins_(j);
+       writePlugins_(data, j);
 #endif
 
        std::ofstream ofs(file);
@@ -442,7 +435,7 @@ bool write(const std::string& file)
 
 /* -------------------------------------------------------------------------- */
 
-int read(const std::string& file, const std::string& basePath)
+int Patch::read(const std::string& file, const std::string& basePath)
 {
        std::ifstream ifs(file);
        if (!ifs.good())
@@ -453,24 +446,24 @@ int read(const std::string& file, const std::string& basePath)
        if (j[PATCH_KEY_HEADER] != "GIADAPTC")
                return G_PATCH_INVALID;
 
-       patch.version = {
+       data.version = {
            static_cast<int>(j[PATCH_KEY_VERSION_MAJOR]),
            static_cast<int>(j[PATCH_KEY_VERSION_MINOR]),
            static_cast<int>(j[PATCH_KEY_VERSION_PATCH])};
-       if (patch.version < Version{0, 16, 0})
+       if (data.version < Version{0, 16, 0})
                return G_PATCH_UNSUPPORTED;
 
        try
        {
-               readCommons_(j);
-               readColumns_(j);
+               readCommons_(data, j);
+               readColumns_(data, j);
 #ifdef WITH_VST
-               readPlugins_(j);
+               readPlugins_(data, j);
 #endif
-               readWaves_(j, basePath);
-               readActions_(j);
-               readChannels_(j);
-               modernize_();
+               readWaves_(data, j, basePath);
+               readActions_(data, j);
+               readChannels_(data, j);
+               modernize_(data);
        }
        catch (nl::json::exception& e)
        {
@@ -480,6 +473,4 @@ int read(const std::string& file, const std::string& basePath)
 
        return G_PATCH_OK;
 }
-} // namespace patch
-} // namespace m
-} // namespace giada
+} // namespace giada::m
index 0a28cabf29cfa00106ce5186c022262d9de1e824..a9e22f17359b56ca586009ffce0f9d6801b13f6d 100644 (file)
 #include <string>
 #include <vector>
 
-namespace giada
+namespace giada::m
 {
-namespace m
+class Patch
 {
-namespace patch
-{
-struct Version
-{
-       int major = G_VERSION_MAJOR;
-       int minor = G_VERSION_MINOR;
-       int patch = G_VERSION_PATCH;
-
-       bool operator==(const Version& o) const;
-       bool operator<(const Version& o) const;
-};
-
-struct Column
-{
-       ID  id;
-       int width;
-};
-
-struct Channel
-{
-       ID          id;
-       ChannelType type;
-       int         height;
-       std::string name;
-       ID          columnId;
-       int         key;
-       bool        mute;
-       bool        solo;
-       float       volume = G_DEFAULT_VOL;
-       float       pan    = G_DEFAULT_PAN;
-       bool        hasActions;
-       bool        armed;
-       bool        midiIn;
-       uint32_t    midiInKeyPress;
-       uint32_t    midiInKeyRel;
-       uint32_t    midiInKill;
-       uint32_t    midiInArm;
-       uint32_t    midiInVolume;
-       uint32_t    midiInMute;
-       uint32_t    midiInSolo;
-       int         midiInFilter;
-       bool        midiOutL;
-       uint32_t    midiOutLplaying;
-       uint32_t    midiOutLmute;
-       uint32_t    midiOutLsolo;
-       // sample channel
-       ID               waveId = 0;
-       SamplePlayerMode mode;
-       Frame            begin;
-       Frame            end;
-       Frame            shift;
-       bool             readActions;
-       float            pitch = G_DEFAULT_PITCH;
-       bool             inputMonitor;
-       bool             overdubProtection;
-       bool             midiInVeloAsVol;
-       uint32_t         midiInReadActions;
-       uint32_t         midiInPitch;
-       // midi channel
-       bool midiOut;
-       int  midiOutChan;
+public:
+       struct Version
+       {
+               int major = G_VERSION_MAJOR;
+               int minor = G_VERSION_MINOR;
+               int patch = G_VERSION_PATCH;
+
+               bool operator==(const Version& o) const;
+               bool operator<(const Version& o) const;
+       };
+
+       struct Column
+       {
+               ID  id;
+               int width;
+       };
+
+       struct Channel
+       {
+               ID          id;
+               ChannelType type;
+               int         height;
+               std::string name;
+               ID          columnId;
+               int         key;
+               bool        mute;
+               bool        solo;
+               float       volume = G_DEFAULT_VOL;
+               float       pan    = G_DEFAULT_PAN;
+               bool        hasActions;
+               bool        armed;
+               bool        midiIn;
+               uint32_t    midiInKeyPress;
+               uint32_t    midiInKeyRel;
+               uint32_t    midiInKill;
+               uint32_t    midiInArm;
+               uint32_t    midiInVolume;
+               uint32_t    midiInMute;
+               uint32_t    midiInSolo;
+               int         midiInFilter;
+               bool        midiOutL;
+               uint32_t    midiOutLplaying;
+               uint32_t    midiOutLmute;
+               uint32_t    midiOutLsolo;
+               // sample channel
+               ID               waveId = 0;
+               SamplePlayerMode mode;
+               Frame            begin;
+               Frame            end;
+               Frame            shift;
+               bool             readActions;
+               float            pitch = G_DEFAULT_PITCH;
+               bool             inputMonitor;
+               bool             overdubProtection;
+               bool             midiInVeloAsVol;
+               uint32_t         midiInReadActions;
+               uint32_t         midiInPitch;
+               // midi channel
+               bool midiOut;
+               int  midiOutChan;
 #ifdef WITH_VST
-       std::vector<ID> pluginIds;
+               std::vector<ID> pluginIds;
 #endif
-};
-
-struct Action
-{
-       ID       id;
-       ID       channelId;
-       Frame    frame;
-       uint32_t event;
-       ID       prevId;
-       ID       nextId;
-};
-
-struct Wave
-{
-       ID          id;
-       std::string path;
-};
+       };
+
+       struct Action
+       {
+               ID       id;
+               ID       channelId;
+               Frame    frame;
+               uint32_t event;
+               ID       prevId;
+               ID       nextId;
+       };
+
+       struct Wave
+       {
+               ID          id;
+               std::string path;
+       };
 
 #ifdef WITH_VST
-struct Plugin
-{
-       ID                    id;
-       std::string           path;
-       bool                  bypass;
-       std::vector<float>    params; // TODO - to be removed in 0.18.0
-       std::string           state;
-       std::vector<uint32_t> midiInParams;
-};
+       struct Plugin
+       {
+               ID                    id;
+               std::string           path;
+               bool                  bypass;
+               std::vector<float>    params; // TODO - to be removed in 0.18.0
+               std::string           state;
+               std::vector<uint32_t> midiInParams;
+       };
 #endif
 
-struct Patch
-{
-       Version     version;
-       std::string name       = G_DEFAULT_PATCH_NAME;
-       int         bars       = G_DEFAULT_BARS;
-       int         beats      = G_DEFAULT_BEATS;
-       float       bpm        = G_DEFAULT_BPM;
-       bool        quantize   = G_DEFAULT_QUANTIZE;
-       int         lastTakeId = 0;
-       int         samplerate = G_DEFAULT_SAMPLERATE;
-       bool        metronome  = false;
-
-       std::vector<Column>  columns;
-       std::vector<Channel> channels;
-       std::vector<Action>  actions;
-       std::vector<Wave>    waves;
+       struct Data
+       {
+               Version     version;
+               std::string name       = G_DEFAULT_PATCH_NAME;
+               int         bars       = G_DEFAULT_BARS;
+               int         beats      = G_DEFAULT_BEATS;
+               float       bpm        = G_DEFAULT_BPM;
+               bool        quantize   = G_DEFAULT_QUANTIZE;
+               int         lastTakeId = 0;
+               int         samplerate = G_DEFAULT_SAMPLERATE;
+               bool        metronome  = false;
+
+               std::vector<Column>  columns;
+               std::vector<Channel> channels;
+               std::vector<Action>  actions;
+               std::vector<Wave>    waves;
 #ifdef WITH_VST
-       std::vector<Plugin> plugins;
+               std::vector<Plugin> plugins;
 #endif
-};
-
-/* -------------------------------------------------------------------------- */
+       };
 
-extern Patch patch;
+       /* write
+       Writes patch to file. */
 
-/* -------------------------------------------------------------------------- */
+       bool write(const std::string& file) const;
 
-/* init
-Initializes the patch with default values. */
+       /* reset
+       Initializes the patch with default values. */
 
-void init();
+       void reset();
 
-/* read
-Reads patch from file. It takes 'basePath' as parameter for Wave reading. */
+       /* read
+       Reads patch from file. It takes 'basePath' as parameter for Wave reading. */
 
-int read(const std::string& file, const std::string& basePath);
+       int read(const std::string& file, const std::string& basePath);
 
-/* write
-Writes patch to file. */
-
-bool write(const std::string& file);
-} // namespace patch
-} // namespace m
-} // namespace giada
+       Data data;
+};
+} // namespace giada::m
 
 #endif
index 5e7011261c03ae1090a6f8ce870f69d0a92a2066..31f75c408ad0aab74f6c8f6be7d6d08a746ea08b 100644 (file)
@@ -26,9 +26,8 @@
 
 #ifdef WITH_VST
 
-#include "plugin.h"
+#include "core/plugins/plugin.h"
 #include "core/const.h"
-#include "core/plugins/pluginManager.h"
 #include "utils/log.h"
 #include "utils/time.h"
 #include <FL/Fl.H>
@@ -48,13 +47,13 @@ Plugin::Plugin(ID id, const std::string& UID)
 
 /* -------------------------------------------------------------------------- */
 
-Plugin::Plugin(ID id, std::unique_ptr<juce::AudioPluginInstance> plugin, double samplerate,
-    int buffersize)
+Plugin::Plugin(ID id, std::unique_ptr<juce::AudioPluginInstance> plugin,
+    std::unique_ptr<PluginHost::Info> playHead, double samplerate, int buffersize)
 : id(id)
 , valid(true)
 , onEditorResize(nullptr)
 , m_plugin(std::move(plugin))
-, m_playHead(std::make_unique<pluginHost::Info>())
+, m_playHead(std::move(playHead))
 , m_bypass(false)
 , m_hasEditor(m_plugin->hasEditor())
 {
@@ -90,18 +89,6 @@ Plugin::Plugin(ID id, std::unique_ptr<juce::AudioPluginInstance> plugin, double
 
 /* -------------------------------------------------------------------------- */
 
-Plugin::Plugin(const Plugin& o)
-: id(o.id)
-, midiInParams(o.midiInParams)
-, valid(o.valid)
-, onEditorResize(o.onEditorResize)
-, m_plugin(std::move(pluginManager::makePlugin(o)->m_plugin))
-, m_bypass(o.m_bypass.load())
-{
-}
-
-/* -------------------------------------------------------------------------- */
-
 Plugin::~Plugin()
 {
        if (!valid)
index 665d2bbbff046a1bfa36b26c97b669c6974a8302..d65136b8c45860d3d2d8264ad2e6ee192e909297 100644 (file)
@@ -41,11 +41,23 @@ namespace giada::m
 class Plugin : private juce::ComponentListener
 {
 public:
+       /* Plugin (1)
+       Constructs an invalid plug-in. */
+
        Plugin(ID id, const std::string& UID);
-       Plugin(ID id, std::unique_ptr<juce::AudioPluginInstance> p, double samplerate, int buffersize);
-       Plugin(const Plugin& o);
+
+       /* Plugin (2)
+       Constructs a valid and working plug-in. */
+
+       Plugin(ID  id, std::unique_ptr<juce::AudioPluginInstance>, std::unique_ptr<PluginHost::Info>,
+           double samplerate, int buffersize);
+
+       Plugin(const Plugin& o) = delete;
+       Plugin(Plugin&& o)      = delete;
+       Plugin& operator=(const Plugin&) = delete;
+       Plugin& operator=(Plugin&&) = delete;
+
        ~Plugin();
-       /* TODO - mark is as non-copiable/movable*/
 
        /* getUniqueId
        Returns a string-based UID. */
@@ -124,7 +136,7 @@ private:
        int countMainOutChannels() const;
 
        std::unique_ptr<juce::AudioPluginInstance> m_plugin;
-       std::unique_ptr<pluginHost::Info>          m_playHead;
+       std::unique_ptr<PluginHost::Info>          m_playHead;
        juce::AudioBuffer<float>                   m_buffer;
 
        std::atomic<bool> m_bypass;
index 4ffdbed37d33663b84c6819d938d14bce1710378..6533ad1c88169c92e1b7a9637bafafa55e4bd941 100644 (file)
 
 #include "core/plugins/pluginHost.h"
 #include "core/channels/channel.h"
-#include "core/clock.h"
 #include "core/const.h"
 #include "core/model/model.h"
 #include "core/plugins/plugin.h"
 #include "core/plugins/pluginManager.h"
+#include "deps/mcl-audio-buffer/src/audioBuffer.hpp"
 #include "utils/log.h"
 #include "utils/vector.h"
 #include <cassert>
 
-namespace giada::m::pluginHost
+namespace giada::m
 {
-namespace
+PluginHost::Info::Info(const Sequencer& s, int sampleRate)
+: m_sequencer(s)
+, m_sampleRate(sampleRate)
 {
-std::vector<Plugin*>     plugins_;
-juce::MessageManager*    messageManager_;
-juce::AudioBuffer<float> audioBuffer_;
-ID                       pluginId_;
-
-/* -------------------------------------------------------------------------- */
-
-void giadaToJuceTempBuf_(const AudioBuffer& outBuf)
-{
-       for (int i = 0; i < outBuf.countFrames(); i++)
-               for (int j = 0; j < outBuf.countChannels(); j++)
-                       audioBuffer_.setSample(j, i, outBuf[i][j]);
-}
-
-/* juceToGiadaOutBuf_
-Converts 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. */
-
-void juceToGiadaOutBuf_(AudioBuffer& outBuf)
-{
-       for (int i = 0; i < outBuf.countFrames(); i++)
-               for (int j = 0; j < outBuf.countChannels(); j++)
-                       outBuf[i][j] = audioBuffer_.getSample(j, i);
 }
 
 /* -------------------------------------------------------------------------- */
 
-void processPlugins_(const std::vector<Plugin*>& plugins, juce::MidiBuffer& events)
-{
-       for (Plugin* p : plugins)
-       {
-               if (!p->valid || p->isSuspended() || p->isBypassed())
-                       continue;
-               p->process(audioBuffer_, events);
-       }
-       events.clear();
-}
-} // namespace
-
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-
-bool Info::getCurrentPosition(CurrentPositionInfo& result)
+bool PluginHost::Info::getCurrentPosition(CurrentPositionInfo& result)
 {
-       result.bpm           = clock::getBpm();
-       result.timeInSamples = clock::getCurrentFrame();
-       result.timeInSeconds = clock::getCurrentSecond();
-       result.isPlaying     = clock::isRunning();
+       result.bpm           = m_sequencer.getBpm();
+       result.timeInSamples = m_sequencer.getCurrentFrame();
+       result.timeInSeconds = m_sequencer.getCurrentSecond(m_sampleRate);
+       result.isPlaying     = m_sequencer.isRunning();
 
        return true;
 }
 
 /* -------------------------------------------------------------------------- */
 
-bool Info::canControlTransport()
+bool PluginHost::Info::canControlTransport()
 {
        return false;
 }
@@ -105,27 +68,25 @@ bool Info::canControlTransport()
 /* -------------------------------------------------------------------------- */
 /* -------------------------------------------------------------------------- */
 
-void close()
+PluginHost::PluginHost(model::Model& m)
+: m_model(m)
 {
-       messageManager_->deleteInstance();
-       model::clear<model::PluginPtrs>();
 }
 
 /* -------------------------------------------------------------------------- */
 
-void init(int buffersize)
+void PluginHost::reset(int bufferSize)
 {
-       messageManager_ = juce::MessageManager::getInstance();
-       audioBuffer_.setSize(G_MAX_IO_CHANS, buffersize);
-       pluginId_ = 0;
+       freeAllPlugins();
+       m_audioBuffer.setSize(G_MAX_IO_CHANS, bufferSize);
 }
 
 /* -------------------------------------------------------------------------- */
 
-void processStack(AudioBuffer& outBuf, const std::vector<Plugin*>& plugins,
+void PluginHost::processStack(mcl::AudioBuffer& outBuf, const std::vector<Plugin*>& plugins,
     juce::MidiBuffer* events)
 {
-       assert(outBuf.countFrames() == audioBuffer_.getNumSamples());
+       assert(outBuf.countFrames() == m_audioBuffer.getNumSamples());
 
        /* If events are null: Audio stack processing (master in, master out or
        sample channels. No need for MIDI events. 
@@ -134,102 +95,109 @@ void processStack(AudioBuffer& outBuf, const std::vector<Plugin*>& plugins,
 
        if (events == nullptr)
        {
-               giadaToJuceTempBuf_(outBuf);
+               giadaToJuceTempBuf(outBuf);
                juce::MidiBuffer dummyEvents; // empty
-               processPlugins_(plugins, dummyEvents);
+               processPlugins(plugins, dummyEvents);
        }
        else
        {
-               audioBuffer_.clear();
-               processPlugins_(plugins, *events);
+               m_audioBuffer.clear();
+               processPlugins(plugins, *events);
        }
-       juceToGiadaOutBuf_(outBuf);
+       juceToGiadaOutBuf(outBuf);
 }
 
 /* -------------------------------------------------------------------------- */
 
-void addPlugin(std::unique_ptr<Plugin> p, ID channelId)
+const Plugin& PluginHost::addPlugin(std::unique_ptr<Plugin> p)
 {
-       model::add(std::move(p));
-
-       const Plugin& pluginRef = model::back<Plugin>();
-
-       /* TODO - unfortunately JUCE wants mutable plugin objects due to the
-       presence of the non-const processBlock() method. Why not const_casting
-       only in the Plugin class? */
-       model::get().getChannel(channelId).plugins.push_back(const_cast<Plugin*>(&pluginRef));
-       model::swap(model::SwapType::HARD);
+       m_model.addShared(std::move(p));
+       return m_model.backShared<Plugin>();
 }
 
 /* -------------------------------------------------------------------------- */
 
-void swapPlugin(const m::Plugin& p1, const m::Plugin& p2, ID channelId)
+void PluginHost::swapPlugin(const m::Plugin& p1, const m::Plugin& p2, std::vector<Plugin*>& plugins)
 {
-       std::vector<m::Plugin*>& pvec   = model::get().getChannel(channelId).plugins;
-       std::size_t              index1 = u::vector::indexOf(pvec, &p1);
-       std::size_t              index2 = u::vector::indexOf(pvec, &p2);
-       std::swap(pvec.at(index1), pvec.at(index2));
-
-       model::swap(model::SwapType::HARD);
+       std::size_t index1 = u::vector::indexOf(plugins, &p1);
+       std::size_t index2 = u::vector::indexOf(plugins, &p2);
+       std::swap(plugins.at(index1), plugins.at(index2));
 }
 
 /* -------------------------------------------------------------------------- */
 
-void freePlugin(const m::Plugin& plugin, ID channelId)
+void PluginHost::freePlugin(const m::Plugin& plugin)
 {
-       u::vector::remove(model::get().getChannel(channelId).plugins, &plugin);
-       model::swap(model::SwapType::HARD);
-       model::remove(plugin);
+       m_model.removeShared(plugin);
 }
 
-void freePlugins(const std::vector<Plugin*>& plugins)
+void PluginHost::freePlugins(const std::vector<Plugin*>& plugins)
 {
-       // TODO - channels???
        for (const Plugin* p : plugins)
-               model::remove(*p);
+               m_model.removeShared(*p);
 }
 
 /* -------------------------------------------------------------------------- */
 
-std::vector<Plugin*> clonePlugins(const std::vector<Plugin*>& plugins)
+void PluginHost::freeAllPlugins()
 {
-       std::vector<Plugin*> out;
-       for (const Plugin* p : plugins)
-       {
-               model::add(pluginManager::makePlugin(*p));
-               out.push_back(&model::back<Plugin>());
-       }
-       return out;
+       m_model.clearShared<model::PluginPtrs>();
 }
 
 /* -------------------------------------------------------------------------- */
 
-void setPluginParameter(ID pluginId, int paramIndex, float value)
+void PluginHost::setPluginParameter(ID pluginId, int paramIndex, float value)
 {
-       model::find<Plugin>(pluginId)->setParameter(paramIndex, value);
+       m_model.findShared<Plugin>(pluginId)->setParameter(paramIndex, value);
 }
 
 /* -------------------------------------------------------------------------- */
 
-void setPluginProgram(ID pluginId, int programIndex)
+void PluginHost::setPluginProgram(ID pluginId, int programIndex)
 {
-       model::find<Plugin>(pluginId)->setCurrentProgram(programIndex);
+       m_model.findShared<Plugin>(pluginId)->setCurrentProgram(programIndex);
 }
 
 /* -------------------------------------------------------------------------- */
 
-void toggleBypass(ID pluginId)
+void PluginHost::toggleBypass(ID pluginId)
 {
-       Plugin& plugin = *model::find<Plugin>(pluginId);
+       Plugin& plugin = *m_model.findShared<Plugin>(pluginId);
        plugin.setBypass(!plugin.isBypassed());
 }
 
 /* -------------------------------------------------------------------------- */
 
-void runDispatchLoop()
+void PluginHost::giadaToJuceTempBuf(const mcl::AudioBuffer& outBuf)
+{
+       for (int i = 0; i < outBuf.countFrames(); i++)
+               for (int j = 0; j < outBuf.countChannels(); j++)
+                       m_audioBuffer.setSample(j, i, outBuf[i][j]);
+}
+
+/* juceToGiadaOutBuf
+Converts 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. */
+
+void PluginHost::juceToGiadaOutBuf(mcl::AudioBuffer& outBuf) const
 {
-       messageManager_->runDispatchLoopUntil(10);
+       for (int i = 0; i < outBuf.countFrames(); i++)
+               for (int j = 0; j < outBuf.countChannels(); j++)
+                       outBuf[i][j] = m_audioBuffer.getSample(j, i);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void PluginHost::processPlugins(const std::vector<Plugin*>& plugins, juce::MidiBuffer& events)
+{
+       for (Plugin* p : plugins)
+       {
+               if (!p->valid || p->isSuspended() || p->isBypassed())
+                       continue;
+               p->process(m_audioBuffer, events);
+       }
+       events.clear();
 }
-} // namespace giada::m::pluginHost
+} // namespace giada::m
 
 #endif // #ifdef WITH_VST
index cff690e1a3bd0c9988f03c21ad2409eff73dceb0..e247e841fc1f0604a289a076bb543a6c180c8ccf 100644 (file)
 #include "deps/juce-config.h"
 #include <functional>
 
+namespace mcl
+{
+class AudioBuffer;
+}
+
 namespace giada::m
 {
 class Plugin;
-class AudioBuffer;
-} // namespace giada::m
-namespace giada::m::pluginHost
+}
+
+namespace giada::m::model
 {
-struct Info : public juce::AudioPlayHead
+class Model;
+}
+
+namespace giada::m
 {
-       bool getCurrentPosition(CurrentPositionInfo& result) override;
-       bool canControlTransport() override;
-};
+class Sequencer;
+class PluginHost final
+{
+public:
+       class Info final : public juce::AudioPlayHead
+       {
+       public:
+               Info(const Sequencer&, int sampleRate);
 
-/* -------------------------------------------------------------------------- */
+               bool getCurrentPosition(CurrentPositionInfo& result) override;
+               bool canControlTransport() override;
 
-void init(int buffersize);
-void close();
+       private:
+               const Sequencer& m_sequencer;
+               int              m_sampleRate;
+       };
 
-/* addPlugin
-Adds a new plugin to channel 'channelId'. */
+       PluginHost(model::Model&);
 
-void addPlugin(std::unique_ptr<Plugin> p, ID channelId);
+       /* reset
+       Brings everything back to the initial state. */
 
-/* processStack
-Applies the fx list to the buffer. */
+       void reset(int bufferSize);
 
-void processStack(AudioBuffer& outBuf, const std::vector<Plugin*>& plugins,
-    juce::MidiBuffer* events = nullptr);
+       /* addPlugin
+       Loads a new plugin into memory. Returns a reference to the newly created
+       object. */
 
-/* swapPlugin 
-Swaps plug-in 1 with plug-in 2 in Channel 'channelId'. */
+       const Plugin& addPlugin(std::unique_ptr<Plugin> p);
 
-void swapPlugin(const m::Plugin& p1, const m::Plugin& p2, ID channelId);
+       /* processStack
+       Applies the fx list to the buffer. */
 
-/* freePlugin.
-Unloads plugin from channel 'channelId'. */
+       void processStack(mcl::AudioBuffer& outBuf, const std::vector<Plugin*>& plugins,
+           juce::MidiBuffer* events = nullptr);
 
-void freePlugin(const m::Plugin& plugin, ID channelId);
+       /* swapPlugin 
+       Swaps plug-in 1 with plug-in 2 in the plug-in vector. */
 
-/* freePlugins
-Unloads multiple plugins. Useful when freeing or deleting a channel. */
+       void swapPlugin(const m::Plugin& p1, const m::Plugin& p2, std::vector<Plugin*>& plugins);
 
-void freePlugins(const std::vector<Plugin*>& plugins);
+       /* freePlugin.
+       Unloads plugin from memory. */
 
-/* clonePlugins
-Clones all the plug-ins in the 'plugins' vector. */
+       void freePlugin(const m::Plugin& plugin);
 
-std::vector<Plugin*> clonePlugins(const std::vector<Plugin*>& plugins);
+       /* freePlugins
+       Unloads multiple plugins. Useful when freeing or deleting a channel. */
 
-void setPluginParameter(ID pluginId, int paramIndex, float value);
-void setPluginProgram(ID pluginId, int programIndex);
-void toggleBypass(ID pluginId);
+       void freePlugins(const std::vector<Plugin*>& plugins);
 
-/* runDispatchLoop
-Wakes up plugins' GUI manager for N milliseconds. */
+       /* freeAllPlugins
+       Just deletes everything. */
 
-void runDispatchLoop();
-} // namespace giada::m::pluginHost
+       void freeAllPlugins();
+
+       void setPluginParameter(ID pluginId, int paramIndex, float value);
+       void setPluginProgram(ID pluginId, int programIndex);
+       void toggleBypass(ID pluginId);
+
+private:
+       void giadaToJuceTempBuf(const mcl::AudioBuffer& outBuf);
+
+       /* juceToGiadaOutBuf
+       Converts 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. */
+
+       void juceToGiadaOutBuf(mcl::AudioBuffer& outBuf) const;
+
+       void processPlugins(const std::vector<Plugin*>& plugins, juce::MidiBuffer& events);
+
+       model::Model& m_model;
+
+       juce::AudioBuffer<float> m_audioBuffer;
+};
+} // namespace giada::m
 
 #endif
 
index 482c4be959e8074c0af464245ca7ce1ea9cd31fc..7e5cbb7adf3559449b490ffbf5c932df491f1d7f 100644 (file)
 
 #ifdef WITH_VST
 
-#include "pluginManager.h"
-#include "core/conf.h"
+#include "core/plugins/pluginManager.h"
 #include "core/const.h"
-#include "core/idManager.h"
 #include "core/model/model.h"
 #include "core/patch.h"
 #include "core/plugins/plugin.h"
 #include "utils/string.h"
 #include <cassert>
 
-namespace giada::m::pluginManager
+namespace giada::m
 {
-namespace
+void PluginManager::reset(SortMethod sortMethod)
 {
-IdManager pluginId_;
+       m_pluginId       = IdManager();
+       m_missingPlugins = false;
 
-int samplerate_;
-int buffersize_;
-
-/* formatManager
-Plugin format manager. */
-
-juce::AudioPluginFormatManager formatManager_;
-
-/* knownPuginList
-List of known (i.e. scanned) plugins. */
-
-juce::KnownPluginList knownPluginList_;
-
-/* unknownPluginList
-List of unrecognized plugins found in a patch. */
-
-std::vector<std::string> unknownPluginList_;
-
-/* missingPlugins
-If some plugins from any stack are missing. */
-
-bool missingPlugins_;
-
-std::unique_ptr<Plugin> makeInvalidPlugin_(const std::string& pid, ID id)
-{
-       missingPlugins_ = true;
-       unknownPluginList_.push_back(pid);
-       return std::make_unique<Plugin>(pluginId_.generate(id), pid); // Invalid plug-in
-}
-} // namespace
-
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-
-void init(int samplerate, int buffersize)
-{
-       pluginId_       = IdManager();
-       samplerate_     = samplerate;
-       buffersize_     = buffersize;
-       missingPlugins_ = false;
-
-       formatManager_.addDefaultFormats();
-       unknownPluginList_.clear();
+       m_unknownPluginList.clear();
+       if (m_formatManager.getNumFormats() == 0) // Must be called only once
+               m_formatManager.addDefaultFormats();
 
        loadList(u::fs::getHomePath() + G_SLASH + "plugins.xml");
-       sortPlugins(static_cast<pluginManager::SortMethod>(conf::conf.pluginSortMethod));
+       sortPlugins(sortMethod);
 }
 
 /* -------------------------------------------------------------------------- */
 
-int scanDirs(const std::string& dirs, const std::function<void(float)>& cb)
+int PluginManager::scanDirs(const std::string& dirs, const std::function<void(float)>& cb)
 {
        u::log::print("[pluginManager::scanDir] requested directories: '%s'\n", dirs);
-       u::log::print("[pluginManager::scanDir] current plugins: %d\n", knownPluginList_.getNumTypes());
+       u::log::print("[pluginManager::scanDir] current plug-in types: %d\n", m_knownPluginList.getNumTypes());
 
-       knownPluginList_.clear(); // clear up previous plugins
+       m_knownPluginList.clear(); // clear up previous plugins
 
        std::vector<std::string> dirVec = u::string::split(dirs, ";");
 
@@ -108,10 +66,9 @@ int scanDirs(const std::string& dirs, const std::function<void(float)>& cb)
        for (const std::string& dir : dirVec)
                searchPath.add(juce::File(dir));
 
-       for (int i = 0; i < formatManager_.getNumFormats(); i++)
+       for (int i = 0; i < m_formatManager.getNumFormats(); i++)
        {
-
-               juce::PluginDirectoryScanner scanner(knownPluginList_, *formatManager_.getFormat(i), searchPath,
+               juce::PluginDirectoryScanner scanner(m_knownPluginList, *m_formatManager.getFormat(i), searchPath,
                    /*recursive=*/true, juce::File());
 
                juce::String name;
@@ -122,15 +79,15 @@ int scanDirs(const std::string& dirs, const std::function<void(float)>& cb)
                }
        }
 
-       u::log::print("[pluginManager::scanDir] %d plugin(s) found\n", knownPluginList_.getNumTypes());
-       return knownPluginList_.getNumTypes();
+       u::log::print("[pluginManager::scanDir] %d plugin(s) found\n", m_knownPluginList.getNumTypes());
+       return m_knownPluginList.getNumTypes();
 }
 
 /* -------------------------------------------------------------------------- */
 
-bool saveList(const std::string& filepath)
+bool PluginManager::saveList(const std::string& filepath) const
 {
-       bool out = knownPluginList_.createXml()->writeTo(juce::File(filepath));
+       bool out = m_knownPluginList.createXml()->writeTo(juce::File(filepath));
        if (!out)
                u::log::print("[pluginManager::saveList] unable to save plugin list to %s\n", filepath);
        return out;
@@ -138,65 +95,72 @@ bool saveList(const std::string& filepath)
 
 /* -------------------------------------------------------------------------- */
 
-bool loadList(const std::string& filepath)
+bool PluginManager::loadList(const std::string& filepath)
 {
        std::unique_ptr<juce::XmlElement> elem(juce::XmlDocument::parse(juce::File(filepath)));
        if (elem == nullptr)
                return false;
-       knownPluginList_.recreateFromXml(*elem);
+       m_knownPluginList.recreateFromXml(*elem);
        return true;
 }
 
 /* -------------------------------------------------------------------------- */
 
-std::unique_ptr<Plugin> makePlugin(const std::string& pid, ID id)
+std::unique_ptr<Plugin> PluginManager::makePlugin(const std::string& pid,
+    int sampleRate, int bufferSize, const Sequencer& sequencer, ID id)
 {
        /* Plug-in ID generator is updated anyway, as we store Plugin objects also
        if they are in an invalid state. */
 
-       pluginId_.set(id);
+       m_pluginId.set(id);
 
-       const std::unique_ptr<juce::PluginDescription> pd = knownPluginList_.getTypeForIdentifierString(pid);
+       const std::unique_ptr<juce::PluginDescription> pd = m_knownPluginList.getTypeForIdentifierString(pid);
        if (pd == nullptr)
        {
                u::log::print("[pluginManager::makePlugin] no plugin found with pid=%s!\n", pid);
-               return makeInvalidPlugin_(pid, id);
+               return makeInvalidPlugin(pid, id);
        }
 
        juce::String                               error;
-       std::unique_ptr<juce::AudioPluginInstance> pi = formatManager_.createPluginInstance(*pd, samplerate_, buffersize_, error);
+       std::unique_ptr<juce::AudioPluginInstance> pi = m_formatManager.createPluginInstance(*pd, sampleRate, bufferSize, error);
        if (pi == nullptr)
        {
                u::log::print("[pluginManager::makePlugin] unable to create instance with pid=%s! Error: %s\n",
                    pid, error.toStdString());
-               return makeInvalidPlugin_(pid, id);
+               return makeInvalidPlugin(pid, id);
        }
 
        u::log::print("[pluginManager::makePlugin] plugin instance with pid=%s created\n", pid);
 
-       return std::make_unique<Plugin>(pluginId_.generate(id), std::move(pi), samplerate_, buffersize_);
+       return std::make_unique<Plugin>(
+           m_pluginId.generate(id),
+           std::move(pi),
+           std::make_unique<PluginHost::Info>(sequencer, sampleRate),
+           sampleRate, bufferSize);
 }
 
 /* -------------------------------------------------------------------------- */
 
-std::unique_ptr<Plugin> makePlugin(int index)
+std::unique_ptr<Plugin> PluginManager::makePlugin(int index, int sampleRate,
+    int bufferSize, const Sequencer& sequencer)
 {
-       juce::PluginDescription pd = knownPluginList_.getTypes()[index];
+       juce::PluginDescription pd = m_knownPluginList.getTypes()[index];
 
-       if (pd.uid == 0) // Invalid
+       if (pd.uniqueId == 0) // Invalid
                return {};
 
        u::log::print("[pluginManager::makePlugin] plugin found, uid=%s, name=%s...\n",
            pd.createIdentifierString().toRawUTF8(), pd.name.toRawUTF8());
 
-       return makePlugin(pd.createIdentifierString().toStdString());
+       return makePlugin(pd.createIdentifierString().toStdString(), sampleRate, bufferSize, sequencer);
 }
 
 /* -------------------------------------------------------------------------- */
 
-std::unique_ptr<Plugin> makePlugin(const Plugin& src)
+std::unique_ptr<Plugin> PluginManager::makePlugin(const Plugin& src, int sampleRate,
+    int bufferSize, const Sequencer& sequencer)
 {
-       std::unique_ptr<Plugin> p = makePlugin(src.getUniqueId());
+       std::unique_ptr<Plugin> p = makePlugin(src.getUniqueId(), sampleRate, bufferSize, sequencer);
 
        for (int i = 0; i < src.getNumParameters(); i++)
                p->setParameter(i, src.getParameter(i));
@@ -206,9 +170,9 @@ std::unique_ptr<Plugin> makePlugin(const Plugin& src)
 
 /* -------------------------------------------------------------------------- */
 
-const patch::Plugin serializePlugin(const Plugin& p)
+const Patch::Plugin PluginManager::serializePlugin(const Plugin& p) const
 {
-       patch::Plugin pp;
+       Patch::Plugin pp;
        pp.id     = p.id;
        pp.path   = p.getUniqueId();
        pp.bypass = p.isBypassed();
@@ -222,16 +186,17 @@ const patch::Plugin serializePlugin(const Plugin& p)
 
 /* -------------------------------------------------------------------------- */
 
-std::unique_ptr<Plugin> deserializePlugin(const patch::Plugin& p, patch::Version version)
+std::unique_ptr<Plugin> PluginManager::deserializePlugin(const Patch::Plugin& p,
+    Patch::Version version, int sampleRate, int bufferSize, const Sequencer& sequencer)
 {
-       std::unique_ptr<Plugin> plugin = makePlugin(p.path, p.id);
+       std::unique_ptr<Plugin> plugin = makePlugin(p.path, sampleRate, bufferSize, sequencer, p.id);
        if (!plugin->valid)
                return plugin; // Return invalid version
 
        /* Fill plug-in parameters. */
        plugin->setBypass(p.bypass);
 
-       if (version < patch::Version{0, 17, 0}) // TODO - to be removed in 0.18.0
+       if (version < Patch::Version{0, 17, 0}) // TODO - to be removed in 0.18.0
                for (unsigned j = 0; j < p.params.size(); j++)
                        plugin->setParameter(j, p.params.at(j));
        else
@@ -254,12 +219,12 @@ std::unique_ptr<Plugin> deserializePlugin(const patch::Plugin& p, patch::Version
 
 /* -------------------------------------------------------------------------- */
 
-std::vector<Plugin*> hydratePlugins(std::vector<ID> pluginIds)
+std::vector<Plugin*> PluginManager::hydratePlugins(std::vector<ID> pluginIds, model::Model& model)
 {
        std::vector<Plugin*> out;
        for (ID id : pluginIds)
        {
-               Plugin* plugin = model::find<Plugin>(id);
+               Plugin* plugin = model.findShared<Plugin>(id);
                if (plugin != nullptr)
                        out.push_back(plugin);
        }
@@ -268,74 +233,82 @@ std::vector<Plugin*> hydratePlugins(std::vector<ID> pluginIds)
 
 /* -------------------------------------------------------------------------- */
 
-int countAvailablePlugins()
+int PluginManager::countAvailablePlugins() const
 {
-       return knownPluginList_.getNumTypes();
+       return m_knownPluginList.getNumTypes();
 }
 
 /* -------------------------------------------------------------------------- */
 
-int countUnknownPlugins()
+std::vector<PluginManager::PluginInfo> PluginManager::getPluginsInfo() const
 {
-       return unknownPluginList_.size();
-}
+       std::vector<PluginInfo> out;
 
-/* -------------------------------------------------------------------------- */
-
-PluginInfo getAvailablePluginInfo(int i)
-{
-       juce::PluginDescription pd = knownPluginList_.getTypes()[i];
-       PluginInfo              pi;
-       pi.uid              = pd.fileOrIdentifier.toStdString();
-       pi.name             = pd.descriptiveName.toStdString();
-       pi.category         = pd.category.toStdString();
-       pi.manufacturerName = pd.manufacturerName.toStdString();
-       pi.format           = pd.pluginFormatName.toStdString();
-       pi.isInstrument     = pd.isInstrument;
-       return pi;
-}
-
-/* -------------------------------------------------------------------------- */
-
-bool hasMissingPlugins()
-{
-       return missingPlugins_;
-}
+       for (int i = 0; i < m_knownPluginList.getNumTypes(); i++)
+       {
+               juce::PluginDescription pd = m_knownPluginList.getTypes()[i];
+               PluginInfo              pi;
+
+               pi.uid              = pd.fileOrIdentifier.toStdString();
+               pi.name             = pd.descriptiveName.toStdString();
+               pi.category         = pd.category.toStdString();
+               pi.manufacturerName = pd.manufacturerName.toStdString();
+               pi.format           = pd.pluginFormatName.toStdString();
+               pi.isInstrument     = pd.isInstrument;
+               pi.exists           = m_formatManager.doesPluginStillExist(*m_knownPluginList.getTypeForFile(pi.uid));
+               pi.isKnown          = true;
+               out.push_back(pi);
+       }
 
-/* -------------------------------------------------------------------------- */
+       for (const std::string& uid : m_unknownPluginList)
+       {
+               PluginInfo pi;
+               pi.uid          = uid;
+               pi.isInstrument = false;
+               pi.exists       = false;
+               pi.isKnown      = false;
+               out.push_back(pi);
+       }
 
-std::string getUnknownPluginInfo(int i)
-{
-       return unknownPluginList_.at(i);
+       return out;
 }
 
 /* -------------------------------------------------------------------------- */
 
-bool doesPluginExist(const std::string& pid)
+bool PluginManager::hasMissingPlugins() const
 {
-       return formatManager_.doesPluginStillExist(*knownPluginList_.getTypeForFile(pid));
+       return m_missingPlugins;
 }
 
 /* -------------------------------------------------------------------------- */
 
-void sortPlugins(SortMethod method)
+void PluginManager::sortPlugins(SortMethod method)
 {
        switch (method)
        {
        case SortMethod::NAME:
-               knownPluginList_.sort(juce::KnownPluginList::SortMethod::sortAlphabetically, true);
+               m_knownPluginList.sort(juce::KnownPluginList::SortMethod::sortAlphabetically, true);
                break;
        case SortMethod::CATEGORY:
-               knownPluginList_.sort(juce::KnownPluginList::SortMethod::sortByCategory, true);
+               m_knownPluginList.sort(juce::KnownPluginList::SortMethod::sortByCategory, true);
                break;
        case SortMethod::MANUFACTURER:
-               knownPluginList_.sort(juce::KnownPluginList::SortMethod::sortByManufacturer, true);
+               m_knownPluginList.sort(juce::KnownPluginList::SortMethod::sortByManufacturer, true);
                break;
        case SortMethod::FORMAT:
-               knownPluginList_.sort(juce::KnownPluginList::SortMethod::sortByFormat, true);
+               m_knownPluginList.sort(juce::KnownPluginList::SortMethod::sortByFormat, true);
                break;
        }
 }
-} // namespace giada::m::pluginManager
+
+/* -------------------------------------------------------------------------- */
+
+std::unique_ptr<Plugin> PluginManager::makeInvalidPlugin(const std::string& pid, ID id)
+{
+       m_missingPlugins = true;
+       m_unknownPluginList.push_back(pid);
+       return std::make_unique<Plugin>(m_pluginId.generate(id), pid); // Invalid plug-in
+}
+} // namespace giada::m
 
 #endif // #ifdef WITH_VST
index d44a02d374c5f315d324904e92856178915783ca..8b16ae48a972e59da23c363c41a13822d2d8831b 100644 (file)
@@ -29,6 +29,8 @@
 #ifndef G_PLUGIN_MANAGER_H
 #define G_PLUGIN_MANAGER_H
 
+#include "core/idManager.h"
+#include "core/patch.h"
 #include "deps/juce-config.h"
 #include "plugin.h"
 
@@ -37,77 +39,111 @@ namespace giada::m::patch
 struct Plugin;
 struct Version;
 } // namespace giada::m::patch
-namespace giada::m::pluginManager
-{
-enum class SortMethod : int
+
+namespace giada::m::model
 {
-       NAME = 0,
-       CATEGORY,
-       MANUFACTURER,
-       FORMAT
-};
+class Model;
+}
 
-struct PluginInfo
+namespace giada::m
 {
-       std::string uid;
-       std::string name;
-       std::string category;
-       std::string manufacturerName;
-       std::string format;
-       bool        isInstrument;
-};
+class Sequencer;
+class PluginManager final
+{
+public:
+       enum class SortMethod : int
+       {
+               NAME = 0,
+               CATEGORY,
+               MANUFACTURER,
+               FORMAT
+       };
+
+       struct PluginInfo
+       {
+               std::string uid;
+               std::string name;
+               std::string category;
+               std::string manufacturerName;
+               std::string format;
+               bool        isInstrument;
+               bool        exists;
+               bool        isKnown;
+       };
+
+       /* getPluginsInfo
+       Returns a vector of PluginInfo objects containing all plug-ins, known and
+       unknown, scanned so far. */
 
-void init(int samplerate, int buffersize);
+       std::vector<PluginInfo> getPluginsInfo() const;
 
-/* scanDirs
-Parses plugin directories (semicolon-separated) and store list in 
-knownPluginList. The callback is called on each plugin found. Used to update the 
-main window from the GUI thread. */
+       /* hasMissingPlugins
+       True if some plug-ins have been marked as missing during the initial scan. */
 
-int scanDirs(const std::string& paths, const std::function<void(float)>& cb);
+       bool hasMissingPlugins() const;
 
-/* (save|load)List
-(Save|Load) knownPluginList (in|from) an XML file. */
+       /* countAvailablePlugins
+       Returns how many plug-ins are ready and available for usage. */
 
-bool saveList(const std::string& path);
-bool loadList(const std::string& path);
+       int countAvailablePlugins() const;
 
-/* countAvailablePlugins
-Returns how many plug-ins are ready and available for usage. */
+       /* reset
+       Brings everything back to the initial state. */
 
-int countAvailablePlugins();
+       void reset(SortMethod);
 
-/* countUnknownPlugins
-Returns how many plug-ins are in a unknown/not-found state. */
+       /* scanDirs
+       Parses plugin directories (semicolon-separated) and store list in 
+       knownPluginList. The callback is called on each plugin found. Used to update
+       the main window from the GUI thread. */
 
-int countUnknownPlugins();
+       int scanDirs(const std::string& paths, const std::function<void(float)>& cb);
 
-std::unique_ptr<Plugin> makePlugin(const std::string& pid, ID id = 0);
-std::unique_ptr<Plugin> makePlugin(int index);
-std::unique_ptr<Plugin> makePlugin(const Plugin& other);
+       /* (save|load)List
+       (Save|Load) knownPluginList (in|from) an XML file. */
 
-/* (de)serializePlugin
-Transforms patch data into a Plugin object and vice versa. */
+       bool saveList(const std::string& path) const;
+       bool loadList(const std::string& path);
 
-const patch::Plugin     serializePlugin(const Plugin& p);
-std::unique_ptr<Plugin> deserializePlugin(const patch::Plugin& p, patch::Version version);
-std::vector<Plugin*>    hydratePlugins(std::vector<ID> pluginIds);
+       std::unique_ptr<Plugin> makePlugin(const std::string& pid, int sampleRate, int bufferSize, const Sequencer&, ID id = 0);
+       std::unique_ptr<Plugin> makePlugin(int index, int sampleRate, int bufferSize, const Sequencer&);
+       std::unique_ptr<Plugin> makePlugin(const Plugin& other, int sampleRate, int bufferSize, const Sequencer&);
 
-/* getAvailablePluginInfo
-Returns the available plugin information (name, type, ...) given a plug-in
-index. */
+       /* (de)serializePlugin
+       Transforms patch data into a Plugin object and vice versa. */
 
-PluginInfo getAvailablePluginInfo(int index);
+       const Patch::Plugin     serializePlugin(const Plugin& p) const;
+       std::unique_ptr<Plugin> deserializePlugin(const Patch::Plugin& p, Patch::Version version, int sampleRate, int bufferSize, const Sequencer&);
+       std::vector<Plugin*>    hydratePlugins(std::vector<ID> pluginIds, model::Model& model);
 
-std::string getUnknownPluginInfo(int index);
+       void sortPlugins(SortMethod sortMethod);
 
-bool doesPluginExist(const std::string& pid);
+private:
+       std::unique_ptr<Plugin> makeInvalidPlugin(const std::string& pid, ID id);
 
-bool hasMissingPlugins();
+       IdManager m_pluginId;
 
-void sortPlugins(SortMethod sortMethod);
+       /* formatManager
+       Plugin format manager. */
 
-} // namespace giada::m::pluginManager
+       juce::AudioPluginFormatManager m_formatManager;
+
+       /* knownPuginList
+       List of known (i.e. scanned) plugins. */
+
+       juce::KnownPluginList m_knownPluginList;
+
+       /* unknownPluginList
+       List of unrecognized plugins found in a patch. */
+
+       std::vector<std::string> m_unknownPluginList;
+
+       /* missingPlugins
+       If some plugins from any stack are missing. */
+
+       bool m_missingPlugins;
+};
+} // namespace giada::m
 
 #endif
 
index aa6cdd795fd07d955e95504b68a9d440783edbdc..f21c37467072a0581128d1d93e05a9bb3d92bb25 100644 (file)
@@ -25,7 +25,6 @@
  * -------------------------------------------------------------------------- */
 
 #include "quantizer.h"
-#include "core/clock.h"
 #include <cassert>
 
 namespace giada::m
index 88760b81859f455e959988fed64a19cefd4237d7..490e57e6bc7552682a13e74ea84d95824eff76c7 100644 (file)
@@ -67,7 +67,7 @@ public:
 
        bool hasBeenTriggered() const;
 
-  private:
+private:
        std::map<int, std::function<void(Frame)>> m_callbacks;
        int                                       m_performId = -1;
 };
index 0e95d6faab477883671c1c34f73cb9f17d40d626..7b07395a6b156b9a8be0fe2e6839d64e35a80111 100644 (file)
@@ -30,9 +30,7 @@
 #include <array>
 #include <atomic>
 
-namespace giada
-{
-namespace m
+namespace giada::m
 {
 /* Queue
 Single producer, single consumer lock-free queue. */
@@ -48,6 +46,9 @@ public:
        }
 
        Queue(const Queue&) = delete;
+       Queue(Queue&&)      = delete;
+       Queue& operator=(const Queue&) = delete;
+       Queue& operator=(Queue&&) = delete;
 
        bool pop(T& item)
        {
@@ -73,7 +74,7 @@ public:
                return true;
        }
 
-  private:
+private:
        std::size_t increment(std::size_t i) const
        {
                return (i + 1) % size;
@@ -83,7 +84,6 @@ public:
        std::atomic<std::size_t> m_head;
        std::atomic<std::size_t> m_tail;
 };
-} // namespace m
-} // namespace giada
+} // namespace giada::m
 
 #endif
diff --git a/src/core/recManager.cpp b/src/core/recManager.cpp
deleted file mode 100644 (file)
index f14e46d..0000000
+++ /dev/null
@@ -1,264 +0,0 @@
-/* -----------------------------------------------------------------------------
- *
- * Giada - Your Hardcore Loopmachine
- *
- * -----------------------------------------------------------------------------
- *
- * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
- *
- * This file is part of Giada - Your Hardcore Loopmachine.
- *
- * Giada - Your Hardcore Loopmachine is free software: you can
- * redistribute it and/or modify it under the terms of the GNU General
- * Public License as published by the Free Software Foundation, either
- * version 3 of the License, or (at your option) any later version.
- *
- * Giada - Your Hardcore Loopmachine is distributed in the hope that it
- * will be useful, but WITHOUT ANY WARRANTY; without even the implied
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Giada - Your Hardcore Loopmachine. If not, see
- * <http://www.gnu.org/licenses/>.
- *
- * -------------------------------------------------------------------------- */
-
-#include "core/recManager.h"
-#include "core/clock.h"
-#include "core/conf.h"
-#include "core/kernelAudio.h"
-#include "core/midiDispatcher.h"
-#include "core/mixer.h"
-#include "core/mixerHandler.h"
-#include "core/model/model.h"
-#include "core/recorder.h"
-#include "core/recorderHandler.h"
-#include "core/sequencer.h"
-#include "core/types.h"
-#include "gui/dispatcher.h"
-
-namespace giada::m::recManager
-{
-namespace
-{
-bool isKernelReady_()
-{
-       return kernelAudio::isReady();
-}
-
-bool canRec_()
-{
-       return isKernelReady_() && kernelAudio::isInputEnabled();
-}
-
-/* -------------------------------------------------------------------------- */
-
-void setRecordingAction_(bool v)
-{
-       model::get().recorder.isRecordingAction = v;
-       model::swap(model::SwapType::NONE);
-}
-
-void setRecordingInput_(bool v)
-{
-       model::get().recorder.isRecordingInput = v;
-       model::swap(model::SwapType::NONE);
-}
-
-/* -------------------------------------------------------------------------- */
-
-bool startActionRec_()
-{
-       clock::setStatus(ClockStatus::RUNNING);
-       sequencer::start();
-       conf::conf.recTriggerMode = RecTriggerMode::NORMAL;
-       return true;
-}
-
-/* -------------------------------------------------------------------------- */
-
-void startInputRec_()
-{
-       /* Start recording from the current frame, not the beginning. */
-       mixer::startInputRec(clock::getCurrentFrame());
-       sequencer::start();
-       conf::conf.recTriggerMode = RecTriggerMode::NORMAL;
-}
-} // namespace
-
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-
-bool isRecording()
-{
-       return isRecordingAction() || isRecordingInput();
-}
-
-bool isRecordingAction()
-{
-       return model::get().recorder.isRecordingAction;
-}
-
-bool isRecordingInput()
-{
-       return model::get().recorder.isRecordingInput;
-}
-
-/* -------------------------------------------------------------------------- */
-
-void startActionRec(RecTriggerMode mode)
-{
-       if (!isKernelReady_())
-               return;
-
-       if (mode == RecTriggerMode::NORMAL)
-       {
-               startActionRec_();
-               setRecordingAction_(true);
-       }
-       else
-       { // RecTriggerMode::SIGNAL
-               clock::setStatus(ClockStatus::WAITING);
-               clock::rewind();
-               midiDispatcher::setSignalCallback(startActionRec_);
-               v::dispatcher::setSignalCallback(startActionRec_);
-               setRecordingAction_(true);
-       }
-}
-
-/* -------------------------------------------------------------------------- */
-
-void stopActionRec()
-{
-       setRecordingAction_(false);
-
-       /* If you stop the Action Recorder in SIGNAL mode before any actual 
-       recording: just clean up everything and return. */
-
-       if (clock::getStatus() == ClockStatus::WAITING)
-       {
-               clock::setStatus(ClockStatus::STOPPED);
-               midiDispatcher::setSignalCallback(nullptr);
-               v::dispatcher::setSignalCallback(nullptr);
-               return;
-       }
-
-       std::unordered_set<ID> channels = recorderHandler::consolidate();
-
-       /* Enable reading actions for Channels that have just been filled with 
-       actions. Start reading right away, without checking whether 
-       conf::treatRecsAsLoops is enabled or not. Same thing for MIDI channels.  */
-
-       for (ID id : channels)
-       {
-               channel::Data& ch = model::get().getChannel(id);
-               ch.state->readActions.store(true);
-               if (ch.type == ChannelType::MIDI)
-                       ch.state->playStatus.store(ChannelStatus::PLAY);
-       }
-       model::swap(model::SwapType::HARD);
-}
-
-/* -------------------------------------------------------------------------- */
-
-void toggleActionRec(RecTriggerMode m)
-{
-       isRecordingAction() ? stopActionRec() : startActionRec(m);
-}
-
-/* -------------------------------------------------------------------------- */
-
-bool startInputRec(RecTriggerMode triggerMode, InputRecMode inputMode)
-{
-       if (!canRec_() || !mh::hasInputRecordableChannels())
-               return false;
-
-       if (triggerMode == RecTriggerMode::SIGNAL || inputMode == InputRecMode::FREE)
-               clock::rewind();
-
-       if (inputMode == InputRecMode::FREE)
-               mixer::setEndOfRecCallback([inputMode] { stopInputRec(inputMode); });
-
-       if (triggerMode == RecTriggerMode::NORMAL)
-       {
-               startInputRec_();
-               setRecordingInput_(true);
-               G_DEBUG("Start input rec, NORMAL mode");
-       }
-       else
-       {
-               clock::setStatus(ClockStatus::WAITING);
-               mixer::setSignalCallback([] {
-                       startInputRec_();
-                       setRecordingInput_(true);
-               });
-               G_DEBUG("Start input rec, SIGNAL mode");
-       }
-
-       return true;
-}
-
-/* -------------------------------------------------------------------------- */
-
-void stopInputRec(InputRecMode recMode)
-{
-       setRecordingInput_(false);
-
-       Frame recordedFrames = mixer::stopInputRec();
-
-       /* When recording in RIGID mode, the amount of recorded frames is always 
-       equal to the current loop length. */
-
-       if (recMode == InputRecMode::RIGID)
-               recordedFrames = clock::getFramesInLoop();
-
-       G_DEBUG("Stop input rec, recordedFrames=" << recordedFrames);
-
-       /* If you stop the Input Recorder in SIGNAL mode before any actual 
-       recording: just clean up everything and return. */
-
-       if (clock::getStatus() == ClockStatus::WAITING)
-       {
-               clock::setStatus(ClockStatus::STOPPED);
-               mixer::setSignalCallback(nullptr);
-               return;
-       }
-
-       /* Finalize recordings. InputRecMode::FREE requires some adjustments. */
-
-       mh::finalizeInputRec(recordedFrames);
-
-       if (recMode == InputRecMode::FREE)
-       {
-               clock::rewind();
-               clock::setBpm(clock::calcBpmFromRec(recordedFrames));
-               mixer::setEndOfRecCallback(nullptr);
-               refreshInputRecMode(); // Back to RIGID mode if necessary
-       }
-}
-
-/* -------------------------------------------------------------------------- */
-
-bool toggleInputRec(RecTriggerMode m, InputRecMode i)
-{
-       if (isRecordingInput())
-       {
-               stopInputRec(i);
-               return true;
-       }
-       return startInputRec(m, i);
-}
-
-/* -------------------------------------------------------------------------- */
-
-bool canEnableRecOnSignal() { return !clock::isRunning(); }
-bool canEnableFreeInputRec() { return !mh::hasAudioData(); }
-
-void refreshInputRecMode()
-{
-       if (!canEnableFreeInputRec())
-               conf::conf.inputRecMode = InputRecMode::RIGID;
-}
-} // namespace giada::m::recManager
\ No newline at end of file
diff --git a/src/core/recManager.h b/src/core/recManager.h
deleted file mode 100644 (file)
index 1dd4971..0000000
+++ /dev/null
@@ -1,65 +0,0 @@
-/* -----------------------------------------------------------------------------
- *
- * Giada - Your Hardcore Loopmachine
- *
- * -----------------------------------------------------------------------------
- *
- * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
- *
- * This file is part of Giada - Your Hardcore Loopmachine.
- *
- * Giada - Your Hardcore Loopmachine is free software: you can
- * redistribute it and/or modify it under the terms of the GNU General
- * Public License as published by the Free Software Foundation, either
- * version 3 of the License, or (at your option) any later version.
- *
- * Giada - Your Hardcore Loopmachine is distributed in the hope that it
- * will be useful, but WITHOUT ANY WARRANTY; without even the implied
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Giada - Your Hardcore Loopmachine. If not, see
- * <http://www.gnu.org/licenses/>.
- *
- * -------------------------------------------------------------------------- */
-
-#ifndef G_REC_MANAGER_H
-#define G_REC_MANAGER_H
-
-#include "core/types.h"
-
-namespace giada::m::recManager
-{
-bool isRecording();
-bool isRecordingAction();
-bool isRecordingInput();
-
-void startActionRec(RecTriggerMode);
-void stopActionRec();
-void toggleActionRec(RecTriggerMode);
-
-bool startInputRec(RecTriggerMode, InputRecMode);
-void stopInputRec(InputRecMode);
-bool toggleInputRec(RecTriggerMode, InputRecMode);
-
-/* canEnableRecOnSignal
-True if rec-on-signal can be enabled: can't set it while sequencer is running,
-in order to prevent mistakes while live recording. */
-
-bool canEnableRecOnSignal();
-
-/* canEnableFreeInputRec
-True if free loop-length can be enabled: Can't set it if there's already a 
-filled Sample Channel in the current project. */
-
-bool canEnableFreeInputRec();
-
-/* refreshInputRecMode
-Makes sure the input rec mode stays the right one when a new Sample Channel is
-filled with data. See canEnableFreeInputRec() rationale. */
-
-void refreshInputRecMode();
-} // namespace giada::m::recManager
-
-#endif
index bfee13a8b36238fc5fe46ff7e0c2ab3d52aaf36b..d131ae73e208afc8148bd886a87c4a6e3beb1777 100644 (file)
  * -------------------------------------------------------------------------- */
 
 #include "core/recorder.h"
-#include "core/action.h"
-#include "core/idManager.h"
+#include "core/mixerHandler.h"
 #include "core/model/model.h"
-#include "utils/log.h"
-#include <algorithm>
-#include <cassert>
-#include <memory>
+#include "core/sequencer.h"
+#include "core/types.h"
+#include "src/core/actions/actionRecorder.h"
+#include "src/core/actions/actions.h"
 
-namespace giada::m::recorder
+namespace giada::m
 {
-namespace
+Recorder::Recorder(model::Model& m, Sequencer& s, MixerHandler& mh)
+: m_model(m)
+, m_sequencer(s)
+, m_mixerHandler(mh)
 {
-IdManager actionId_;
-
-/* -------------------------------------------------------------------------- */
-
-Action* findAction_(ActionMap& src, ID id)
-{
-       for (auto& [frame, actions] : src)
-               for (Action& a : actions)
-                       if (a.id == id)
-                               return &a;
-       assert(false);
-       return nullptr;
-}
-
-/* -------------------------------------------------------------------------- */
-
-/* updateMapPointers_
-Updates all prev/next actions pointers into the action map. This is required
-after an action has been recorded, since pushing back new actions in a Action 
-vector makes it reallocating the existing ones. */
-
-void updateMapPointers_(ActionMap& src)
-{
-       for (auto& kv : src)
-       {
-               for (Action& action : kv.second)
-               {
-                       if (action.nextId != 0)
-                               action.next = findAction_(src, action.nextId);
-                       if (action.prevId != 0)
-                               action.prev = findAction_(src, action.prevId);
-               }
-       }
 }
 
 /* -------------------------------------------------------------------------- */
 
-/* optimize
-Removes frames without actions. */
-
-void optimize_(ActionMap& map)
+bool Recorder::isRecording() const
 {
-       for (auto it = map.cbegin(); it != map.cend();)
-               it->second.size() == 0 ? it = map.erase(it) : ++it;
+       return isRecordingAction() || isRecordingInput();
 }
 
-/* -------------------------------------------------------------------------- */
-
-void removeIf_(std::function<bool(const Action&)> f)
+bool Recorder::isRecordingAction() const
 {
-       model::DataLock lock;
-
-       ActionMap& map = model::getAll<model::Actions>();
-       for (auto& [frame, actions] : map)
-               actions.erase(std::remove_if(actions.begin(), actions.end(), f), actions.end());
-       optimize_(map);
-       updateMapPointers_(map);
+       return m_model.get().recorder.a_isRecordingAction();
 }
 
-/* -------------------------------------------------------------------------- */
-
-bool exists_(ID channelId, Frame frame, const MidiEvent& event, const ActionMap& target)
-{
-       for (const auto& [_, actions] : target)
-               for (const Action& a : actions)
-                       if (a.channelId == channelId && a.frame == frame && a.event.getRaw() == event.getRaw())
-                               return true;
-       return false;
-}
-
-bool exists_(ID channelId, Frame frame, const MidiEvent& event)
-{
-       return exists_(channelId, frame, event, model::getAll<model::Actions>());
-}
-} // namespace
-
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-
-void init()
-{
-       actionId_ = IdManager();
-       clearAll();
-}
-
-/* -------------------------------------------------------------------------- */
-
-void clearAll()
-{
-       model::DataLock lock;
-       model::getAll<model::Actions>().clear();
-}
-
-/* -------------------------------------------------------------------------- */
-
-void clearChannel(ID channelId)
-{
-       removeIf_([=](const Action& a) { return a.channelId == channelId; });
-}
-
-/* -------------------------------------------------------------------------- */
-
-void clearActions(ID channelId, int type)
+bool Recorder::isRecordingInput() const
 {
-       removeIf_([=](const Action& a) {
-               return a.channelId == channelId && a.event.getStatus() == type;
-       });
+       return m_model.get().recorder.a_isRecordingInput();
 }
 
 /* -------------------------------------------------------------------------- */
 
-void deleteAction(ID id)
+void Recorder::prepareActionRec(RecTriggerMode mode)
 {
-       removeIf_([=](const Action& a) { return a.id == id; });
-}
-
-void deleteAction(ID currId, ID nextId)
-{
-       removeIf_([=](const Action& a) { return a.id == currId || a.id == nextId; });
+       if (mode == RecTriggerMode::NORMAL)
+       {
+               startActionRec();
+               m_sequencer.setStatus(SeqStatus::RUNNING);
+               G_DEBUG("Start action rec, NORMAL mode");
+       }
+       else
+       { // RecTriggerMode::SIGNAL
+               m_sequencer.setStatus(SeqStatus::WAITING);
+               G_DEBUG("Start action rec, SIGNAL mode (waiting for signal from Midi Dispatcher...)");
+       }
 }
 
 /* -------------------------------------------------------------------------- */
 
-void updateKeyFrames(std::function<Frame(Frame old)> f)
+void Recorder::stopActionRec(ActionRecorder& actionRecorder)
 {
-       recorder::ActionMap temp;
+       setRecordingAction(false);
 
-       /* Copy all existing actions in local map by cloning them, with just a
-       difference: they have a new frame value. */
+       /* If you stop the Action Recorder in SIGNAL mode before any actual 
+       recording: just clean up everything and return. */
 
-       for (const auto& [oldFrame, actions] : model::getAll<model::Actions>())
+       if (m_sequencer.getStatus() == SeqStatus::WAITING)
        {
-               Frame newFrame = f(oldFrame);
-               for (const Action& a : actions)
-               {
-                       Action copy = a;
-                       copy.frame  = newFrame;
-                       temp[newFrame].push_back(copy);
-               }
-               G_DEBUG(oldFrame << " -> " << newFrame);
+               m_sequencer.setStatus(SeqStatus::STOPPED);
+               return;
        }
 
-       updateMapPointers_(temp);
+       std::unordered_set<ID> channels = actionRecorder.consolidate();
 
-       model::DataLock lock;
-       model::getAll<model::Actions>() = std::move(temp);
-}
+       /* Enable reading actions for Channels that have just been filled with 
+       actions. Start reading right away, without checking whether 
+       conf::treatRecsAsLoops is enabled or not. Same thing for MIDI channels.  */
 
-/* -------------------------------------------------------------------------- */
-
-void updateEvent(ID id, MidiEvent e)
-{
-       model::DataLock lock;
-       findAction_(model::getAll<model::Actions>(), id)->event = e;
+       for (ID id : channels)
+       {
+               Channel& ch = m_model.get().getChannel(id);
+               ch.shared->readActions.store(true);
+               ch.shared->recStatus.store(ChannelStatus::PLAY);
+               if (ch.type == ChannelType::MIDI)
+                       ch.shared->playStatus.store(ChannelStatus::PLAY);
+       }
+       m_model.swap(model::SwapType::HARD);
 }
 
 /* -------------------------------------------------------------------------- */
 
-void updateSiblings(ID id, ID prevId, ID nextId)
+bool Recorder::prepareInputRec(RecTriggerMode triggerMode, InputRecMode inputMode)
 {
-       model::DataLock lock;
-
-       Action* pcurr = findAction_(model::getAll<model::Actions>(), id);
-       Action* pprev = findAction_(model::getAll<model::Actions>(), prevId);
-       Action* pnext = findAction_(model::getAll<model::Actions>(), nextId);
+       if (inputMode == InputRecMode::FREE)
+               m_sequencer.rewind();
 
-       pcurr->prev   = pprev;
-       pcurr->prevId = pprev->id;
-       pcurr->next   = pnext;
-       pcurr->nextId = pnext->id;
-
-       if (pprev != nullptr)
+       if (triggerMode == RecTriggerMode::NORMAL)
        {
-               pprev->next   = pcurr;
-               pprev->nextId = pcurr->id;
+               startInputRec();
+               m_sequencer.setStatus(SeqStatus::RUNNING);
+               G_DEBUG("Start input rec, NORMAL mode");
        }
-       if (pnext != nullptr)
+       else
        {
-               pnext->prev   = pcurr;
-               pnext->prevId = pcurr->id;
+               m_sequencer.setStatus(SeqStatus::WAITING);
+               G_DEBUG("Start input rec, SIGNAL mode (waiting for signal from Mixer...)");
        }
-}
 
-/* -------------------------------------------------------------------------- */
-
-bool hasActions(ID channelId, int type)
-{
-       for (const auto& [frame, actions] : model::getAll<model::Actions>())
-               for (const Action& a : actions)
-                       if (a.channelId == channelId && (type == 0 || type == a.event.getStatus()))
-                               return true;
-       return false;
+       return true;
 }
 
 /* -------------------------------------------------------------------------- */
 
-Action makeAction(ID id, ID channelId, Frame frame, MidiEvent e)
+void Recorder::stopInputRec(InputRecMode recMode, int sampleRate)
 {
-       Action out{actionId_.generate(id), channelId, frame, e, -1, -1};
-       actionId_.set(id);
-       return out;
-}
+       setRecordingInput(false);
 
-Action makeAction(const patch::Action& a)
-{
-       actionId_.set(a.id);
-       return Action{a.id, a.channelId, a.frame, a.event, -1, -1, a.prevId,
-           a.nextId};
-}
+       Frame recordedFrames = m_mixerHandler.stopInputRec();
 
-/* -------------------------------------------------------------------------- */
+       /* When recording in RIGID mode, the amount of recorded frames is always 
+       equal to the current loop length. */
 
-Action rec(ID channelId, Frame frame, MidiEvent event)
-{
-       /* Skip duplicates. */
+       if (recMode == InputRecMode::RIGID)
+               recordedFrames = m_sequencer.getFramesInLoop();
 
-       if (exists_(channelId, frame, event))
-               return {};
+       G_DEBUG("Stop input rec, recordedFrames=" << recordedFrames);
 
-       Action a = makeAction(0, channelId, frame, event);
+       /* If you stop the Input Recorder in SIGNAL mode before any actual 
+       recording: just clean up everything and return. */
 
-       /* If key frame doesn't exist yet, the [] operator in std::map is smart 
-       enough to insert a new item first. No plug-in data for now. */
+       if (m_sequencer.getStatus() == SeqStatus::WAITING)
+       {
+               m_sequencer.setStatus(SeqStatus::STOPPED);
+               return;
+       }
 
-       model::DataLock lock;
+       /* Finalize recordings. InputRecMode::FREE requires some adjustments. */
 
-       model::getAll<model::Actions>()[frame].push_back(a);
-       updateMapPointers_(model::getAll<model::Actions>());
+       m_mixerHandler.finalizeInputRec(recordedFrames, m_sequencer.getCurrentFrame());
 
-       return a;
+       if (recMode == InputRecMode::FREE)
+       {
+               m_sequencer.rewind();
+               m_sequencer.setBpm(m_sequencer.calcBpmFromRec(recordedFrames, sampleRate), sampleRate);
+       }
 }
 
 /* -------------------------------------------------------------------------- */
 
-void rec(std::vector<Action>& actions)
-{
-       if (actions.size() == 0)
-               return;
-
-       model::DataLock lock;
+bool Recorder::canEnableRecOnSignal() const { return !m_sequencer.isRunning(); }
+bool Recorder::canEnableFreeInputRec() const { return !m_mixerHandler.hasAudioData(); }
 
-       ActionMap& map = model::getAll<model::Actions>();
+/* -------------------------------------------------------------------------- */
 
-       for (const Action& a : actions)
-               if (!exists_(a.channelId, a.frame, a.event, map))
-                       map[a.frame].push_back(a);
-       updateMapPointers_(map);
+bool Recorder::canRecordActions() const
+{
+       return isRecordingAction() && m_sequencer.isRunning() && !isRecordingInput();
 }
 
 /* -------------------------------------------------------------------------- */
 
-void rec(ID channelId, Frame f1, Frame f2, MidiEvent e1, MidiEvent e2)
+void Recorder::setRecordingAction(bool v)
 {
-       model::DataLock lock;
-
-       ActionMap& map = model::getAll<model::Actions>();
-
-       map[f1].push_back(makeAction(0, channelId, f1, e1));
-       map[f2].push_back(makeAction(0, channelId, f2, e2));
-
-       Action* a1 = findAction_(map, map[f1].back().id);
-       Action* a2 = findAction_(map, map[f2].back().id);
-       a1->nextId = a2->id;
-       a2->prevId = a1->id;
-
-       updateMapPointers_(map);
+       m_model.get().recorder.a_setRecordingAction(v);
 }
 
-/* -------------------------------------------------------------------------- */
-
-const std::vector<Action>* getActionsOnFrame(Frame frame)
+void Recorder::setRecordingInput(bool v)
 {
-       if (model::getAll<model::Actions>().count(frame) == 0)
-               return nullptr;
-       return &model::getAll<model::Actions>().at(frame);
+       m_model.get().recorder.a_setRecordingInput(v);
 }
 
 /* -------------------------------------------------------------------------- */
 
-Action getClosestAction(ID channelId, Frame f, int type)
+void Recorder::startActionRec()
 {
-       Action out = {};
-       forEachAction([&](const Action& a) {
-               if (a.event.getStatus() != type || a.channelId != channelId)
-                       return;
-               if (!out.isValid() || (a.frame <= f && a.frame > out.frame))
-                       out = a;
-       });
-       return out;
+       setRecordingAction(true);
 }
 
 /* -------------------------------------------------------------------------- */
 
-std::vector<Action> getActionsOnChannel(ID channelId)
+void Recorder::startActionRecOnCallback()
 {
-       std::vector<Action> out;
-       forEachAction([&](const Action& a) {
-               if (a.channelId == channelId)
-                       out.push_back(a);
-       });
-       return out;
+       if (m_sequencer.getStatus() != SeqStatus::WAITING)
+               return;
+       startActionRec();
+       m_sequencer.setStatus(SeqStatus::RUNNING);
 }
 
 /* -------------------------------------------------------------------------- */
 
-void forEachAction(std::function<void(const Action&)> f)
+void Recorder::startInputRec()
 {
-       for (auto& [_, actions] : model::getAll<model::Actions>())
-               for (const Action& action : actions)
-                       f(action);
+       /* Start recording from the current frame, not the beginning. */
+       m_mixerHandler.startInputRec(m_sequencer.getCurrentFrame());
+       setRecordingInput(true);
 }
 
 /* -------------------------------------------------------------------------- */
 
-ID getNewActionId()
+void Recorder::startInputRecOnCallback()
 {
-       return actionId_.generate();
+       if (m_sequencer.getStatus() != SeqStatus::WAITING)
+               return;
+       startInputRec();
+       m_sequencer.setStatus(SeqStatus::RUNNING);
 }
-} // namespace giada::m::recorder
+} // namespace giada::m
\ No newline at end of file
index d20b4ada433809e53bd5a7d6348897f7db0688f4..f98a49be067a26491ed0c60c32ebfa866ec97438 100644 (file)
  *
  * -------------------------------------------------------------------------- */
 
-#ifndef G_RECORDER_H
-#define G_RECORDER_H
+#ifndef G_REC_MANAGER_H
+#define G_REC_MANAGER_H
 
-#include "core/action.h"
-#include "core/midiEvent.h"
-#include "core/patch.h"
 #include "core/types.h"
-#include <functional>
-#include <map>
-#include <memory>
-#include <vector>
 
-namespace giada::m::recorder
+namespace giada::m::model
 {
-using ActionMap = std::map<Frame, std::vector<Action>>;
+class Model;
+}
 
-/* init
-Initializes the recorder: everything starts from here. */
-
-void init();
-
-/* clearAll
-Deletes all recorded actions. */
-
-void clearAll();
-
-/* clearChannel
-Clears all actions from a channel. */
-
-void clearChannel(ID channelId);
-
-/* clearActions
-Clears the actions by type from a channel. */
-
-void clearActions(ID channelId, int type);
-
-/* deleteAction (1)
-Deletes a specific action. */
-
-void deleteAction(ID id);
-
-/* deleteAction (2)
-Deletes a specific pair of actions. Useful for composite stuff (i.e. MIDI). */
-
-void deleteAction(ID currId, ID nextId);
-
-/* updateKeyFrames
-Update all the key frames in the internal map of actions, according to a lambda 
-function 'f'. */
-
-void updateKeyFrames(std::function<Frame(Frame old)> f);
-
-/* updateEvent
-Changes the event in action 'a'. */
-
-void updateEvent(ID id, MidiEvent e);
-
-/* updateSiblings
-Changes previous and next actions in action with id 'id'. Mostly used for 
-chained actions such as envelopes. */
-
-void updateSiblings(ID id, ID prevId, ID nextId);
-
-/* hasActions
-Checks if the channel has at least one action recorded. */
-
-bool hasActions(ID channelId, int type = 0);
-
-/* makeAction
-Makes a new action given some data. */
-
-Action makeAction(ID id, ID channelId, Frame frame, MidiEvent e);
-Action makeAction(const patch::Action& a);
-
-/* rec (1)
-Records an action and returns it. Used by the Action Editor. */
-
-Action rec(ID channelId, Frame frame, MidiEvent e);
-
-/* rec (2)
-Transfer a vector of actions into the current ActionMap. This is called by 
-recordHandler when a live session is over and consolidation is required. */
-
-void rec(std::vector<Action>& actions);
-
-/* rec (3)
-Records two actions on channel 'channel'. Useful when recording composite 
-actions in the Action Editor. */
+namespace giada::m
+{
+class ActionRecorder;
+class MixerHandler;
+class Sequencer;
+class Recorder final
+{
+public:
+       Recorder(model::Model&, Sequencer&, MixerHandler&);
 
-void rec(ID channelId, Frame f1, Frame f2, MidiEvent e1, MidiEvent e2);
+       bool isRecording() const;
+       bool isRecordingAction() const;
+       bool isRecordingInput() const;
 
-/* forEachAction
-Applies a read-only callback on each action recorded. NEVER do anything inside 
-the callback that might alter the ActionMap. */
+       /* canEnableRecOnSignal
+    True if rec-on-signal can be enabled: can't set it while sequencer is 
+    running, in order to prevent mistakes while live recording. */
 
-void forEachAction(std::function<void(const Action&)> f);
+       bool canEnableRecOnSignal() const;
 
-/* getActionsOnFrame
-Returns a pointer to a vector of actions recorded on frame 'f', or nullptr if
-the frame has no actions. */
+       /* canEnableFreeInputRec
+    True if free loop-length can be enabled: Can't set it if there's already a 
+    filled Sample Channel in the current project. */
 
-const std::vector<Action>* getActionsOnFrame(Frame f);
+       bool canEnableFreeInputRec() const;
 
-/* getActionsOnChannel
-Returns a vector of actions belonging to channel 'ch'. */
+       /* canRecordActions
+       True if actions are recordable right now. */
 
-std::vector<Action> getActionsOnChannel(ID channelId);
+       bool canRecordActions() const;
 
-/* getClosestAction
-Given a frame 'f' returns the closest action. */
+       void prepareActionRec(RecTriggerMode);
+       void startActionRec();
+       void startActionRecOnCallback();
+       void stopActionRec(ActionRecorder&);
 
-Action getClosestAction(ID channelId, Frame f, int type);
+       bool prepareInputRec(RecTriggerMode, InputRecMode);
+       void startInputRec();
+       void startInputRecOnCallback();
+       void stopInputRec(InputRecMode, int sampleRate);
 
-/* getNewActionId
-Returns a new action ID, internally generated. */
+private:
+       void setRecordingAction(bool v);
+       void setRecordingInput(bool v);
 
-ID getNewActionId();
-} // namespace giada::m::recorder
+       model::Model& m_model;
+       Sequencer&    m_sequencer;
+       MixerHandler& m_mixerHandler;
+};
+} // namespace giada::m
 
 #endif
diff --git a/src/core/recorderHandler.cpp b/src/core/recorderHandler.cpp
deleted file mode 100644 (file)
index 60ab6db..0000000
+++ /dev/null
@@ -1,297 +0,0 @@
-/* -----------------------------------------------------------------------------
- *
- * Giada - Your Hardcore Loopmachine
- *
- * -----------------------------------------------------------------------------
- *
- * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
- *
- * This file is part of Giada - Your Hardcore Loopmachine.
- *
- * Giada - Your Hardcore Loopmachine is free software: you can
- * redistribute it and/or modify it under the terms of the GNU General
- * Public License as published by the Free Software Foundation, either
- * version 3 of the License, or (at your option) any later version.
- *
- * Giada - Your Hardcore Loopmachine is distributed in the hope that it
- * will be useful, but WITHOUT ANY WARRANTY; without even the implied
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Giada - Your Hardcore Loopmachine. If not, see
- * <http://www.gnu.org/licenses/>.
- *
- * -------------------------------------------------------------------------- */
-
-#include "recorderHandler.h"
-#include "action.h"
-#include "clock.h"
-#include "const.h"
-#include "model/model.h"
-#include "patch.h"
-#include "recorder.h"
-#include "utils/log.h"
-#include "utils/ver.h"
-#include <algorithm>
-#include <cassert>
-#include <cmath>
-#include <unordered_map>
-
-namespace giada::m::recorderHandler
-{
-namespace
-{
-constexpr int MAX_LIVE_RECS_CHUNK = 128;
-
-std::vector<Action> recs_;
-
-/* -------------------------------------------------------------------------- */
-
-const Action* getActionPtrById_(int id, const recorder::ActionMap& source)
-{
-       for (const auto& [_, actions] : source)
-               for (const Action& action : actions)
-                       if (action.id == id)
-                               return &action;
-       return nullptr;
-}
-
-/* -------------------------------------------------------------------------- */
-
-/* areComposite_
-Composite: NOTE_ON + NOTE_OFF on the same note. */
-
-bool areComposite_(const Action& a1, const Action& a2)
-{
-       return a1.event.getStatus() == MidiEvent::NOTE_ON &&
-              a2.event.getStatus() == MidiEvent::NOTE_OFF &&
-              a1.event.getNote() == a2.event.getNote() &&
-              a1.channelId == a2.channelId;
-}
-
-/* -------------------------------------------------------------------------- */
-
-/* consolidate_
-Given an action 'a1' tries to find the matching NOTE_OFF. This algorithm must
-start searching from the element next to 'a1': since live actions are recorded
-in linear sequence, the potential partner of 'a1' always lies beyond a1 itself. 
-Without this trick (i.e. if it loops from vector.begin() each time) the
-algorithm would end up matching wrong partners. */
-
-void consolidate_(const Action& a1, std::size_t i)
-{
-       for (auto it = recs_.begin() + i; it != recs_.end(); ++it)
-       {
-
-               const Action& a2 = *it;
-
-               if (!areComposite_(a1, a2))
-                       continue;
-
-               const_cast<Action&>(a1).nextId = a2.id;
-               const_cast<Action&>(a2).prevId = a1.id;
-
-               break;
-       }
-}
-
-/* -------------------------------------------------------------------------- */
-
-void consolidate_()
-{
-       for (auto it = recs_.begin(); it != recs_.end(); ++it)
-               consolidate_(*it, it - recs_.begin()); // Pass current index
-}
-} // namespace
-
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-
-void init()
-{
-       recs_.reserve(MAX_LIVE_RECS_CHUNK);
-}
-
-/* -------------------------------------------------------------------------- */
-
-bool isBoundaryEnvelopeAction(const Action& a)
-{
-       assert(a.prev != nullptr);
-       assert(a.next != nullptr);
-       return a.prev->frame > a.frame || a.next->frame < a.frame;
-}
-
-/* -------------------------------------------------------------------------- */
-
-void updateBpm(float ratio, int quantizerStep)
-{
-       if (ratio == 1.0f)
-               return;
-
-       recorder::updateKeyFrames([=](Frame old) {
-               /* The division here cannot be precise. A new frame can be 44099 and the 
-               quantizer set to 44100. That would mean two recs completely useless. So we 
-               compute a reject value ('delta'): if it's lower than 6 frames the new frame 
-               is collapsed with a quantized frame. FIXME - maybe 6 frames are too low. */
-               Frame frame = static_cast<Frame>(old * ratio);
-               if (frame != 0)
-               {
-                       Frame delta = quantizerStep % frame;
-                       if (delta > 0 && delta <= 6)
-                               frame = frame + delta;
-               }
-               return frame;
-       });
-}
-
-/* -------------------------------------------------------------------------- */
-
-void updateSamplerate(int systemRate, int patchRate)
-{
-       if (systemRate == patchRate)
-               return;
-
-       float ratio = systemRate / (float)patchRate;
-
-       recorder::updateKeyFrames([=](Frame old) { return floorf(old * ratio); });
-}
-
-/* -------------------------------------------------------------------------- */
-
-bool cloneActions(ID channelId, ID newChannelId)
-{
-       bool                       cloned = false;
-       std::vector<Action>        actions;
-       std::unordered_map<ID, ID> map; // Action ID mapper, old -> new
-
-       recorder::forEachAction([&](const Action& a) {
-               if (a.channelId != channelId)
-                       return;
-
-               ID newActionId = recorder::getNewActionId();
-
-               map.insert({a.id, newActionId});
-
-               Action clone(a);
-               clone.id        = newActionId;
-               clone.channelId = newChannelId;
-
-               actions.push_back(clone);
-               cloned = true;
-       });
-
-       /* Update nextId and prevId relationships given the new action ID. */
-
-       for (Action& a : actions)
-       {
-               if (a.prevId != 0)
-                       a.prevId = map.at(a.prevId);
-               if (a.nextId != 0)
-                       a.nextId = map.at(a.nextId);
-       }
-
-       recorder::rec(actions);
-
-       return cloned;
-}
-
-/* -------------------------------------------------------------------------- */
-
-void liveRec(ID channelId, MidiEvent e, Frame globalFrame)
-{
-       assert(e.isNoteOnOff()); // Can't record any other kind of events for now
-
-       /* TODO - this might allocate on the MIDI thread */
-       if (recs_.size() >= recs_.capacity())
-               recs_.reserve(recs_.size() + MAX_LIVE_RECS_CHUNK);
-
-       recs_.push_back(recorder::makeAction(recorder::getNewActionId(), channelId, globalFrame, e));
-}
-
-/* -------------------------------------------------------------------------- */
-
-std::unordered_set<ID> consolidate()
-{
-       consolidate_();
-       recorder::rec(recs_);
-
-       std::unordered_set<ID> out;
-       for (const Action& action : recs_)
-               out.insert(action.channelId);
-
-       recs_.clear();
-       return out;
-}
-
-/* -------------------------------------------------------------------------- */
-
-void clearAllActions()
-{
-       for (channel::Data& ch : model::get().channels)
-               ch.hasActions = false;
-
-       model::swap(model::SwapType::HARD);
-
-       recorder::clearAll();
-}
-
-/* -------------------------------------------------------------------------- */
-
-recorder::ActionMap deserializeActions(const std::vector<patch::Action>& pactions)
-{
-       recorder::ActionMap out;
-
-       /* First pass: add actions with no relationship, that is with no prev/next
-       pointers filled in. */
-
-       for (const patch::Action& paction : pactions)
-               out[paction.frame].push_back(recorder::makeAction(paction));
-
-       /* Second pass: fill in previous and next actions, if any. Is this the
-       fastest/smartest way to do it? Maybe not. Optimizations are welcome. */
-
-       for (const patch::Action& paction : pactions)
-       {
-               if (paction.nextId == 0 && paction.prevId == 0)
-                       continue;
-               Action* curr = const_cast<Action*>(getActionPtrById_(paction.id, out));
-               assert(curr != nullptr);
-               if (paction.nextId != 0)
-               {
-                       curr->next = getActionPtrById_(paction.nextId, out);
-                       assert(curr->next != nullptr);
-               }
-               if (paction.prevId != 0)
-               {
-                       curr->prev = getActionPtrById_(paction.prevId, out);
-                       assert(curr->prev != nullptr);
-               }
-       }
-
-       return out;
-}
-
-/* -------------------------------------------------------------------------- */
-
-std::vector<patch::Action> serializeActions(const recorder::ActionMap& actions)
-{
-       std::vector<patch::Action> out;
-       for (const auto& kv : actions)
-       {
-               for (const Action& a : kv.second)
-               {
-                       out.push_back({
-                           a.id,
-                           a.channelId,
-                           a.frame,
-                           a.event.getRaw(),
-                           a.prevId,
-                           a.nextId,
-                       });
-               }
-       }
-       return out;
-}
-} // namespace giada::m::recorderHandler
\ No newline at end of file
diff --git a/src/core/recorderHandler.h b/src/core/recorderHandler.h
deleted file mode 100644 (file)
index 90f7eee..0000000
+++ /dev/null
@@ -1,90 +0,0 @@
-/* -----------------------------------------------------------------------------
- *
- * Giada - Your Hardcore Loopmachine
- *
- * -----------------------------------------------------------------------------
- *
- * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
- *
- * This file is part of Giada - Your Hardcore Loopmachine.
- *
- * Giada - Your Hardcore Loopmachine is free software: you can
- * redistribute it and/or modify it under the terms of the GNU General
- * Public License as published by the Free Software Foundation, either
- * version 3 of the License, or (at your option) any later version.
- *
- * Giada - Your Hardcore Loopmachine is distributed in the hope that it
- * will be useful, but WITHOUT ANY WARRANTY; without even the implied
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Giada - Your Hardcore Loopmachine. If not, see
- * <http://www.gnu.org/licenses/>.
- *
- * -------------------------------------------------------------------------- */
-
-#ifndef G_RECORDER_HANDLER_H
-#define G_RECORDER_HANDLER_H
-
-#include "core/midiEvent.h"
-#include "core/recorder.h"
-#include "core/types.h"
-#include <unordered_set>
-
-namespace giada::m::patch
-{
-struct Action;
-}
-namespace giada::m
-{
-struct Action;
-}
-namespace giada::m::recorderHandler
-{
-void init();
-
-bool isBoundaryEnvelopeAction(const Action& a);
-
-/* updateBpm
-Changes actions position by calculating the new bpm value. */
-
-void updateBpm(float ratio, int quantizerStep);
-
-/* updateSamplerate
-Changes actions position by taking in account the new samplerate. If 
-f_system == f_patch nothing will change, otherwise the conversion is 
-mandatory. */
-
-void updateSamplerate(int systemRate, int patchRate);
-
-/* cloneActions
-Clones actions in channel 'channelId', giving them a new channel ID. Returns
-whether any action has been cloned. */
-
-bool cloneActions(ID channelId, ID newChannelId);
-
-/* liveRec
-Records a user-generated action. NOTE_ON or NOTE_OFF only for now. */
-
-void liveRec(ID channelId, MidiEvent e, Frame global);
-
-/* consolidate
-Records all live actions. Returns a set of channels IDs that have been 
-recorded. */
-
-std::unordered_set<ID> consolidate();
-
-/* clearAllActions
-Deletes all recorded actions. */
-
-void clearAllActions();
-
-/* (de)serializeActions
-Creates new Actions given the patch raw data and vice versa. */
-
-recorder::ActionMap        deserializeActions(const std::vector<patch::Action>& as);
-std::vector<patch::Action> serializeActions(const recorder::ActionMap& as);
-} // namespace giada::m::recorderHandler
-
-#endif
index 6425e7f37db745c01a5310b345f0846bdc50b086..fa602097b207257fdd97bf46e3631dd204e39b76 100644 (file)
  *
  * -------------------------------------------------------------------------- */
 
-#include "sequencer.h"
-#include "core/clock.h"
-#include "core/conf.h"
-#include "core/const.h"
+#include "core/sequencer.h"
+#include "core/actions/actionRecorder.h"
+#include "core/jackTransport.h"
 #include "core/kernelAudio.h"
 #include "core/metronome.h"
-#include "core/mixer.h"
 #include "core/model/model.h"
 #include "core/quantizer.h"
-#include "core/recManager.h"
+#include "core/synchronizer.h"
+#include "utils/log.h"
+#include "utils/math.h"
 
-namespace giada::m::sequencer
+namespace giada::m
 {
 namespace
 {
 constexpr int Q_ACTION_REWIND = 0;
+} // namespace
 
-/* eventBuffer_
-Buffer of events found in each block sent to channels for event parsing. This is 
-filled during react(). */
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
 
-EventBuffer eventBuffer_;
+Sequencer::Sequencer(model::Model& m, Synchronizer& s, JackTransport& j)
+: onAboutStart(nullptr)
+, onAboutStop(nullptr)
+, m_model(m)
+, m_synchronizer(s)
+, m_jackTransport(j)
+, m_quantizerStep(1)
+{
+       quantizer.schedule(Q_ACTION_REWIND, [this](Frame delta) { rewindQ(delta); });
+}
+/* -------------------------------------------------------------------------- */
 
-Metronome metronome_;
+bool      Sequencer::canQuantize() const { return m_model.get().sequencer.canQuantize(); }
+bool      Sequencer::isRunning() const { return m_model.get().sequencer.isRunning(); }
+bool      Sequencer::isActive() const { return m_model.get().sequencer.isActive(); }
+bool      Sequencer::isOnBar() const { return m_model.get().sequencer.a_isOnBar(); }
+bool      Sequencer::isOnBeat() const { return m_model.get().sequencer.a_isOnBeat(); }
+bool      Sequencer::isOnFirstBeat() const { return m_model.get().sequencer.a_isOnFirstBeat(); }
+float     Sequencer::getBpm() const { return m_model.get().sequencer.bpm; }
+int       Sequencer::getBeats() const { return m_model.get().sequencer.beats; }
+int       Sequencer::getBars() const { return m_model.get().sequencer.bars; }
+int       Sequencer::getCurrentBeat() const { return m_model.get().sequencer.a_getCurrentBeat(); }
+Frame     Sequencer::getCurrentFrame() const { return m_model.get().sequencer.a_getCurrentFrame(); }
+Frame     Sequencer::getCurrentFrameQuantized() const { return quantize(getCurrentFrame()); }
+float     Sequencer::getCurrentSecond(int sampleRate) const { return getCurrentFrame() / static_cast<float>(sampleRate); }
+Frame     Sequencer::getFramesInBar() const { return m_model.get().sequencer.framesInBar; }
+Frame     Sequencer::getFramesInBeat() const { return m_model.get().sequencer.framesInBeat; }
+Frame     Sequencer::getFramesInLoop() const { return m_model.get().sequencer.framesInLoop; }
+Frame     Sequencer::getFramesInSeq() const { return m_model.get().sequencer.framesInSeq; }
+int       Sequencer::getQuantizerValue() const { return m_model.get().sequencer.quantize; }
+int       Sequencer::getQuantizerStep() const { return m_quantizerStep; }
+SeqStatus Sequencer::getStatus() const { return m_model.get().sequencer.status; }
 
 /* -------------------------------------------------------------------------- */
 
-void rewindQ_(Frame delta)
+Frame Sequencer::getMaxFramesInLoop(int sampleRate) const
 {
-       clock::rewind();
-       eventBuffer_.push_back({EventType::REWIND, 0, delta});
+       return (sampleRate * (60.0f / G_MIN_BPM)) * getBeats();
 }
-} // namespace
 
 /* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
+
+float Sequencer::calcBpmFromRec(Frame recordedFrames, int sampleRate) const
+{
+       return (60.0f * getBeats()) / (recordedFrames / static_cast<float>(sampleRate));
+}
+
 /* -------------------------------------------------------------------------- */
 
-Quantizer quantizer;
+Frame Sequencer::quantize(Frame f) const
+{
+       if (!canQuantize())
+               return f;
+       return u::math::quantize(f, m_quantizerStep) % getFramesInLoop(); // No overflow
+}
 
 /* -------------------------------------------------------------------------- */
 
-void init()
+void Sequencer::reset(int sampleRate)
 {
-       quantizer.schedule(Q_ACTION_REWIND, rewindQ_);
-       clock::rewind();
+       model::Sequencer& s = m_model.get().sequencer;
+
+       s.bars     = G_DEFAULT_BARS;
+       s.beats    = G_DEFAULT_BEATS;
+       s.bpm      = G_DEFAULT_BPM;
+       s.quantize = G_DEFAULT_QUANTIZE;
+       recomputeFrames(sampleRate); // Model swap is done here, no need to call it twice
+       rewind();
 }
 
 /* -------------------------------------------------------------------------- */
 
-void react(const eventDispatcher::EventBuffer& events)
+void Sequencer::react(const EventDispatcher::EventBuffer& events)
 {
-       for (const eventDispatcher::Event& e : events)
+       for (const EventDispatcher::Event& e : events)
        {
-               if (e.type == eventDispatcher::EventType::SEQUENCER_START)
+               if (e.type == EventDispatcher::EventType::SEQUENCER_START)
                {
-                       start();
+                       if (!m_jackTransport.start())
+                               rawStart();
                        break;
                }
-               if (e.type == eventDispatcher::EventType::SEQUENCER_STOP)
+               if (e.type == EventDispatcher::EventType::SEQUENCER_STOP)
                {
-                       stop();
+                       if (!m_jackTransport.stop())
+                               rawStop();
                        break;
                }
-               if (e.type == eventDispatcher::EventType::SEQUENCER_REWIND)
+               if (e.type == EventDispatcher::EventType::SEQUENCER_REWIND)
                {
-                       rewind();
+                       if (!m_jackTransport.setPosition(0))
+                               rawRewind();
                        break;
                }
        }
@@ -98,15 +145,21 @@ void react(const eventDispatcher::EventBuffer& events)
 
 /* -------------------------------------------------------------------------- */
 
-const EventBuffer& advance(Frame bufferSize)
+const Sequencer::EventBuffer& Sequencer::advance(Frame bufferSize, const ActionRecorder& actionRecorder)
 {
-       eventBuffer_.clear();
+       m_eventBuffer.clear();
+
+       const model::Sequencer& sequencer = m_model.get().sequencer;
 
-       const Frame start        = clock::getCurrentFrame();
+       const Frame start        = sequencer.a_getCurrentFrame();
        const Frame end          = start + bufferSize;
-       const Frame framesInLoop = clock::getFramesInLoop();
-       const Frame framesInBar  = clock::getFramesInBar();
-       const Frame framesInBeat = clock::getFramesInBeat();
+       const Frame framesInLoop = sequencer.framesInLoop;
+       const Frame framesInBar  = sequencer.framesInBar;
+       const Frame framesInBeat = sequencer.framesInBeat;
+       const Frame nextFrame    = end % framesInLoop;
+       const int   nextBeat     = nextFrame / framesInBeat;
+
+       /* Process events in the current block. */
 
        for (Frame i = start, local = 0; i < end; i++, local++)
        {
@@ -115,51 +168,58 @@ const EventBuffer& advance(Frame bufferSize)
 
                if (global == 0)
                {
-                       eventBuffer_.push_back({EventType::FIRST_BEAT, global, local});
-                       metronome_.trigger(Metronome::Click::BEAT, local);
+                       m_eventBuffer.push_back({EventType::FIRST_BEAT, global, local});
+                       m_metronome.trigger(Metronome::Click::BEAT, local);
                }
                else if (global % framesInBar == 0)
                {
-                       eventBuffer_.push_back({EventType::BAR, global, local});
-                       metronome_.trigger(Metronome::Click::BAR, local);
+                       m_eventBuffer.push_back({EventType::BAR, global, local});
+                       m_metronome.trigger(Metronome::Click::BAR, local);
                }
                else if (global % framesInBeat == 0)
                {
-                       metronome_.trigger(Metronome::Click::BEAT, local);
+                       m_metronome.trigger(Metronome::Click::BEAT, local);
                }
 
-               const std::vector<Action>* as = recorder::getActionsOnFrame(global);
+               const std::vector<Action>* as = actionRecorder.getActionsOnFrame(global);
                if (as != nullptr)
-                       eventBuffer_.push_back({EventType::ACTIONS, global, local, as});
+                       m_eventBuffer.push_back({EventType::ACTIONS, global, local, as});
        }
 
-       /* Advance clock and quantizer after the event parsing. */
-       clock::advance(bufferSize);
-       quantizer.advance(Range<Frame>(start, end), clock::getQuantizerStep());
+       /* Advance this and quantizer after the event parsing. */
+
+       sequencer.a_setCurrentFrame(nextFrame);
+       sequencer.a_setCurrentBeat(nextBeat);
+       quantizer.advance(Range<Frame>(start, end), getQuantizerStep());
 
-       return eventBuffer_;
+       return m_eventBuffer;
 }
 
 /* -------------------------------------------------------------------------- */
 
-void render(AudioBuffer& outBuf)
+void Sequencer::render(mcl::AudioBuffer& outBuf)
 {
-       if (metronome_.running)
-               metronome_.render(outBuf);
+       if (m_metronome.running)
+               m_metronome.render(outBuf);
 }
 
 /* -------------------------------------------------------------------------- */
 
-void rawStart()
+void Sequencer::rawStart()
 {
-       switch (clock::getStatus())
+       assert(onAboutStart != nullptr);
+
+       const SeqStatus status = getStatus();
+
+       onAboutStart(status);
+
+       switch (status)
        {
-       case ClockStatus::STOPPED:
-               clock::setStatus(ClockStatus::RUNNING);
+       case SeqStatus::STOPPED:
+               setStatus(SeqStatus::RUNNING);
                break;
-       case ClockStatus::WAITING:
-               clock::setStatus(ClockStatus::RUNNING);
-               recManager::stopActionRec();
+       case SeqStatus::WAITING:
+               setStatus(SeqStatus::RUNNING);
                break;
        default:
                break;
@@ -168,68 +228,140 @@ void rawStart()
 
 /* -------------------------------------------------------------------------- */
 
-void rawStop()
+void Sequencer::rawStop()
 {
-       clock::setStatus(ClockStatus::STOPPED);
+       assert(onAboutStop != nullptr);
 
-       /* If recordings (both input and action) are active deactivate them, but 
-       store the takes. RecManager takes care of it. */
-
-       if (recManager::isRecordingAction())
-               recManager::stopActionRec();
-       else if (recManager::isRecordingInput())
-               recManager::stopInputRec(conf::conf.inputRecMode);
+       onAboutStop();
+       setStatus(SeqStatus::STOPPED);
 }
 
 /* -------------------------------------------------------------------------- */
 
-void rawRewind()
+void Sequencer::rawRewind()
 {
-       if (clock::canQuantize())
+       if (canQuantize())
                quantizer.trigger(Q_ACTION_REWIND);
        else
-               rewindQ_(/*delta=*/0);
+               rewindQ(/*delta=*/0);
 }
 
 /* -------------------------------------------------------------------------- */
 
-void start()
+void Sequencer::rewind()
 {
-#ifdef WITH_AUDIO_JACK
-       if (kernelAudio::getAPI() == G_SYS_API_JACK)
-               kernelAudio::jackStart();
-       else
-#endif
-               rawStart();
+       const model::Sequencer& c = m_model.get().sequencer;
+
+       c.a_setCurrentFrame(0);
+       c.a_setCurrentBeat(0);
 }
 
 /* -------------------------------------------------------------------------- */
 
-void stop()
+bool Sequencer::isMetronomeOn() const { return m_metronome.running; }
+void Sequencer::toggleMetronome() { m_metronome.running = !m_metronome.running; }
+void Sequencer::setMetronome(bool v) { m_metronome.running = v; }
+
+/* -------------------------------------------------------------------------- */
+
+void Sequencer::rewindQ(Frame delta)
 {
-#ifdef WITH_AUDIO_JACK
-       if (kernelAudio::getAPI() == G_SYS_API_JACK)
-               kernelAudio::jackStop();
-       else
-#endif
-               rawStop();
+       rewind();
+       m_eventBuffer.push_back({EventType::REWIND, 0, delta});
 }
 
 /* -------------------------------------------------------------------------- */
 
-void rewind()
+void Sequencer::recomputeFrames(int sampleRate)
 {
-#ifdef WITH_AUDIO_JACK
-       if (kernelAudio::getAPI() == G_SYS_API_JACK)
-               kernelAudio::jackSetPosition(0);
-       else
-#endif
-               rawRewind();
+       model::Sequencer& s = m_model.get().sequencer;
+
+       s.framesInLoop = static_cast<int>((sampleRate * (60.0f / s.bpm)) * s.beats);
+       s.framesInBar  = static_cast<int>(s.framesInLoop / (float)s.bars);
+       s.framesInBeat = static_cast<int>(s.framesInLoop / (float)s.beats);
+       s.framesInSeq  = s.framesInBeat * G_MAX_BEATS;
+
+       if (s.quantize != 0)
+               m_quantizerStep = s.framesInBeat / s.quantize;
+
+       m_model.swap(model::SwapType::NONE);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Sequencer::setBpm(float b, int sampleRate)
+{
+       b = std::clamp(b, G_MIN_BPM, G_MAX_BPM);
+
+       /* If JACK is being used, let it handle the bpm change. */
+       if (!m_jackTransport.setBpm(b))
+               rawSetBpm(b, sampleRate);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Sequencer::rawSetBpm(float v, int sampleRate)
+{
+       assert(onBpmChange != nullptr);
+
+       const float oldVal = m_model.get().sequencer.bpm;
+       const float newVal = v;
+
+       m_model.get().sequencer.bpm = newVal;
+       recomputeFrames(sampleRate);
+       m_model.swap(model::SwapType::HARD);
+
+       onBpmChange(oldVal, newVal, m_quantizerStep);
+
+       u::log::print("[clock::rawSetBpm] Bpm changed to %f\n", newVal);
+}
+/* -------------------------------------------------------------------------- */
+
+void Sequencer::setBeats(int newBeats, int newBars, int sampleRate)
+{
+       newBeats = std::clamp(newBeats, 1, G_MAX_BEATS);
+       newBars  = std::clamp(newBars, 1, newBeats); // Bars cannot be greater than beats
+
+       m_model.get().sequencer.beats = newBeats;
+       m_model.get().sequencer.bars  = newBars;
+       recomputeFrames(sampleRate);
+
+       m_model.swap(model::SwapType::HARD);
 }
 
 /* -------------------------------------------------------------------------- */
 
-bool isMetronomeOn() { return metronome_.running; }
-void toggleMetronome() { metronome_.running = !metronome_.running; }
-void setMetronome(bool v) { metronome_.running = v; }
-} // namespace giada::m::sequencer
\ No newline at end of file
+void Sequencer::setQuantize(int q, int sampleRate)
+{
+       m_model.get().sequencer.quantize = q;
+       recomputeFrames(sampleRate);
+
+       m_model.swap(model::SwapType::HARD);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Sequencer::setStatus(SeqStatus s)
+{
+       m_model.get().sequencer.status = s;
+       m_model.swap(model::SwapType::SOFT);
+
+       /* Additional things to do when the status changes. */
+
+       switch (s)
+       {
+       case SeqStatus::WAITING:
+               rewind();
+               m_synchronizer.sendMIDIrewind();
+               break;
+       case SeqStatus::STOPPED:
+               m_synchronizer.sendMIDIstop();
+               break;
+       case SeqStatus::RUNNING:
+               m_synchronizer.sendMIDIstart();
+               break;
+       default:
+               break;
+       }
+}
+} // namespace giada::m
\ No newline at end of file
index 50498c5953db54f262376061628b8c205a88f0ac..e68ca6438e0c572baf353bca0517bc5403296cc9 100644 (file)
 #define G_SEQUENCER_H
 
 #include "core/eventDispatcher.h"
+#include "core/metronome.h"
 #include "core/quantizer.h"
 #include <vector>
 
-namespace giada::m
+namespace mcl
 {
 class AudioBuffer;
 }
-namespace giada::m::sequencer
-{
-enum class EventType
+
+namespace giada::m::model
 {
-       NONE,
-       FIRST_BEAT,
-       BAR,
-       REWIND,
-       ACTIONS
-};
+class Model;
+}
 
-struct Event
+namespace giada::m
 {
-       EventType                  type    = EventType::NONE;
-       Frame                      global  = 0;
-       Frame                      delta   = 0;
-       const std::vector<Action>* actions = nullptr;
-};
+class JackTransport;
+class ActionRecorder;
+class Synchronizer;
+class Sequencer final
+{
+public:
+       enum class EventType
+       {
+               NONE,
+               FIRST_BEAT,
+               BAR,
+               REWIND,
+               ACTIONS
+       };
+
+       struct Event
+       {
+               EventType                  type    = EventType::NONE;
+               Frame                      global  = 0;
+               Frame                      delta   = 0;
+               const std::vector<Action>* actions = nullptr;
+       };
+
+       using EventBuffer = RingBuffer<Event, G_MAX_SEQUENCER_EVENTS>;
+
+       Sequencer(model::Model&, Synchronizer&, JackTransport&);
+
+       /* canQuantize
+    Tells whether the quantizer value is > 0 and the sequencer is running. */
+
+       bool canQuantize() const;
+
+       /* isRunning
+    When sequencer is actually moving forward, i.e. SeqStatus == RUNNING. */
+
+       bool isRunning() const;
+
+       /* isActive
+    Sequencer is enabled, but might be in wait mode, i.e. SeqStatus == RUNNING or
+    SeqStatus == WAITING. */
+
+       bool isActive() const;
+
+       bool isOnBeat() const;
+       bool isOnBar() const;
+       bool isOnFirstBeat() const;
+       bool isMetronomeOn() const;
+
+       float     getBpm() const;
+       int       getBeats() const;
+       int       getBars() const;
+       int       getCurrentBeat() const;
+       Frame     getCurrentFrame() const;
+       Frame     getCurrentFrameQuantized() const;
+       float     getCurrentSecond(int sampleRate) const;
+       Frame     getFramesInBar() const;
+       Frame     getFramesInBeat() const;
+       Frame     getFramesInLoop() const;
+       Frame     getFramesInSeq() const;
+       int       getQuantizerValue() const;
+       int       getQuantizerStep() const;
+       SeqStatus getStatus() const;
 
-using EventBuffer = RingBuffer<Event, G_MAX_SEQUENCER_EVENTS>;
+       /* getMaxFramesInLoop
+    Returns how many frames the current loop length might contain at the slowest
+    speed possible (G_MIN_BPM). Call this whenever you change the number or 
+    beats. */
 
-/* quantizer
-Used by the sequencer itself and each sample channel. */
+       Frame getMaxFramesInLoop(int sampleRate) const;
 
-extern Quantizer quantizer;
+       /* calcBpmFromRec
+    Given the amount of recorded frames, returns the speed of the current 
+    performance. Used while input recording in FREE mode. */
 
-void init();
+       float calcBpmFromRec(Frame recordedFrames, int sampleRate) const;
 
-/* react
-Reacts to live events coming from the EventDispatcher (human events). */
+       /* quantize
+    Quantizes the frame 'f'.  */
 
-void react(const eventDispatcher::EventBuffer& e);
+       Frame quantize(Frame f) const;
 
-/* advance
-Parses sequencer events that might occur in a block and advances the internal 
-quantizer. Returns a reference to the internal EventBuffer filled with events
-(if any). Call this on each new audio block. */
+       /* reset
+       Brings everything back to the initial state. */
 
-const EventBuffer& advance(Frame bufferSize);
+       void reset(int sampleRate);
 
-/* render
-Renders audio coming out from the sequencer: that is, the metronome! */
+       /* react
+       Reacts to live events coming from the EventDispatcher (human events). */
 
-void render(AudioBuffer& outBuf);
+       void react(const EventDispatcher::EventBuffer&);
 
-/* raw[*]
-Raw functions to start, stop and rewind the sequencer. These functions must be
-called only by clock:: when the JACK signal is received. Other modules should
-use the non-raw versions below. */
+       /* advance
+       Parses sequencer events that might occur in a block and advances the internal 
+       quantizer. Returns a reference to the internal EventBuffer filled with events
+       (if any). Call this on each new audio block. */
 
-void rawStart();
-void rawStop();
-void rawRewind();
+       const EventBuffer& advance(Frame bufferSize, const ActionRecorder&);
 
-void start();
-void stop();
-void rewind();
+       /* render
+       Renders audio coming out from the sequencer: that is, the metronome! */
 
-bool isMetronomeOn();
-void toggleMetronome();
-void setMetronome(bool v);
-} // namespace giada::m::sequencer
+       void render(mcl::AudioBuffer& outBuf);
+
+       /* raw[*]
+       Raw functions to start, stop and rewind the sequencer. These functions must 
+       be called only when the JACK signal is received. Other modules should send
+       a SEQUENCER_* event to the Event Dispatcher. */
+
+       void rawStart();
+       void rawStop();
+       void rawRewind();
+
+       /* rawSetBpm
+       Raw function to set the bpm, bypassing any JACK instruction. This function 
+       must be called only by the Synchronizer when the JACK signal is received. 
+       Other modules should use the non-raw version below. */
+
+       void rawSetBpm(float v, int sampleRate);
+
+       void rewind();
+       void toggleMetronome();
+       void setMetronome(bool v);
+       void setBpm(float b, int sampleRate);
+       void setBeats(int beats, int bars, int sampleRate);
+       void setQuantize(int q, int sampleRate);
+       void setStatus(SeqStatus);
+
+       /* recomputeFrames
+    Updates bpm, frames, beats and so on. */
+
+       void recomputeFrames(int sampleRate);
+
+       /* quantizer
+       Used by the sequencer itself and each sample channel. */
+
+       Quantizer quantizer;
+
+       std::function<void(SeqStatus)>         onAboutStart;
+       std::function<void()>                  onAboutStop;
+       std::function<void(float, float, int)> onBpmChange;
+
+private:
+       /* rewindQ
+       Rewinds sequencer, quantized mode. */
+
+       void rewindQ(Frame delta);
+
+       model::Model&  m_model;
+       Synchronizer&  m_synchronizer;
+       JackTransport& m_jackTransport;
+
+       /* m_eventBuffer
+       Buffer of events found in each block sent to channels for event parsing. 
+       This is filled during react(). */
+
+       EventBuffer m_eventBuffer;
+
+       Metronome m_metronome;
+
+       /* m_quantizerStep
+    Tells how many frames to wait to perform a quantized action. */
+
+       int m_quantizerStep;
+};
+} // namespace giada::m
 
 #endif
diff --git a/src/core/swapper.h b/src/core/swapper.h
deleted file mode 100644 (file)
index e29faed..0000000
+++ /dev/null
@@ -1,141 +0,0 @@
-/* -----------------------------------------------------------------------------
- *
- * Giada - Your Hardcore Loopmachine
- *
- * -----------------------------------------------------------------------------
- *
- * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual
- *
- * This file is part of Giada - Your Hardcore Loopmachine.
- *
- * Giada - Your Hardcore Loopmachine is free software: you can
- * redistribute it and/or modify it under the terms of the GNU General
- * Public License as published by the Free Software Foundation, either
- * version 3 of the License, or (at your option) any later version.
- *
- * Giada - Your Hardcore Loopmachine is distributed in the hope that it
- * will be useful, but WITHOUT ANY WARRANTY; without even the implied
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Giada - Your Hardcore Loopmachine. If not, see
- * <http://www.gnu.org/licenses/>.
- *
- * -------------------------------------------------------------------------- */
-
-#ifndef G_SWAPPER_H
-#define G_SWAPPER_H
-
-#include <atomic>
-#include <functional>
-
-namespace giada
-{
-/* Swapper
-A template class that performs atomic double buffering on type T. */
-
-template <typename T>
-class Swapper
-{
-public:
-       class RtLock
-       {
-               friend Swapper;
-
-       public:
-               RtLock(Swapper& s)
-               : m_swapper(s)
-               {
-                       m_swapper.rt_lock();
-               }
-
-               ~RtLock()
-               {
-                       m_swapper.rt_unlock();
-               }
-
-               const T& get() const
-               {
-                       return m_swapper.rt_get();
-               }
-
-       private:
-               Swapper& m_swapper;
-       };
-
-       Swapper()
-       {
-               static_assert(std::is_assignable_v<T, T>);
-       }
-
-       /* get
-       Returns local data for non-realtime thread. */
-
-       T& get()
-       {
-               return m_data[(m_bits.load() & BIT_INDEX) ^ 1];
-       }
-
-       void swap()
-       {
-               int bits = m_bits.load();
-
-               /* Wait for the audio thread to finish, i.e. until the BUSY bit becomes
-        zero. Only then, swap indexes. This will let the audio thread to pick
-        the updated data on its next cycle. */
-               int desired;
-               do
-               {
-                       bits    = bits & ~BIT_BUSY;               // Expected: current value without busy bit set
-                       desired = (bits ^ BIT_INDEX) & BIT_INDEX; // Desired: flipped (xor) index
-               } while (!m_bits.compare_exchange_weak(bits, desired));
-
-               bits = desired;
-
-               /* After the swap above, m_data[(bits & BIT_INDEX) ^ 1] has become the 
-               non-realtime slot and it points to the data previously read by the
-               realtime thread. That data is old, so update it: overwrite it with the 
-               realtime data in the realtime slot (m_data[bits & BIT_INDEX]) that is 
-               currently being read by the realtime thread. */
-               m_data[(bits & BIT_INDEX) ^ 1] = m_data[bits & BIT_INDEX];
-       }
-
-       bool isLocked()
-       {
-               return m_bits.load() & BIT_BUSY;
-       }
-
-private:
-       static constexpr int BIT_INDEX = (1 << 0); // 0001
-       static constexpr int BIT_BUSY  = (1 << 1); // 0010
-
-       /* [realtime] lock. */
-
-       void rt_lock()
-       {
-               /* Set the busy bit and also get the current index. */
-               m_index = m_bits.fetch_or(BIT_BUSY) & BIT_INDEX;
-       }
-
-       /* [realtime] unlock. */
-
-       void rt_unlock()
-       {
-               m_bits.store(m_index & BIT_INDEX);
-       }
-
-       /* [realtime] Get data currently being ready by the rt thread. */
-
-       const T& rt_get() const
-       {
-               return m_data[m_bits.load() & BIT_INDEX];
-       }
-
-       std::array<T, 2> m_data;
-       std::atomic<int> m_bits{0};
-       int              m_index{0};
-};
-} // namespace giada
-
-#endif
\ No newline at end of file
diff --git a/src/core/synchronizer.cpp b/src/core/synchronizer.cpp
new file mode 100644 (file)
index 0000000..1019858
--- /dev/null
@@ -0,0 +1,221 @@
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+#include "core/synchronizer.h"
+#include "core/conf.h"
+#include "core/kernelAudio.h"
+#include "core/kernelMidi.h"
+#include "core/model/model.h"
+
+namespace giada::m
+{
+Synchronizer::Synchronizer(const Conf::Data& c, KernelMidi& k)
+#ifdef WITH_AUDIO_JACK
+: onJackRewind(nullptr)
+, onJackChangeBpm(nullptr)
+, onJackStart(nullptr)
+, onJackStop(nullptr)
+, m_kernelMidi(k)
+, m_conf(c)
+#else
+: m_kernelMidi(k)
+, m_conf(c)
+#endif
+{
+       reset();
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Synchronizer::reset()
+{
+       m_midiTCrate = static_cast<int>((m_conf.samplerate / m_conf.midiTCfps) * G_MAX_IO_CHANS); // stereo values
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Synchronizer::sendMIDIsync(const model::Sequencer& sequencer)
+{
+       /* Sending MIDI sync while waiting is meaningless. */
+
+       if (sequencer.status == SeqStatus::WAITING)
+               return;
+
+       const int currentFrame = sequencer.a_getCurrentFrame();
+
+       /* TODO - only Master (_M) is implemented so far. */
+
+       if (m_conf.midiSync == G_MIDI_SYNC_CLOCK_M)
+       {
+               if (currentFrame % (sequencer.framesInBeat / 24) == 0)
+                       m_kernelMidi.send(MIDI_CLOCK, -1, -1);
+               return;
+       }
+
+       if (m_conf.midiSync == G_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 % m_midiTCrate != 0) // no timecode frame passed
+                       return;
+
+               /* frame low nibble
+                * frame high nibble
+                * seconds low nibble
+                * seconds high nibble */
+
+               if (m_midiTCframes % 2 == 0)
+               {
+                       m_kernelMidi.send(MIDI_MTC_QUARTER, (m_midiTCframes & 0x0F) | 0x00, -1);
+                       m_kernelMidi.send(MIDI_MTC_QUARTER, (m_midiTCframes >> 4) | 0x10, -1);
+                       m_kernelMidi.send(MIDI_MTC_QUARTER, (m_midiTCseconds & 0x0F) | 0x20, -1);
+                       m_kernelMidi.send(MIDI_MTC_QUARTER, (m_midiTCseconds >> 4) | 0x30, -1);
+               }
+
+               /* minutes low nibble
+                * minutes high nibble
+                * hours low nibble
+                * hours high nibble SMPTE frame rate */
+
+               else
+               {
+                       m_kernelMidi.send(MIDI_MTC_QUARTER, (m_midiTCminutes & 0x0F) | 0x40, -1);
+                       m_kernelMidi.send(MIDI_MTC_QUARTER, (m_midiTCminutes >> 4) | 0x50, -1);
+                       m_kernelMidi.send(MIDI_MTC_QUARTER, (m_midiTChours & 0x0F) | 0x60, -1);
+                       m_kernelMidi.send(MIDI_MTC_QUARTER, (m_midiTChours >> 4) | 0x70, -1);
+               }
+
+               m_midiTCframes++;
+
+               /* check if total timecode frames are greater than timecode fps:
+                * if so, a second has passed */
+
+               if (m_midiTCframes > m_conf.midiTCfps)
+               {
+                       m_midiTCframes = 0;
+                       m_midiTCseconds++;
+                       if (m_midiTCseconds >= 60)
+                       {
+                               m_midiTCminutes++;
+                               m_midiTCseconds = 0;
+                               if (m_midiTCminutes >= 60)
+                               {
+                                       m_midiTChours++;
+                                       m_midiTCminutes = 0;
+                               }
+                       }
+                       //u::log::print("%d:%d:%d:%d\n", m_midiTChours, m_midiTCminutes, m_midiTCseconds, m_midiTCframes);
+               }
+       }
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Synchronizer::sendMIDIrewind()
+{
+       m_midiTCframes  = 0;
+       m_midiTCseconds = 0;
+       m_midiTCminutes = 0;
+       m_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 (m_conf.midiSync == G_MIDI_SYNC_MTC_M)
+       {
+               m_kernelMidi.send(MIDI_SYSEX, 0x7F, 0x00); // send msg on channel 0
+               m_kernelMidi.send(0x01, 0x01, 0x00);       // hours 0
+               m_kernelMidi.send(0x00, 0x00, 0x00);       // mins, secs, frames 0
+               m_kernelMidi.send(MIDI_EOX, -1, -1);       // end of sysex
+       }
+       else if (m_conf.midiSync == G_MIDI_SYNC_CLOCK_M)
+               m_kernelMidi.send(MIDI_POSITION_PTR, 0, 0);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Synchronizer::sendMIDIstart()
+{
+       if (m_conf.midiSync == G_MIDI_SYNC_CLOCK_M)
+       {
+               m_kernelMidi.send(MIDI_START, -1, -1);
+               m_kernelMidi.send(MIDI_POSITION_PTR, 0, 0);
+       }
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Synchronizer::sendMIDIstop()
+{
+       if (m_conf.midiSync == G_MIDI_SYNC_CLOCK_M)
+               m_kernelMidi.send(MIDI_STOP, -1, -1);
+}
+
+/* -------------------------------------------------------------------------- */
+
+#ifdef WITH_AUDIO_JACK
+
+void Synchronizer::recvJackSync(const JackTransport::State& state)
+{
+       assert(onJackRewind != nullptr);
+       assert(onJackChangeBpm != nullptr);
+       assert(onJackStart != nullptr);
+       assert(onJackStop != nullptr);
+
+       JackTransport::State jackStateCurr = state;
+
+       if (jackStateCurr != m_jackStatePrev)
+       {
+               if (jackStateCurr.frame != m_jackStatePrev.frame && jackStateCurr.frame == 0)
+               {
+                       G_DEBUG("JackState received - rewind to frame 0");
+                       onJackRewind();
+               }
+
+               // jackStateCurr.bpm == 0 if JACK doesn't send that info
+               if (jackStateCurr.bpm != m_jackStatePrev.bpm && jackStateCurr.bpm > 1.0f)
+               {
+                       G_DEBUG("JackState received - bpm=" << jackStateCurr.bpm);
+                       onJackChangeBpm(jackStateCurr.bpm);
+               }
+
+               if (jackStateCurr.running != m_jackStatePrev.running)
+               {
+                       G_DEBUG("JackState received - running=" << jackStateCurr.running);
+                       jackStateCurr.running ? onJackStart() : onJackStop();
+               }
+       }
+
+       m_jackStatePrev = jackStateCurr;
+}
+
+#endif
+} // namespace giada::m
diff --git a/src/core/synchronizer.h b/src/core/synchronizer.h
new file mode 100644 (file)
index 0000000..20f3bee
--- /dev/null
@@ -0,0 +1,110 @@
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+#ifndef G_SYNC_H
+#define G_SYNC_H
+
+#ifdef WITH_AUDIO_JACK
+#include "core/jackTransport.h"
+#endif
+#include "core/types.h"
+#include "core/conf.h"
+#include <functional>
+
+namespace giada::m::kernelAudio
+{
+struct JackState;
+}
+
+namespace giada::m::model
+{
+class Sequencer;
+}
+
+namespace giada::m
+{
+class KernelMidi;
+class Synchronizer final
+{
+public:
+       Synchronizer(const Conf::Data&, KernelMidi&);
+
+       /* reset
+       Brings everything back to the initial state. */
+
+       void reset();
+
+       /* sendMIDIsync
+    Generates MIDI sync output data. */
+
+       void sendMIDIsync(const model::Sequencer& clock);
+
+       /* sendMIDIrewind
+    Rewinds timecode to beat 0 and also send a MTC full frame to cue the slave. */
+
+       void sendMIDIrewind();
+
+       void sendMIDIstart();
+       void sendMIDIstop();
+
+#ifdef WITH_AUDIO_JACK
+
+       /* recvJackSync
+    Receives a new JACK state. Called by Kernel Audio on each audio block. */
+
+       void recvJackSync(const JackTransport::State& state);
+
+       /* onJack[...]
+    Callbacks called when something happens in the JACK state. */
+
+       std::function<void()>      onJackRewind;
+       std::function<void(float)> onJackChangeBpm;
+       std::function<void()>      onJackStart;
+       std::function<void()>      onJackStop;
+#endif
+
+private:
+       /* midiTC*
+    MIDI timecode variables. */
+
+       int m_midiTCrate    = 0; // Send MTC data every m_midiTCrate frames
+       int m_midiTCframes  = 0;
+       int m_midiTCseconds = 0;
+       int m_midiTCminutes = 0;
+       int m_midiTChours   = 0;
+
+#ifdef WITH_AUDIO_JACK
+
+       JackTransport::State m_jackStatePrev;
+
+#endif
+
+       KernelMidi&       m_kernelMidi;
+       const Conf::Data& m_conf;
+};
+} // namespace giada::m
+
+#endif
index ea8bab0c549bd895f12e5fadf296440dc04f4060..14df0aaa8b82bb3e879a9449a047dc8bc8c848ff 100644 (file)
@@ -45,7 +45,7 @@ enum class Thread
 #ifdef _WIN32
 #undef VOID
 #endif
-enum class ClockStatus
+enum class SeqStatus
 {
        STOPPED,
        WAITING,
@@ -84,7 +84,8 @@ enum class SamplePlayerMode : int
        SINGLE_BASIC,
        SINGLE_PRESS,
        SINGLE_RETRIG,
-       SINGLE_ENDLESS
+       SINGLE_ENDLESS,
+       SINGLE_BASIC_PAUSE
 };
 
 enum class RecTriggerMode : int
@@ -104,6 +105,15 @@ enum class EventType : int
        AUTO = 0,
        MANUAL
 };
+
+/* Peak
+Audio peak information for two In/Out channels. */
+
+struct Peak
+{
+       float left;
+       float right;
+};
 } // namespace giada
 
 #endif
index 38ae59ca3f0133669ee290d2aa30fe7fe48bb1b8..f29054a131018fcdc307a8009c10ca369263919b 100644 (file)
@@ -82,8 +82,8 @@ bool        Wave::isEdited() const { return m_edited; }
 
 /* -------------------------------------------------------------------------- */
 
-AudioBuffer&       Wave::getBuffer() { return m_buffer; }
-const AudioBuffer& Wave::getBuffer() const { return m_buffer; }
+mcl::AudioBuffer&       Wave::getBuffer() { return m_buffer; }
+const mcl::AudioBuffer& Wave::getBuffer() const { return m_buffer; }
 
 /* -------------------------------------------------------------------------- */
 
@@ -117,7 +117,7 @@ void Wave::setPath(const std::string& p, int wid)
 
 /* -------------------------------------------------------------------------- */
 
-void Wave::replaceData(AudioBuffer&& b)
+void Wave::replaceData(mcl::AudioBuffer&& b)
 {
        m_buffer = std::move(b);
 }
index 8f2f697692d1aa7aec424d034654d9bb89215cf2..52c5705547502b2bf624dfe02a241f3d58ecc332 100644 (file)
@@ -27,8 +27,8 @@
 #ifndef G_WAVE_H
 #define G_WAVE_H
 
-#include "core/audioBuffer.h"
 #include "core/types.h"
+#include "deps/mcl-audio-buffer/src/audioBuffer.hpp"
 #include <string>
 
 namespace giada::m
@@ -54,8 +54,8 @@ public:
        /* getBuffer
        Returns a (non-)const reference to the underlying audio buffer. */
 
-       AudioBuffer&       getBuffer();
-       const AudioBuffer& getBuffer() const;
+       mcl::AudioBuffer&       getBuffer();
+       const mcl::AudioBuffer& getBuffer() const;
 
        /* setPath
        Sets new path 'p'. If 'id' != -1 inserts a numeric id next to the file 
@@ -70,19 +70,19 @@ public:
        /* replaceData
        Replaces internal audio buffer with 'b' by moving it. */
 
-       void replaceData(AudioBuffer&& b);
+       void replaceData(mcl::AudioBuffer&& b);
 
        void alloc(Frame size, int channels, int rate, int bits, const std::string& path);
 
        ID id;
 
 private:
-       AudioBuffer m_buffer;
-       int         m_rate;
-       int         m_bits;
-       bool        m_logical; // memory only (a take)
-       bool        m_edited;  // edited via editor
-       std::string m_path;    // E.g. /path/to/my/sample.wav
+       mcl::AudioBuffer m_buffer;
+       int              m_rate;
+       int              m_bits;
+       bool             m_logical; // memory only (a take)
+       bool             m_edited;  // edited via editor
+       std::string      m_path;    // E.g. /path/to/my/sample.wav
 };
 } // namespace giada::m
 
index edaf4f4fe7edc057e33e41b3adce421ac69bb0b2..d68e5a16e805de478b9d78c8a475906923cd39b1 100644 (file)
@@ -26,6 +26,7 @@
 
 #include "waveFx.h"
 #include "const.h"
+#include "deps/mcl-audio-buffer/src/audioBuffer.hpp"
 #include "utils/log.h"
 #include "wave.h"
 #include <algorithm>
@@ -86,7 +87,7 @@ int monoToStereo(Wave& w)
        if (w.getBuffer().countChannels() >= G_MAX_IO_CHANS)
                return G_RES_OK;
 
-       AudioBuffer newData;
+       mcl::AudioBuffer newData;
        newData.alloc(w.getBuffer().countFrames(), G_MAX_IO_CHANS);
 
        for (int i = 0; i < newData.countFrames(); i++)
@@ -124,7 +125,7 @@ void cut(Wave& w, int a, int b)
 
        int newSize = w.getBuffer().countFrames() - (b - a);
 
-       AudioBuffer newData;
+       mcl::AudioBuffer newData;
        newData.alloc(newSize, w.getBuffer().countChannels());
 
        u::log::print("[wfx::cut] cutting from %d to %d\n", a, b);
@@ -154,7 +155,7 @@ void trim(Wave& w, Frame a, Frame b)
 
        Frame newSize = b - a;
 
-       AudioBuffer newData;
+       mcl::AudioBuffer newData;
        newData.alloc(newSize, w.getBuffer().countChannels());
 
        u::log::print("[wfx::trim] trimming from %d to %d (area = %d)\n", a, b, b - a);
@@ -173,7 +174,7 @@ void paste(const Wave& src, Wave& des, Frame a)
 {
        assert(src.getBuffer().countChannels() == des.getBuffer().countChannels());
 
-       AudioBuffer newData;
+       mcl::AudioBuffer newData;
        newData.alloc(src.getBuffer().countFrames() + des.getBuffer().countFrames(), des.getBuffer().countChannels());
 
        /* |---original data---|///paste data///|---original data---|
index 9c2c1f0345752dc6b2bbeef30ad4006aa0c31301..c0db661a610a8501422455575a0591bc256b03e0 100644 (file)
@@ -33,6 +33,7 @@ namespace giada::m
 {
 class Wave;
 }
+
 namespace giada::m::wfx
 {
 /* Windows fix */
index eee58c70fa56817adbc598f4da450ad6501d284c..6cfe4149620e072529e3afe69f96c99eadd70bf2 100644 (file)
@@ -26,8 +26,8 @@
 
 #include "waveManager.h"
 #include "const.h"
+#include "deps/mcl-audio-buffer/src/audioBuffer.hpp"
 #include "idManager.h"
-#include "model/model.h"
 #include "patch.h"
 #include "utils/fs.h"
 #include "utils/log.h"
 #include <samplerate.h>
 #include <sndfile.h>
 
-namespace giada::m::waveManager
+namespace giada::m
 {
 namespace
 {
-IdManager waveId_;
-
-/* -------------------------------------------------------------------------- */
-
 int getBits_(const SF_INFO& header)
 {
        if (header.format & SF_FORMAT_PCM_S8)
@@ -63,20 +59,60 @@ int getBits_(const SF_INFO& header)
                return 64;
        return 0;
 }
+
+/* -------------------------------------------------------------------------- */
+
+std::string makeWavePath_(const std::string& base, const m::Wave& w, int k)
+{
+       return base + G_SLASH + w.getBasename(/*ext=*/false) + "-" + std::to_string(k) + w.getExtension();
+}
+
+/* -------------------------------------------------------------------------- */
+
+bool isWavePathUnique_(const m::Wave& skip, const std::string& path,
+    const std::vector<std::unique_ptr<Wave>>& waves)
+{
+       for (const auto& w : waves)
+               if (w->id != skip.id && w->getPath() == path)
+                       return false;
+       return true;
+}
+
 } // namespace
 
 /* -------------------------------------------------------------------------- */
 /* -------------------------------------------------------------------------- */
 /* -------------------------------------------------------------------------- */
 
-void init()
+std::string makeUniqueWavePath(const std::string& base, const m::Wave& w,
+    const std::vector<std::unique_ptr<Wave>>& waves)
 {
-       waveId_ = IdManager();
+       std::string path = base + G_SLASH + w.getBasename(/*ext=*/true);
+       if (isWavePathUnique_(w, path, waves))
+               return path;
+
+       // TODO - just use a timestamp. e.g. makeWavePath_(..., ..., getTimeStamp())
+       int k = 0;
+       path  = makeWavePath_(base, w, k);
+       while (!isWavePathUnique_(w, path, waves))
+               path = makeWavePath_(base, w, k++);
+
+       return path;
+}
+
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+
+void WaveManager::reset()
+{
+       m_waveId = IdManager();
 }
 
 /* -------------------------------------------------------------------------- */
 
-Result createFromFile(const std::string& path, ID id, int samplerate, int quality)
+WaveManager::Result WaveManager::createFromFile(const std::string& path, ID id,
+    int samplerate, int quality)
 {
        if (path == "" || u::fs::isDir(path))
        {
@@ -102,9 +138,9 @@ Result createFromFile(const std::string& path, ID id, int samplerate, int qualit
                return {G_RES_ERR_WRONG_DATA};
        }
 
-       waveId_.set(id);
+       m_waveId.set(id);
 
-       std::unique_ptr<Wave> wave = std::make_unique<Wave>(waveId_.generate(id));
+       std::unique_ptr<Wave> wave = std::make_unique<Wave>(m_waveId.generate(id));
        wave->alloc(header.frames, header.channels, header.samplerate, getBits_(header), path);
 
        if (sf_readf_float(fileIn, wave->getBuffer()[0], header.frames) != header.frames)
@@ -130,10 +166,10 @@ Result createFromFile(const std::string& path, ID id, int samplerate, int qualit
 
 /* -------------------------------------------------------------------------- */
 
-std::unique_ptr<Wave> createEmpty(int frames, int channels, int samplerate,
+std::unique_ptr<Wave> WaveManager::createEmpty(int frames, int channels, int samplerate,
     const std::string& name)
 {
-       std::unique_ptr<Wave> wave = std::make_unique<Wave>(waveId_.generate());
+       std::unique_ptr<Wave> wave = std::make_unique<Wave>(m_waveId.generate());
        wave->alloc(frames, channels, samplerate, G_DEFAULT_BIT_DEPTH, name);
        wave->setLogical(true);
 
@@ -145,12 +181,12 @@ std::unique_ptr<Wave> createEmpty(int frames, int channels, int samplerate,
 
 /* -------------------------------------------------------------------------- */
 
-std::unique_ptr<Wave> createFromWave(const Wave& src, int a, int b)
+std::unique_ptr<Wave> WaveManager::createFromWave(const Wave& src, int a, int b)
 {
        int channels = src.getBuffer().countChannels();
        int frames   = b - a;
 
-       std::unique_ptr<Wave> wave = std::make_unique<Wave>(waveId_.generate());
+       std::unique_ptr<Wave> wave = std::make_unique<Wave>(m_waveId.generate());
        wave->alloc(frames, channels, src.getRate(), src.getBits(), src.getPath());
        wave->getBuffer().set(src.getBuffer(), frames);
        wave->setLogical(true);
@@ -162,31 +198,24 @@ std::unique_ptr<Wave> createFromWave(const Wave& src, int a, int b)
 
 /* -------------------------------------------------------------------------- */
 
-std::unique_ptr<Wave> deserializeWave(const patch::Wave& w, int samplerate, int quality)
+std::unique_ptr<Wave> WaveManager::deserializeWave(const Patch::Wave& w, int samplerate, int quality)
 {
        return createFromFile(w.path, w.id, samplerate, quality).wave;
 }
 
-const patch::Wave serializeWave(const Wave& w)
+const Patch::Wave WaveManager::serializeWave(const Wave& w) const
 {
        return {w.id, u::fs::basename(w.getPath())};
 }
 
 /* -------------------------------------------------------------------------- */
 
-Wave* hydrateWave(ID waveId)
-{
-       return model::find<Wave>(waveId);
-}
-
-/* -------------------------------------------------------------------------- */
-
-int resample(Wave& w, int quality, int samplerate)
+int WaveManager::resample(Wave& w, int quality, int samplerate)
 {
        float ratio         = samplerate / (float)w.getRate();
        int   newSizeFrames = static_cast<int>(ceil(w.getBuffer().countFrames() * ratio));
 
-       AudioBuffer newData;
+       mcl::AudioBuffer newData;
        newData.alloc(newSizeFrames, w.getBuffer().countChannels());
 
        SRC_DATA src_data;
@@ -213,7 +242,7 @@ int resample(Wave& w, int quality, int samplerate)
 
 /* -------------------------------------------------------------------------- */
 
-int save(const Wave& w, const std::string& path)
+int WaveManager::save(const Wave& w, const std::string& path)
 {
        SF_INFO header;
        header.samplerate = w.getRate();
@@ -235,4 +264,4 @@ int save(const Wave& w, const std::string& path)
 
        return G_RES_OK;
 }
-} // namespace giada::m::waveManager
+} // namespace giada::m
index 36fdf20008c93370d86f25a47c2de9d31be0e550..d0142eac6e44027d0f7eeca52d8c8f3cc54cc8f5 100644 (file)
 #ifndef G_WAVE_MANAGER_H
 #define G_WAVE_MANAGER_H
 
+#include "core/idManager.h"
+#include "core/patch.h"
 #include "core/types.h"
+#include "core/wave.h"
 #include <memory>
 #include <string>
 
 namespace giada::m
 {
-class Wave;
-}
-namespace giada::m::patch
-{
-struct Wave;
-}
-namespace giada::m::waveManager
-{
-struct Result
+std::string makeUniqueWavePath(const std::string& base, const m::Wave& w,
+    const std::vector<std::unique_ptr<Wave>>& waves);
+
+/* -------------------------------------------------------------------------- */
+
+class WaveManager final
 {
-       int                   status;
-       std::unique_ptr<Wave> wave = nullptr;
-};
-/* init
-Initializes internal data. */
+public:
+       struct Result
+       {
+               int                   status;
+               std::unique_ptr<Wave> wave = nullptr;
+       };
+
+       /* reset
+    Resets internal ID generator. */
 
-void init();
+       void reset();
 
-/* create
-Creates a new Wave object with data read from file 'path'. Pass id = 0 to 
-auto-generate it. The function converts the Wave sample rate if it doesn't match
-the desired one as specified in 'samplerate'. */
+       /* create
+       Creates a new Wave object with data read from file 'path'. Pass id = 0 to 
+       auto-generate it. The function converts the Wave sample rate if it doesn't 
+       match the desired one as specified in 'samplerate'. */
 
-Result createFromFile(const std::string& path, ID id, int samplerate, int quality);
+       Result createFromFile(const std::string& path, ID id, int samplerate, int quality);
 
-/* createEmpty
-Creates a new silent Wave object. */
+       /* createEmpty
+       Creates a new silent Wave object. */
 
-std::unique_ptr<Wave> createEmpty(int frames, int channels, int samplerate,
-    const std::string& name);
+       std::unique_ptr<Wave> createEmpty(int frames, int channels, int samplerate,
+           const std::string& name);
 
-/* createFromWave
-Creates a new Wave from an existing one, copying the data in range a - b. */
+       /* createFromWave
+       Creates a new Wave from an existing one, copying the data in range a - b. */
 
-std::unique_ptr<Wave> createFromWave(const Wave& src, int a, int b);
+       std::unique_ptr<Wave> createFromWave(const Wave& src, int a, int b);
 
-/* (de)serializeWave
-Creates a new Wave given the patch raw data and vice versa. */
+       /* (de)serializeWave
+       Creates a new Wave given the patch raw data and vice versa. */
 
-std::unique_ptr<Wave> deserializeWave(const patch::Wave& w, int samplerate, int quality);
-const patch::Wave     serializeWave(const Wave& w);
-Wave*                 hydrateWave(ID waveId);
+       std::unique_ptr<Wave> deserializeWave(const Patch::Wave& w, int samplerate, int quality);
+       const Patch::Wave     serializeWave(const Wave& w) const;
 
-/* resample
-Change sample rate of 'w' to the desider value. The 'quality' parameter sets the
-algorithm to use for the conversion. */
+       /* resample
+       Change sample rate of 'w' to the desider value. The 'quality' parameter sets 
+       the algorithm to use for the conversion. */
 
-int resample(Wave& w, int quality, int samplerate);
+       int resample(Wave& w, int quality, int samplerate);
 
-/* save
-Writes Wave data to file 'path'. Only 'wav' format is supported for now. */
+       /* save
+       Writes Wave data to file 'path'. Only 'wav' format is supported for now. */
 
-int save(const Wave& w, const std::string& path);
-} // namespace giada::m::waveManager
+       int save(const Wave& w, const std::string& path);
+
+private:
+       IdManager m_waveId;
+};
+} // namespace giada::m
 
 #endif
index d3ae7dfdd7f724b736ebf09b279ca7757b6d66ce..f2bca2314ec311e52ca554bfecb697424cb3b00a 100644 (file)
@@ -28,6 +28,7 @@
 #define G_WEAK_ATOMIC_H
 
 #include <atomic>
+#include <functional>
 
 namespace giada
 {
@@ -38,25 +39,25 @@ public:
        WeakAtomic() = default;
 
        WeakAtomic(T t)
-       : m_value(t)
+       : m_atomic(t)
+       , m_value(t)
        {
        }
 
        WeakAtomic(const WeakAtomic& o)
-       : m_value(o.m_value.load(std::memory_order_relaxed))
+       : m_atomic(o.load())
+       , m_value(o.m_value)
        {
        }
 
-       WeakAtomic(WeakAtomic&& o)
-       : m_value(o.m_value.load(std::memory_order_relaxed), std::memory_order_relaxed)
-       {
-       }
+       WeakAtomic(WeakAtomic&& o) = delete;
 
        WeakAtomic& operator=(const WeakAtomic& o)
        {
                if (this == &o)
                        return *this;
-               m_value.store(o.m_value.load(std::memory_order_relaxed), std::memory_order_relaxed);
+               store(o.load());
+               m_value = o.m_value;
                return *this;
        }
 
@@ -64,16 +65,22 @@ public:
 
        T load() const
        {
-               return m_value.load(std::memory_order_relaxed);
+               return m_atomic.load(std::memory_order_relaxed);
        }
 
        void store(T t)
        {
-               return m_value.store(t, std::memory_order_relaxed);
+               m_atomic.store(t, std::memory_order_relaxed);
+               if (onChange != nullptr && t != m_value)
+                       onChange(t);
+               m_value = t;
        }
 
-  private:
-       std::atomic<T> m_value;
+       std::function<void(T)> onChange = nullptr;
+
+private:
+       std::atomic<T> m_atomic;
+       T              m_value;
 };
 } // namespace giada
 
index 86a498d5b7da30a0541e5f1b2853cad009cd2cd1..248a6043b66103011d2f5a2869f72fbf845bb40d 100644 (file)
  *
  * -------------------------------------------------------------------------- */
 
-#include "actionEditor.h"
-#include "core/action.h"
-#include "core/clock.h"
+#include "glue/actionEditor.h"
+#include "core/actions/action.h"
+#include "core/actions/actionRecorder.h"
+#include "core/actions/actions.h"
 #include "core/const.h"
+#include "core/engine.h"
 #include "core/model/model.h"
-#include "core/recorder.h"
-#include "core/recorderHandler.h"
+#include "core/sequencer.h"
 #include "glue/events.h"
 #include "glue/recorder.h"
 #include <cassert>
 
+extern giada::m::Engine g_engine;
+
 namespace giada::c::actionEditor
 {
 namespace
@@ -57,18 +60,16 @@ First action ever? Add actions at boundaries. */
 
 void recordFirstEnvelopeAction_(ID channelId, Frame frame, int value)
 {
-       namespace mr = m::recorder;
-
        // TODO - use MidiEvent(float)
        m::MidiEvent    e1 = m::MidiEvent(m::MidiEvent::ENVELOPE, 0, G_MAX_VELOCITY);
        m::MidiEvent    e2 = m::MidiEvent(m::MidiEvent::ENVELOPE, 0, value);
-       const m::Action a1 = mr::rec(channelId, 0, e1);
-       const m::Action a2 = mr::rec(channelId, frame, e2);
-       const m::Action a3 = mr::rec(channelId, m::clock::getFramesInLoop() - 1, e1);
+       const m::Action a1 = g_engine.actionRecorder.rec(channelId, 0, e1);
+       const m::Action a2 = g_engine.actionRecorder.rec(channelId, frame, e2);
+       const m::Action a3 = g_engine.actionRecorder.rec(channelId, g_engine.sequencer.getFramesInLoop() - 1, e1);
 
-       mr::updateSiblings(a1.id, /*prev=*/a3.id, /*next=*/a2.id); // Circular loop (begin)
-       mr::updateSiblings(a2.id, /*prev=*/a1.id, /*next=*/a3.id);
-       mr::updateSiblings(a3.id, /*prev=*/a2.id, /*next=*/a1.id); // Circular loop (end)
+       g_engine.actionRecorder.updateSiblings(a1.id, /*prev=*/a3.id, /*next=*/a2.id); // Circular loop (begin)
+       g_engine.actionRecorder.updateSiblings(a2.id, /*prev=*/a1.id, /*next=*/a3.id);
+       g_engine.actionRecorder.updateSiblings(a3.id, /*prev=*/a2.id, /*next=*/a1.id); // Circular loop (end)
 }
 
 /* -------------------------------------------------------------------------- */
@@ -79,9 +80,7 @@ Vertical envelope points are forbidden. */
 
 void recordNonFirstEnvelopeAction_(ID channelId, Frame frame, int value)
 {
-       namespace mr = m::recorder;
-
-       const m::Action a1 = mr::getClosestAction(channelId, frame, m::MidiEvent::ENVELOPE);
+       const m::Action a1 = g_engine.actionRecorder.getClosestAction(channelId, frame, m::MidiEvent::ENVELOPE);
        const m::Action a3 = a1.next != nullptr ? *a1.next : m::Action{};
 
        assert(a1.isValid());
@@ -93,9 +92,9 @@ void recordNonFirstEnvelopeAction_(ID channelId, Frame frame, int value)
 
        // TODO - use MidiEvent(float)
        m::MidiEvent    e2 = m::MidiEvent(m::MidiEvent::ENVELOPE, 0, value);
-       const m::Action a2 = mr::rec(channelId, frame, e2);
+       const m::Action a2 = g_engine.actionRecorder.rec(channelId, frame, e2);
 
-       mr::updateSiblings(a2.id, a1.id, a3.id);
+       g_engine.actionRecorder.updateSiblings(a2.id, a1.id, a3.id);
 }
 
 /* -------------------------------------------------------------------------- */
@@ -103,7 +102,7 @@ void recordNonFirstEnvelopeAction_(ID channelId, Frame frame, int value)
 bool isSinglePressMode_(ID channelId)
 {
        /* TODO - use m::model getChannel utils (to be added) */
-       return m::model::get().getChannel(channelId).samplePlayer->mode == SamplePlayerMode::SINGLE_PRESS;
+       return g_engine.model.get().getChannel(channelId).samplePlayer->mode == SamplePlayerMode::SINGLE_PRESS;
 }
 } // namespace
 
@@ -111,7 +110,7 @@ bool isSinglePressMode_(ID channelId)
 /* -------------------------------------------------------------------------- */
 /* -------------------------------------------------------------------------- */
 
-SampleData::SampleData(const m::samplePlayer::Data& s)
+SampleData::SampleData(const m::SamplePlayer& s)
 : channelMode(s.mode)
 , isLoopMode(s.isAnyLoopMode())
 {
@@ -119,37 +118,52 @@ SampleData::SampleData(const m::samplePlayer::Data& s)
 
 /* -------------------------------------------------------------------------- */
 
-Data::Data(const m::channel::Data& c)
+Data::Data(const m::Channel& c)
 : channelId(c.id)
 , channelName(c.name)
-, actions(m::recorder::getActionsOnChannel(c.id))
+, framesInSeq(g_engine.sequencer.getFramesInSeq())
+, framesInBeat(g_engine.sequencer.getFramesInBeat())
+, framesInBar(g_engine.sequencer.getFramesInBar())
+, framesInLoop(g_engine.sequencer.getFramesInLoop())
+, actions(g_engine.actionRecorder.getActionsOnChannel(c.id))
 {
        if (c.type == ChannelType::SAMPLE)
                sample = std::make_optional<SampleData>(c.samplePlayer.value());
 }
 
+/* -------------------------------------------------------------------------- */
+
+Frame Data::getCurrentFrame() const
+{
+       return g_engine.sequencer.getCurrentFrame();
+}
+
+/* -------------------------------------------------------------------------- */
+
+bool Data::isChannelPlaying() const
+{
+       return g_engine.model.get().getChannel(channelId).isPlaying();
+}
+
 /* -------------------------------------------------------------------------- */
 /* -------------------------------------------------------------------------- */
 /* -------------------------------------------------------------------------- */
 
 Data getData(ID channelId)
 {
-       return Data(m::model::get().getChannel(channelId));
+       return Data(g_engine.model.get().getChannel(channelId));
 }
 
 /* -------------------------------------------------------------------------- */
 
 void recordMidiAction(ID channelId, int note, int velocity, Frame f1, Frame f2)
 {
-       namespace mr = m::recorder;
-       namespace cr = c::recorder;
-
        if (f2 == 0)
                f2 = f1 + G_DEFAULT_ACTION_SIZE;
 
        /* Avoid frame overflow. */
 
-       Frame overflow = f2 - (m::clock::getFramesInLoop());
+       Frame overflow = f2 - (g_engine.sequencer.getFramesInLoop());
        if (overflow > 0)
        {
                f2 -= overflow;
@@ -159,7 +173,7 @@ void recordMidiAction(ID channelId, int note, int velocity, Frame f1, Frame f2)
        m::MidiEvent e1 = m::MidiEvent(m::MidiEvent::NOTE_ON, note, velocity);
        m::MidiEvent e2 = m::MidiEvent(m::MidiEvent::NOTE_OFF, note, velocity);
 
-       mr::rec(channelId, f1, f2, e1, e2);
+       g_engine.actionRecorder.rec(channelId, f1, f2, e1, e2);
 
        recorder::updateChannel(channelId, /*updateActionEditor=*/false);
 }
@@ -168,8 +182,6 @@ void recordMidiAction(ID channelId, int note, int velocity, Frame f1, Frame f2)
 
 void deleteMidiAction(ID channelId, const m::Action& a)
 {
-       namespace mr = m::recorder;
-
        assert(a.isValid());
        assert(a.event.getStatus() == m::MidiEvent::NOTE_ON);
 
@@ -179,10 +191,10 @@ void deleteMidiAction(ID channelId, const m::Action& a)
        if (a.next != nullptr)
        {
                events::sendMidiToChannel(channelId, a.next->event, Thread::MAIN);
-               mr::deleteAction(a.id, a.next->id);
+               g_engine.actionRecorder.deleteAction(a.id, a.next->id);
        }
        else
-               mr::deleteAction(a.id);
+               g_engine.actionRecorder.deleteAction(a.id);
 
        recorder::updateChannel(channelId, /*updateActionEditor=*/false);
 }
@@ -192,9 +204,7 @@ void deleteMidiAction(ID channelId, const m::Action& a)
 void updateMidiAction(ID channelId, const m::Action& a, int note, int velocity,
     Frame f1, Frame f2)
 {
-       namespace mr = m::recorder;
-
-       mr::deleteAction(a.id, a.next->id);
+       g_engine.actionRecorder.deleteAction(a.id, a.next->id);
        recordMidiAction(channelId, note, velocity, f1, f2);
 }
 
@@ -202,20 +212,18 @@ void updateMidiAction(ID channelId, const m::Action& a, int note, int velocity,
 
 void recordSampleAction(ID channelId, int type, Frame f1, Frame f2)
 {
-       namespace mr = m::recorder;
-
        if (isSinglePressMode_(channelId))
        {
                if (f2 == 0)
                        f2 = f1 + G_DEFAULT_ACTION_SIZE;
                m::MidiEvent e1 = m::MidiEvent(m::MidiEvent::NOTE_ON, 0, 0);
                m::MidiEvent e2 = m::MidiEvent(m::MidiEvent::NOTE_OFF, 0, 0);
-               mr::rec(channelId, f1, f2, e1, e2);
+               g_engine.actionRecorder.rec(channelId, f1, f2, e1, e2);
        }
        else
        {
                m::MidiEvent e1 = m::MidiEvent(type, 0, 0);
-               mr::rec(channelId, f1, e1);
+               g_engine.actionRecorder.rec(channelId, f1, e1);
        }
 
        recorder::updateChannel(channelId, /*updateActionEditor=*/false);
@@ -226,12 +234,10 @@ void recordSampleAction(ID channelId, int type, Frame f1, Frame f2)
 void updateSampleAction(ID channelId, const m::Action& a, int type,
     Frame f1, Frame f2)
 {
-       namespace mr = m::recorder;
-
        if (isSinglePressMode_(channelId))
-               mr::deleteAction(a.id, a.next->id);
+               g_engine.actionRecorder.deleteAction(a.id, a.next->id);
        else
-               mr::deleteAction(a.id);
+               g_engine.actionRecorder.deleteAction(a.id);
 
        recordSampleAction(channelId, type, f1, f2);
 }
@@ -240,13 +246,10 @@ void updateSampleAction(ID channelId, const m::Action& a, int type,
 
 void deleteSampleAction(ID channelId, const m::Action& a)
 {
-       namespace mr = m::recorder;
-       namespace cr = c::recorder;
-
        if (a.next != nullptr) // For ChannelMode::SINGLE_PRESS combo
-               mr::deleteAction(a.id, a.next->id);
+               g_engine.actionRecorder.deleteAction(a.id, a.next->id);
        else
-               mr::deleteAction(a.id);
+               g_engine.actionRecorder.deleteAction(a.id);
 
        recorder::updateChannel(channelId, /*updateActionEditor=*/false);
 }
@@ -255,16 +258,13 @@ void deleteSampleAction(ID channelId, const m::Action& a)
 
 void recordEnvelopeAction(ID channelId, Frame f, int value)
 {
-       namespace mr = m::recorder;
-       namespace cr = c::recorder;
-
        assert(value >= 0 && value <= G_MAX_VELOCITY);
 
        /* First action ever? Add actions at boundaries. Else, find action right
        before frame 'f' and inject a new action in there. Vertical envelope points 
        are forbidden for now. */
 
-       if (!mr::hasActions(channelId, m::MidiEvent::ENVELOPE))
+       if (!g_engine.actionRecorder.hasActions(channelId, m::MidiEvent::ENVELOPE))
                recordFirstEnvelopeAction_(channelId, f, value);
        else
                recordNonFirstEnvelopeAction_(channelId, f, value);
@@ -276,26 +276,17 @@ void recordEnvelopeAction(ID channelId, Frame f, int value)
 
 void deleteEnvelopeAction(ID channelId, const m::Action& a)
 {
-       namespace mr  = m::recorder;
-       namespace cr  = c::recorder;
-       namespace mrh = m::recorderHandler;
-
        /* Deleting a boundary action wipes out everything. If is volume, remember 
        to restore _i and _d members in channel. */
        /* TODO - move this to c::*/
        /* TODO - FIX*/
-       if (mrh::isBoundaryEnvelopeAction(a))
+       if (g_engine.actionRecorder.isBoundaryEnvelopeAction(a))
        {
                if (a.isVolumeEnvelope())
                {
-                       /*
-                       m::model::onSwap(m::model::channels, channelId, [&](m::Channel& c)
-                       {
-                               c.volume_i = 1.0;
-                               c.volume_d = 0.0;
-                       });*/
+                       // TODO reset all volume vars to 1.0
                }
-               mr::clearActions(channelId, a.event.getStatus());
+               g_engine.actionRecorder.clearActions(channelId, a.event.getStatus());
        }
        else
        {
@@ -310,12 +301,12 @@ void deleteEnvelopeAction(ID channelId, const m::Action& a)
                /* Original status:   a1--->a--->a3
                   Modified status:   a1-------->a3 
                Order is important, here: first update siblings, then delete the action.
-               Otherwise mr::deleteAction would complain of missing prevId/nextId no
-               longer found. */
+               Otherwise ActionRecorder::deleteAction() would complain of missing 
+               prevId/nextId no longer found. */
 
-               mr::updateSiblings(a1.id, a1prev.id, a3.id);
-               mr::updateSiblings(a3.id, a1.id, a3next.id);
-               mr::deleteAction(a.id);
+               g_engine.actionRecorder.updateSiblings(a1.id, a1prev.id, a3.id);
+               g_engine.actionRecorder.updateSiblings(a3.id, a1.id, a3next.id);
+               g_engine.actionRecorder.deleteAction(a.id);
        }
 
        recorder::updateChannel(channelId, /*updateActionEditor=*/false);
@@ -325,15 +316,11 @@ void deleteEnvelopeAction(ID channelId, const m::Action& a)
 
 void updateEnvelopeAction(ID channelId, const m::Action& a, Frame f, int value)
 {
-       namespace mr  = m::recorder;
-       namespace cr  = c::recorder;
-       namespace mrh = m::recorderHandler;
-
        /* Update the action directly if it is a boundary one. Else, delete the
        previous one and record a new action. */
 
-       if (mrh::isBoundaryEnvelopeAction(a))
-               mr::updateEvent(a.id, m::MidiEvent(m::MidiEvent::ENVELOPE, 0, value));
+       if (g_engine.actionRecorder.isBoundaryEnvelopeAction(a))
+               g_engine.actionRecorder.updateEvent(a.id, m::MidiEvent(m::MidiEvent::ENVELOPE, 0, value));
        else
        {
                deleteEnvelopeAction(channelId, a);
@@ -345,18 +332,16 @@ void updateEnvelopeAction(ID channelId, const m::Action& a, Frame f, int value)
 
 std::vector<m::Action> getActions(ID channelId)
 {
-       return m::recorder::getActionsOnChannel(channelId);
+       return g_engine.actionRecorder.getActionsOnChannel(channelId);
 }
 
 /* -------------------------------------------------------------------------- */
 
 void updateVelocity(const m::Action& a, int value)
 {
-       namespace mr = m::recorder;
-
        m::MidiEvent event(a.event);
        event.setVelocity(value);
 
-       mr::updateEvent(a.id, event);
+       g_engine.actionRecorder.updateEvent(a.id, event);
 }
 } // namespace giada::c::actionEditor
index ab4fab75fc963f84bf267c903599462e82cff4d8..899c0605761caecde52443901ef2a8aeee8e7cd7 100644 (file)
 namespace giada::m
 {
 struct Action;
-}
-namespace giada::m::channel
-{
-struct Data;
-}
-namespace giada::m::samplePlayer
-{
-struct Data;
-}
+class SamplePlayer;
+class Channel;
+} // namespace giada::m
+
 namespace giada::c::actionEditor
 {
 struct SampleData
 {
-       SampleData(const m::samplePlayer::Data&);
+       SampleData(const m::SamplePlayer&);
 
        SamplePlayerMode channelMode;
        bool             isLoopMode;
@@ -57,10 +52,17 @@ struct SampleData
 struct Data
 {
        Data() = default;
-       Data(const m::channel::Data&);
+       Data(const m::Channel&);
+
+       Frame getCurrentFrame() const;
+       bool  isChannelPlaying() const;
 
        ID                     channelId;
        std::string            channelName;
+       Frame                  framesInSeq;
+       Frame                  framesInBeat;
+       Frame                  framesInBar;
+       Frame                  framesInLoop;
        std::vector<m::Action> actions;
 
        std::optional<SampleData> sample;
index 5e37292c9d597913e2da024998d07a4150fb29f3..2928b6e93435d9c0fb7c3bf7472036999b2404cd 100644 (file)
  * -------------------------------------------------------------------------- */
 
 #include "gui/elems/mainWindow/keyboard/channel.h"
-#include "channel.h"
-#include "core/clock.h"
+#include "core/actions/actionRecorder.h"
+#include "core/channels/channelManager.h"
 #include "core/conf.h"
+#include "core/engine.h"
 #include "core/kernelAudio.h"
 #include "core/mixer.h"
 #include "core/mixerHandler.h"
 #include "core/model/model.h"
+#include "core/patch.h"
 #include "core/plugins/plugin.h"
 #include "core/plugins/pluginHost.h"
-#include "core/recManager.h"
+#include "core/plugins/pluginManager.h"
 #include "core/recorder.h"
 #include "core/wave.h"
 #include "core/waveManager.h"
+#include "glue/channel.h"
+#include "glue/main.h"
 #include "gui/dialogs/mainWindow.h"
 #include "gui/dialogs/sampleEditor.h"
 #include "gui/dialogs/warnings.h"
+#include "gui/dispatcher.h"
 #include "gui/elems/basics/dial.h"
 #include "gui/elems/basics/input.h"
 #include "gui/elems/mainWindow/keyboard/channelButton.h"
@@ -53,7 +58,8 @@
 #include "gui/elems/sampleEditor/volumeTool.h"
 #include "gui/elems/sampleEditor/waveTools.h"
 #include "gui/elems/sampleEditor/waveform.h"
-#include "main.h"
+#include "gui/ui.h"
+#include "src/core/actions/actions.h"
 #include "utils/fs.h"
 #include "utils/gui.h"
 #include "utils/log.h"
@@ -62,7 +68,8 @@
 #include <cmath>
 #include <functional>
 
-extern giada::v::gdMainWindow* G_MainWin;
+extern giada::v::Ui     g_ui;
+extern giada::m::Engine g_engine;
 
 namespace giada::c::channel
 {
@@ -85,8 +92,8 @@ void printLoadError_(int res)
 /* -------------------------------------------------------------------------- */
 /* -------------------------------------------------------------------------- */
 
-// TODO - just pass const channel::Data&
-SampleData::SampleData(const m::channel::Data& ch)
+// TODO - just pass const Channel&
+SampleData::SampleData(const m::Channel& ch)
 : waveId(ch.samplePlayer->getWaveId())
 , mode(ch.samplePlayer->mode)
 , isLoop(ch.samplePlayer->isAnyLoopMode())
@@ -95,7 +102,7 @@ SampleData::SampleData(const m::channel::Data& ch)
 {
 }
 
-Frame SampleData::getTracker() const { return m_channel->state->tracker.load(); }
+Frame SampleData::getTracker() const { return m_channel->shared->tracker.load(); }
 /* TODO - useless methods, turn them into member vars */
 Frame SampleData::getBegin() const { return m_channel->samplePlayer->begin; }
 Frame SampleData::getEnd() const { return m_channel->samplePlayer->end; }
@@ -104,7 +111,7 @@ bool  SampleData::getOverdubProtection() const { return m_channel->audioReceiver
 
 /* -------------------------------------------------------------------------- */
 
-MidiData::MidiData(const m::channel::Data& m)
+MidiData::MidiData(const m::Channel& m)
 : m_channel(&m)
 {
 }
@@ -115,8 +122,9 @@ int  MidiData::getFilter() const { return m_channel->midiSender->filter; }
 
 /* -------------------------------------------------------------------------- */
 
-Data::Data(const m::channel::Data& c)
-: id(c.id)
+Data::Data(const m::Channel& c)
+: viewDispatcher(g_ui.dispatcher)
+, id(c.id)
 , columnId(c.columnId)
 #ifdef WITH_VST
 , plugins(c.plugins)
@@ -136,14 +144,14 @@ Data::Data(const m::channel::Data& c)
                midi = std::make_optional<MidiData>(c);
 }
 
-ChannelStatus Data::getPlayStatus() const { return m_channel.state->playStatus.load(); }
-ChannelStatus Data::getRecStatus() const { return m_channel.state->recStatus.load(); }
-bool          Data::getReadActions() const { return m_channel.state->readActions.load(); }
-bool          Data::isRecordingInput() const { return m::recManager::isRecordingInput(); }
-bool          Data::isRecordingAction() const { return m::recManager::isRecordingAction(); }
+ChannelStatus Data::getPlayStatus() const { return m_channel.shared->playStatus.load(); }
+ChannelStatus Data::getRecStatus() const { return m_channel.shared->recStatus.load(); }
+bool          Data::getReadActions() const { return m_channel.shared->readActions.load(); }
+bool          Data::isRecordingInput() const { return g_engine.recorder.isRecordingInput(); }
+bool          Data::isRecordingAction() const { return g_engine.recorder.isRecordingAction(); }
 /* TODO - useless methods, turn them into member vars */
-bool Data::getSolo() const { return m_channel.solo; }
-bool Data::getMute() const { return m_channel.mute; }
+bool Data::getSolo() const { return m_channel.isSoloed(); }
+bool Data::getMute() const { return m_channel.isMuted(); }
 bool Data::isArmed() const { return m_channel.armed; }
 
 /* -------------------------------------------------------------------------- */
@@ -152,13 +160,13 @@ bool Data::isArmed() const { return m_channel.armed; }
 
 Data getData(ID channelId)
 {
-       return Data(m::model::get().getChannel(channelId));
+       return Data(g_engine.model.get().getChannel(channelId));
 }
 
 std::vector<Data> getChannels()
 {
        std::vector<Data> out;
-       for (const m::channel::Data& ch : m::model::get().channels)
+       for (const m::Channel& ch : g_engine.model.get().channels)
                if (!ch.isInternal())
                        out.push_back(Data(ch));
        return out;
@@ -168,46 +176,60 @@ std::vector<Data> getChannels()
 
 int loadChannel(ID channelId, const std::string& fname)
 {
+       m::WaveManager::Result res = g_engine.waveManager.createFromFile(fname, /*id=*/0,
+           g_engine.kernelAudio.getSampleRate(), g_engine.conf.data.rsmpQuality);
+
+       if (res.status != G_RES_OK)
+       {
+               printLoadError_(res.status);
+               return res.status;
+       }
+
        /* Save the patch and take the last browser's dir in order to re-use it the 
        next time. */
 
-       m::conf::conf.samplePath = u::fs::dirname(fname);
-
-       int res = m::mh::loadChannel(channelId, fname);
-       if (res != G_RES_OK)
-               printLoadError_(res);
+       g_engine.conf.data.samplePath = u::fs::dirname(fname);
+       g_engine.mixerHandler.loadChannel(channelId, std::move(res.wave));
 
-       return res;
+       return G_RES_OK;
 }
 
 /* -------------------------------------------------------------------------- */
 
 void addChannel(ID columnId, ChannelType type)
 {
-       m::mh::addChannel(type, columnId);
+       g_engine.mixerHandler.addChannel(type, columnId, g_engine.kernelAudio.getBufferSize(), g_engine.channelManager);
 }
 
 /* -------------------------------------------------------------------------- */
 
-void addAndLoadChannel(ID columnId, const std::string& fpath)
+void addAndLoadChannel(ID columnId, const std::string& fname)
 {
-       int res = m::mh::addAndLoadChannel(columnId, fpath);
-       if (res != G_RES_OK)
-               printLoadError_(res);
+       m::WaveManager::Result res = g_engine.waveManager.createFromFile(fname, /*id=*/0,
+           g_engine.kernelAudio.getSampleRate(), g_engine.conf.data.rsmpQuality);
+       if (res.status == G_RES_OK)
+               g_engine.mixerHandler.addAndLoadChannel(columnId, std::move(res.wave), g_engine.kernelAudio.getBufferSize(),
+                   g_engine.channelManager);
+       else
+               printLoadError_(res.status);
 }
 
-void addAndLoadChannels(ID columnId, const std::vector<std::string>& fpaths)
+void addAndLoadChannels(ID columnId, const std::vector<std::string>& fnames)
 {
-       if (fpaths.size() == 1)
-               return addAndLoadChannel(columnId, fpaths[0]);
-
        bool errors = false;
-       for (const std::string& f : fpaths)
-               if (m::mh::addAndLoadChannel(columnId, f) != G_RES_OK)
+       for (const std::string& f : fnames)
+       {
+               m::WaveManager::Result res = g_engine.waveManager.createFromFile(f, /*id=*/0,
+                   g_engine.kernelAudio.getSampleRate(), g_engine.conf.data.rsmpQuality);
+               if (res.status == G_RES_OK)
+                       g_engine.mixerHandler.addAndLoadChannel(columnId, std::move(res.wave), g_engine.kernelAudio.getBufferSize(),
+                           g_engine.channelManager);
+               else
                        errors = true;
+       }
 
        if (errors)
-               v::gdAlert("Some files weren't loaded sucessfully.");
+               v::gdAlert("Some files weren't loaded successfully.");
 }
 
 /* -------------------------------------------------------------------------- */
@@ -216,9 +238,16 @@ void deleteChannel(ID channelId)
 {
        if (!v::gdConfirmWin("Warning", "Delete channel: are you sure?"))
                return;
-       u::gui::closeAllSubwindows();
-       m::recorder::clearChannel(channelId);
-       m::mh::deleteChannel(channelId);
+       g_ui.closeAllSubwindows();
+
+#ifdef WITH_VST
+       const std::vector<m::Plugin*> plugins = g_engine.model.get().getChannel(channelId).plugins;
+#endif
+       g_engine.mixerHandler.deleteChannel(channelId);
+       g_engine.actionRecorder.clearChannel(channelId);
+#ifdef WITH_VST
+       g_engine.pluginHost.freePlugins(plugins);
+#endif
 }
 
 /* -------------------------------------------------------------------------- */
@@ -227,58 +256,66 @@ void freeChannel(ID channelId)
 {
        if (!v::gdConfirmWin("Warning", "Free channel: are you sure?"))
                return;
-       u::gui::closeAllSubwindows();
-       m::recorder::clearChannel(channelId);
-       m::mh::freeChannel(channelId);
+       g_ui.closeAllSubwindows();
+       g_engine.actionRecorder.clearChannel(channelId);
+       g_engine.mixerHandler.freeChannel(channelId);
 }
 
 /* -------------------------------------------------------------------------- */
 
 void setInputMonitor(ID channelId, bool value)
 {
-       m::model::get().getChannel(channelId).audioReceiver->inputMonitor = value;
-       m::model::swap(m::model::SwapType::SOFT);
+       g_engine.model.get().getChannel(channelId).audioReceiver->inputMonitor = value;
+       g_engine.model.swap(m::model::SwapType::SOFT);
 }
 
 /* -------------------------------------------------------------------------- */
 
 void setOverdubProtection(ID channelId, bool value)
 {
-       m::channel::Data& ch                = m::model::get().getChannel(channelId);
+       m::Channel& ch                      = g_engine.model.get().getChannel(channelId);
        ch.audioReceiver->overdubProtection = value;
        if (value == true && ch.armed)
                ch.armed = false;
-       m::model::swap(m::model::SwapType::SOFT);
+       g_engine.model.swap(m::model::SwapType::SOFT);
 }
 
 /* -------------------------------------------------------------------------- */
 
 void cloneChannel(ID channelId)
 {
-       m::mh::cloneChannel(channelId);
+       g_engine.actionRecorder.cloneActions(channelId, g_engine.channelManager.getNextId());
+#ifdef WITH_VST
+       g_engine.mixerHandler.cloneChannel(channelId, g_engine.patch.data.samplerate,
+           g_engine.kernelAudio.getBufferSize(), g_engine.channelManager, g_engine.waveManager,
+           g_engine.sequencer, g_engine.pluginManager);
+#else
+       g_engine.mixerHandler.cloneChannel(channelId, g_engine.kernelAudio.getBufferSize(),
+           g_engine.channelManager, g_engine.waveManager);
+#endif
 }
 
 /* -------------------------------------------------------------------------- */
 
 void setSamplePlayerMode(ID channelId, SamplePlayerMode mode)
 {
-       m::model::get().getChannel(channelId).samplePlayer->mode = mode;
-       m::model::swap(m::model::SwapType::HARD); // TODO - SOFT should be enough, fix geChannel refresh method
-       u::gui::refreshActionEditor();
+       g_engine.model.get().getChannel(channelId).samplePlayer->mode = mode;
+       g_engine.model.swap(m::model::SwapType::HARD); // TODO - SOFT should be enough, fix geChannel refresh method
+       g_ui.refreshSubWindow(WID_ACTION_EDITOR);
 }
 
 /* -------------------------------------------------------------------------- */
 
 void setHeight(ID channelId, Pixel p)
 {
-       m::model::get().getChannel(channelId).height = p;
-       m::model::swap(m::model::SwapType::SOFT);
+       g_engine.model.get().getChannel(channelId).height = p;
+       g_engine.model.swap(m::model::SwapType::SOFT);
 }
 
 /* -------------------------------------------------------------------------- */
 
 void setName(ID channelId, const std::string& name)
 {
-       m::mh::renameChannel(channelId, name);
+       g_engine.mixerHandler.renameChannel(channelId, name);
 }
 } // namespace giada::c::channel
index cddf96c288b5ff979e211e3d6eaa3b02b3709c91..0a380b683aa868fb9634b9b1132b40f2200175d0 100644 (file)
@@ -38,12 +38,18 @@ namespace giada::m
 {
 class Plugin;
 }
+
+namespace giada::v
+{
+class Dispatcher;
+}
+
 namespace giada::c::channel
 {
 struct SampleData
 {
        SampleData() = delete;
-       SampleData(const m::channel::Data&);
+       SampleData(const m::Channel&);
 
        Frame getTracker() const;
        Frame getBegin() const;
@@ -56,25 +62,25 @@ struct SampleData
        bool             isLoop;
        float            pitch;
 
-  private:
-       const m::channel::Data* m_channel;
+private:
+       const m::Channel* m_channel;
 };
 
 struct MidiData
 {
        MidiData() = delete;
-       MidiData(const m::channel::Data&);
+       MidiData(const m::Channel&);
 
        bool isOutputEnabled() const;
        int  getFilter() const;
 
-  private:
-       const m::channel::Data* m_channel;
+private:
+       const m::Channel* m_channel;
 };
 
 struct Data
 {
-       Data(const m::channel::Data&);
+       Data(const m::Channel&);
 
        bool          getMute() const;
        bool          getSolo() const;
@@ -85,6 +91,8 @@ struct Data
        bool          isRecordingInput() const;
        bool          isRecordingAction() const;
 
+       v::Dispatcher& viewDispatcher;
+
        ID id;
        ID columnId;
 #ifdef WITH_VST
@@ -101,8 +109,8 @@ struct Data
        std::optional<SampleData> sample;
        std::optional<MidiData>   midi;
 
-  private:
-       const m::channel::Data& m_channel;
+private:
+       const m::Channel& m_channel;
 };
 
 /* getChannels
index 9232c0f15ad70e7990399a1c76d171fd615b4369..088d0f7210c690cacf5cdcced313437525a838f1 100644 (file)
  *
  * -------------------------------------------------------------------------- */
 
-#include "config.h"
+#include "glue/config.h"
 #include "core/conf.h"
 #include "core/const.h"
+#include "core/engine.h"
 #include "core/kernelAudio.h"
+#include "core/kernelMidi.h"
+#include "core/midiMapper.h"
+#include "core/plugins/pluginManager.h"
 #include "deps/rtaudio/RtAudio.h"
+#include "gui/dialogs/browser/browserDir.h"
+#include "gui/dialogs/config.h"
+#include "gui/dialogs/warnings.h"
+#include "gui/elems/config/tabPlugins.h"
+#include "gui/ui.h"
+#include "utils/fs.h"
+#include "utils/vector.h"
+#include <FL/Fl_Tooltip.H>
+
+extern giada::v::Ui     g_ui;
+extern giada::m::Engine g_engine;
 
 namespace giada::c::config
 {
@@ -36,7 +51,7 @@ namespace
 {
 AudioDeviceData getAudioDeviceData_(DeviceType type, size_t index, int channelsCount, int channelsStart)
 {
-       for (const m::kernelAudio::Device& device : m::kernelAudio::getDevices())
+       for (const m::KernelAudio::Device& device : g_engine.kernelAudio.getDevices())
                if (device.index == index)
                        return AudioDeviceData(type, device, channelsCount, channelsStart);
        return AudioDeviceData();
@@ -47,7 +62,7 @@ AudioDeviceData getAudioDeviceData_(DeviceType type, size_t index, int channelsC
 /* -------------------------------------------------------------------------- */
 /* -------------------------------------------------------------------------- */
 
-AudioDeviceData::AudioDeviceData(DeviceType type, const m::kernelAudio::Device& device,
+AudioDeviceData::AudioDeviceData(DeviceType type, const m::KernelAudio::Device& device,
     int channelsCount, int channelsStart)
 : type(type)
 , index(device.index)
@@ -100,39 +115,39 @@ AudioData getAudioData()
 
 #if defined(G_OS_LINUX)
 
-       if (m::kernelAudio::hasAPI(RtAudio::LINUX_ALSA))
+       if (g_engine.kernelAudio.hasAPI(RtAudio::LINUX_ALSA))
                audioData.apis[G_SYS_API_ALSA] = "ALSA";
-       if (m::kernelAudio::hasAPI(RtAudio::UNIX_JACK))
+       if (g_engine.kernelAudio.hasAPI(RtAudio::UNIX_JACK))
                audioData.apis[G_SYS_API_JACK] = "Jack";
-       if (m::kernelAudio::hasAPI(RtAudio::LINUX_PULSE))
+       if (g_engine.kernelAudio.hasAPI(RtAudio::LINUX_PULSE))
                audioData.apis[G_SYS_API_PULSE] = "PulseAudio";
 
 #elif defined(G_OS_FREEBSD)
 
-       if (m::kernelAudio::hasAPI(RtAudio::UNIX_JACK))
+       if (g_engine.kernelAudio.hasAPI(RtAudio::UNIX_JACK))
                audioData.apis[G_SYS_API_JACK] = "Jack";
-       if (m::kernelAudio::hasAPI(RtAudio::LINUX_PULSE))
+       if (g_engine.kernelAudio.hasAPI(RtAudio::LINUX_PULSE))
                audioData.apis[G_SYS_API_PULSE] = "PulseAudio";
 
 #elif defined(G_OS_WINDOWS)
 
-       if (m::kernelAudio::hasAPI(RtAudio::WINDOWS_DS))
+       if (g_engine.kernelAudio.hasAPI(RtAudio::WINDOWS_DS))
                audioData.apis[G_SYS_API_DS] = "DirectSound";
-       if (m::kernelAudio::hasAPI(RtAudio::WINDOWS_ASIO))
+       if (g_engine.kernelAudio.hasAPI(RtAudio::WINDOWS_ASIO))
                audioData.apis[G_SYS_API_ASIO] = "ASIO";
-       if (m::kernelAudio::hasAPI(RtAudio::WINDOWS_WASAPI))
+       if (g_engine.kernelAudio.hasAPI(RtAudio::WINDOWS_WASAPI))
                audioData.apis[G_SYS_API_WASAPI] = "WASAPI";
 
 #elif defined(G_OS_MAC)
 
-       if (m::kernelAudio::hasAPI(RtAudio::MACOSX_CORE))
+       if (g_engine.kernelAudio.hasAPI(RtAudio::MACOSX_CORE))
                audioData.apis[G_SYS_API_CORE] = "CoreAudio";
 
 #endif
 
-       std::vector<m::kernelAudio::Device> devices = m::kernelAudio::getDevices();
+       std::vector<m::KernelAudio::Device> devices = g_engine.kernelAudio.getDevices();
 
-       for (const m::kernelAudio::Device& device : devices)
+       for (const m::KernelAudio::Device& device : devices)
        {
                if (device.maxOutputChannels > 0)
                        audioData.outputDevices.push_back(AudioDeviceData(DeviceType::OUTPUT, device, G_MAX_IO_CHANS, 0));
@@ -140,37 +155,177 @@ AudioData getAudioData()
                        audioData.inputDevices.push_back(AudioDeviceData(DeviceType::INPUT, device, 1, 0));
        }
 
-       audioData.api             = m::conf::conf.soundSystem;
-       audioData.bufferSize      = m::conf::conf.buffersize;
-       audioData.sampleRate      = m::conf::conf.samplerate;
-       audioData.limitOutput     = m::conf::conf.limitOutput;
-       audioData.recTriggerLevel = m::conf::conf.recTriggerLevel;
-       audioData.resampleQuality = m::conf::conf.rsmpQuality;
+       audioData.api             = g_engine.conf.data.soundSystem;
+       audioData.bufferSize      = g_engine.conf.data.buffersize;
+       audioData.sampleRate      = g_engine.conf.data.samplerate;
+       audioData.limitOutput     = g_engine.conf.data.limitOutput;
+       audioData.recTriggerLevel = g_engine.conf.data.recTriggerLevel;
+       audioData.resampleQuality = g_engine.conf.data.rsmpQuality;
        audioData.outputDevice    = getAudioDeviceData_(DeviceType::OUTPUT,
-        m::conf::conf.soundDeviceOut, m::conf::conf.channelsOutCount,
-        m::conf::conf.channelsOutStart);
+        g_engine.conf.data.soundDeviceOut, g_engine.conf.data.channelsOutCount,
+        g_engine.conf.data.channelsOutStart);
        audioData.inputDevice     = getAudioDeviceData_(DeviceType::INPUT,
-        m::conf::conf.soundDeviceIn, m::conf::conf.channelsInCount,
-        m::conf::conf.channelsInStart);
+        g_engine.conf.data.soundDeviceIn, g_engine.conf.data.channelsInCount,
+        g_engine.conf.data.channelsInStart);
 
        return audioData;
 }
 
 /* -------------------------------------------------------------------------- */
 
+MidiData getMidiData()
+{
+       MidiData midiData;
+
+#if defined(G_OS_LINUX)
+
+       if (g_engine.kernelMidi.hasAPI(RtMidi::LINUX_ALSA))
+               midiData.apis[G_MIDI_API_ALSA] = "ALSA";
+       if (g_engine.kernelMidi.hasAPI(RtMidi::UNIX_JACK))
+               midiData.apis[G_MIDI_API_JACK] = "JACK";
+
+#elif defined(G_OS_FREEBSD)
+
+       if (g_engine.kernelMidi.hasAPI(RtMidi::UNIX_JACK))
+               midiData.apis[G_MIDI_API_JACK] = "JACK";
+
+#elif defined(G_OS_WINDOWS)
+
+       if (g_engine.kernelMidi.hasAPI(RtMidi::WINDOWS_MM))
+               midiData.apis[G_MIDI_API_MM] = "Multimedia MIDI";
+
+#elif defined(G_OS_MAC)
+
+       if (g_engine.kernelMidi.hasAPI(RtMidi::MACOSX_CORE))
+               midiData.apis[G_MIDI_API_CORE] = "OSX Core MIDI";
+
+#endif
+
+       midiData.syncModes[G_MIDI_SYNC_NONE]    = "(disabled)";
+       midiData.syncModes[G_MIDI_SYNC_CLOCK_M] = "MIDI Clock (master)";
+       midiData.syncModes[G_MIDI_SYNC_MTC_M]   = "MTC (master)";
+
+       midiData.midiMaps = g_engine.midiMapper.getMapFilesFound();
+       midiData.midiMap  = u::vector::indexOf(midiData.midiMaps, g_engine.conf.data.midiMapPath);
+
+       for (unsigned i = 0; i < g_engine.kernelMidi.countOutPorts(); i++)
+               midiData.outPorts.push_back(g_engine.kernelMidi.getOutPortName(i));
+       for (unsigned i = 0; i < g_engine.kernelMidi.countInPorts(); i++)
+               midiData.inPorts.push_back(g_engine.kernelMidi.getInPortName(i));
+
+       midiData.api      = g_engine.conf.data.midiSystem;
+       midiData.syncMode = g_engine.conf.data.midiSync;
+       midiData.outPort  = g_engine.conf.data.midiPortOut;
+       midiData.inPort   = g_engine.conf.data.midiPortIn;
+
+       return midiData;
+}
+
+/* -------------------------------------------------------------------------- */
+
+#ifdef WITH_VST
+
+PluginData getPluginData()
+{
+       PluginData pluginData;
+       pluginData.numAvailablePlugins = g_engine.pluginManager.countAvailablePlugins();
+       pluginData.pluginPath          = g_engine.conf.data.pluginPath;
+       return pluginData;
+}
+
+#endif
+
+/* -------------------------------------------------------------------------- */
+
+MiscData getMiscData()
+{
+       MiscData miscData;
+       miscData.logMode      = g_engine.conf.data.logMode;
+       miscData.showTooltips = g_engine.conf.data.showTooltips;
+       return miscData;
+}
+/* -------------------------------------------------------------------------- */
+
 void save(const AudioData& data)
 {
-       m::conf::conf.soundSystem      = data.api;
-       m::conf::conf.soundDeviceOut   = data.outputDevice.index;
-       m::conf::conf.soundDeviceIn    = data.inputDevice.index;
-       m::conf::conf.channelsOutCount = data.outputDevice.channelsCount;
-       m::conf::conf.channelsOutStart = data.outputDevice.channelsStart;
-       m::conf::conf.channelsInCount  = data.inputDevice.channelsCount;
-       m::conf::conf.channelsInStart  = data.inputDevice.channelsStart;
-       m::conf::conf.limitOutput      = data.limitOutput;
-       m::conf::conf.rsmpQuality      = data.resampleQuality;
-       m::conf::conf.buffersize       = data.bufferSize;
-       m::conf::conf.recTriggerLevel  = data.recTriggerLevel;
-       m::conf::conf.samplerate       = data.sampleRate;
+       g_engine.conf.data.soundSystem      = data.api;
+       g_engine.conf.data.soundDeviceOut   = data.outputDevice.index;
+       g_engine.conf.data.soundDeviceIn    = data.inputDevice.index;
+       g_engine.conf.data.channelsOutCount = data.outputDevice.channelsCount;
+       g_engine.conf.data.channelsOutStart = data.outputDevice.channelsStart;
+       g_engine.conf.data.channelsInCount  = data.inputDevice.channelsCount;
+       g_engine.conf.data.channelsInStart  = data.inputDevice.channelsStart;
+       g_engine.conf.data.limitOutput      = data.limitOutput;
+       g_engine.conf.data.rsmpQuality      = data.resampleQuality;
+       g_engine.conf.data.buffersize       = data.bufferSize;
+       g_engine.conf.data.recTriggerLevel  = data.recTriggerLevel;
+       g_engine.conf.data.samplerate       = data.sampleRate;
+       g_engine.updateMixerModel();
 }
+
+/* -------------------------------------------------------------------------- */
+
+#ifdef WITH_VST
+
+void save(const PluginData& data)
+{
+       g_engine.conf.data.pluginPath = data.pluginPath;
+}
+
+#endif
+
+/* -------------------------------------------------------------------------- */
+
+void save(const MidiData& data)
+{
+       g_engine.conf.data.midiSystem  = data.api;
+       g_engine.conf.data.midiPortOut = data.outPort;
+       g_engine.conf.data.midiPortIn  = data.inPort;
+       //g_engine.conf.data.midiMapPath = data.midiMap >= 0 && data.midiMap < (int)data.midiMaps.size() ? data.midiMaps[data.midiMap] : "";
+       g_engine.conf.data.midiMapPath = u::vector::atOr(data.midiMaps, data.midiMap, "");
+       g_engine.conf.data.midiSync    = data.syncMode;
+}
+
+/* -------------------------------------------------------------------------- */
+
+void save(const MiscData& data)
+{
+       g_engine.conf.data.logMode      = data.logMode;
+       g_engine.conf.data.showTooltips = data.showTooltips;
+       Fl_Tooltip::enable(g_engine.conf.data.showTooltips);
+}
+
+/* -------------------------------------------------------------------------- */
+
+#ifdef WITH_VST
+
+void scanPlugins(std::string dir, const std::function<void(float)>& progress)
+{
+       g_engine.pluginManager.scanDirs(dir, progress);
+       g_engine.pluginManager.saveList(u::fs::getHomePath() + G_SLASH + "plugins.xml");
+}
+
+/* -------------------------------------------------------------------------- */
+
+void setPluginPathCb(void* data)
+{
+       v::gdBrowserDir* browser    = static_cast<v::gdBrowserDir*>(data);
+       std::string&     pluginPath = g_engine.conf.data.pluginPath;
+
+       if (browser->getCurrentPath() == "")
+       {
+               v::gdAlert("Invalid path.");
+               return;
+       }
+
+       if (!pluginPath.empty() && pluginPath.back() != ';')
+               pluginPath += ";";
+       pluginPath += browser->getCurrentPath();
+
+       browser->do_callback();
+
+       v::gdConfig* configWin = static_cast<v::gdConfig*>(g_ui.getSubwindow(*g_ui.mainWindow.get(), WID_CONFIG));
+       configWin->tabPlugins->rebuild();
+}
+#endif
 } // namespace giada::c::config
\ No newline at end of file
index fb398171cf879fb60a33b6fb304b32aa4f4be952..ae479e33cb179b562b54e2029c75baaafdef2c3c 100644 (file)
 #ifndef G_GLUE_CONFIG_H
 #define G_GLUE_CONFIG_H
 
+#include "core/kernelAudio.h"
 #include "core/types.h"
 #include <map>
 #include <string>
 #include <vector>
 
-namespace giada::m::kernelAudio
-{
-struct Device;
-}
 namespace giada::c::config
 {
 enum class DeviceType
@@ -47,7 +44,7 @@ enum class DeviceType
 struct AudioDeviceData
 {
        AudioDeviceData() = default;
-       AudioDeviceData(DeviceType t, const m::kernelAudio::Device&, int channelsCount, int channelsStart);
+       AudioDeviceData(DeviceType t, const m::KernelAudio::Device&, int channelsCount, int channelsStart);
 
        DeviceType       type        = DeviceType::OUTPUT;
        int              index       = -1;
@@ -82,14 +79,62 @@ struct AudioData
        int             resampleQuality;
 };
 
-/* getAudioData
-Returns viewModel object filled with data. */
+struct MidiData
+{
+       std::map<int, std::string> apis;
+       std::map<int, std::string> syncModes;
+       std::vector<std::string>   midiMaps;
+       std::vector<std::string>   outPorts;
+       std::vector<std::string>   inPorts;
+
+       /* Selectable values. */
+
+       int api;
+       int syncMode;
+       int midiMap;
+       int outPort;
+       int inPort;
+};
+
+#ifdef WITH_VST
+
+struct PluginData
+{
+       int         numAvailablePlugins;
+       std::string pluginPath;
+};
+
+#endif
+
+struct MiscData
+{
+       int  logMode;
+       bool showTooltips;
+};
+
+/* get*
+Return viewModel objects filled with data. */
 
 AudioData getAudioData();
-/*
-AudioDeviceData getAudioDeviceData(size_t index, int channelsCount, int channelsStart);
-*/
+MidiData  getMidiData();
+MiscData  getMiscData();
+#ifdef WITH_VST
+PluginData getPluginData();
+#endif
+
 void save(const AudioData&);
+void save(const MidiData&);
+void save(const MiscData&);
+#ifdef WITH_VST
+void save(const PluginData&);
+void scanPlugins(std::string dir, const std::function<void(float)>& progress);
+
+/* setPluginPathCb
+Callback attached to the DirBrowser for adding new Plug-in search paths in the
+configuration window. */
+
+void setPluginPathCb(void* data);
+#endif
 } // namespace giada::c::config
 
 #endif
index a6bd9097461793350040e5aed15a317827c58a72..30a10437e6d4d4110d1fff1e7b519be146a46b0e 100644 (file)
  * -------------------------------------------------------------------------- */
 
 #include "events.h"
-#include "core/clock.h"
 #include "core/conf.h"
 #include "core/const.h"
+#include "core/engine.h"
 #include "core/eventDispatcher.h"
+#include "core/kernelAudio.h"
 #include "core/midiEvent.h"
 #include "core/mixer.h"
 #include "core/mixerHandler.h"
 #include "core/model/model.h"
 #include "core/plugins/pluginHost.h"
-#include "core/recManager.h"
+#include "core/recorder.h"
 #include "core/sequencer.h"
 #include "core/types.h"
 #include "glue/main.h"
 #include "gui/elems/sampleEditor/panTool.h"
 #include "gui/elems/sampleEditor/pitchTool.h"
 #include "gui/elems/sampleEditor/volumeTool.h"
+#include "gui/ui.h"
 #include "utils/log.h"
 #include <FL/Fl.H>
 #include <cassert>
 
-extern giada::v::gdMainWindow* G_MainWin;
+extern giada::v::Ui     g_ui;
+extern giada::m::Engine g_engine;
 
 namespace giada::c::events
 {
 namespace
 {
-void pushEvent_(m::eventDispatcher::Event e, Thread t)
+void pushEvent_(m::EventDispatcher::Event e, Thread t)
 {
        bool res = true;
        if (t == Thread::MAIN)
-               res = m::eventDispatcher::UIevents.push(e);
+               res = g_engine.eventDispatcher.UIevents.push(e);
        else if (t == Thread::MIDI)
-               res = m::eventDispatcher::MidiEvents.push(e);
+               res = g_engine.eventDispatcher.MidiEvents.push(e);
        else
                assert(false);
 
@@ -84,17 +87,17 @@ void pressChannel(ID channelId, int velocity, Thread t)
 {
        m::MidiEvent e;
        e.setVelocity(velocity);
-       pushEvent_({m::eventDispatcher::EventType::KEY_PRESS, 0, channelId, velocity}, t);
+       pushEvent_({m::EventDispatcher::EventType::KEY_PRESS, 0, channelId, velocity}, t);
 }
 
 void releaseChannel(ID channelId, Thread t)
 {
-       pushEvent_({m::eventDispatcher::EventType::KEY_RELEASE, 0, channelId, {}}, t);
+       pushEvent_({m::EventDispatcher::EventType::KEY_RELEASE, 0, channelId, {}}, t);
 }
 
 void killChannel(ID channelId, Thread t)
 {
-       pushEvent_({m::eventDispatcher::EventType::KEY_KILL, 0, channelId, {}}, t);
+       pushEvent_({m::EventDispatcher::EventType::KEY_KILL, 0, channelId, {}}, t);
 }
 
 /* -------------------------------------------------------------------------- */
@@ -103,14 +106,14 @@ void setChannelVolume(ID channelId, float v, Thread t)
 {
        v = std::clamp(v, 0.0f, G_MAX_VOLUME);
 
-       pushEvent_({m::eventDispatcher::EventType::CHANNEL_VOLUME, 0, channelId, v}, t);
+       pushEvent_({m::EventDispatcher::EventType::CHANNEL_VOLUME, 0, channelId, v}, t);
 
        sampleEditor::onRefresh(t == Thread::MAIN, [v](v::gdSampleEditor& e) { e.volumeTool->update(v); });
 
        if (t != Thread::MAIN)
        {
                Fl::lock();
-               G_MainWin->keyboard->getChannel(channelId)->vol->value(v);
+               g_ui.mainWindow->keyboard->getChannel(channelId)->vol->value(v);
                Fl::unlock();
        }
 }
@@ -121,7 +124,7 @@ void setChannelPitch(ID channelId, float v, Thread t)
 {
        v = std::clamp(v, G_MIN_PITCH, G_MAX_PITCH);
 
-       pushEvent_({m::eventDispatcher::EventType::CHANNEL_PITCH, 0, channelId, v}, t);
+       pushEvent_({m::EventDispatcher::EventType::CHANNEL_PITCH, 0, channelId, v}, t);
 
        sampleEditor::onRefresh(t == Thread::MAIN, [v](v::gdSampleEditor& e) { e.pitchTool->update(v); });
 }
@@ -133,7 +136,7 @@ void sendChannelPan(ID channelId, float v)
        v = std::clamp(v, 0.0f, G_MAX_PAN);
 
        /* Pan event is currently triggered only by the main thread. */
-       pushEvent_({m::eventDispatcher::EventType::CHANNEL_PAN, 0, channelId, v}, Thread::MAIN);
+       pushEvent_({m::EventDispatcher::EventType::CHANNEL_PAN, 0, channelId, v}, Thread::MAIN);
 
        sampleEditor::onRefresh(/*gui=*/true, [v](v::gdSampleEditor& e) { e.panTool->update(v); });
 }
@@ -142,67 +145,67 @@ void sendChannelPan(ID channelId, float v)
 
 void toggleMuteChannel(ID channelId, Thread t)
 {
-       pushEvent_({m::eventDispatcher::EventType::CHANNEL_MUTE, 0, channelId, {}}, t);
+       pushEvent_({m::EventDispatcher::EventType::CHANNEL_MUTE, 0, channelId, {}}, t);
 }
 
 void toggleSoloChannel(ID channelId, Thread t)
 {
-       pushEvent_({m::eventDispatcher::EventType::CHANNEL_SOLO, 0, channelId, {}}, t);
+       pushEvent_({m::EventDispatcher::EventType::CHANNEL_SOLO, 0, channelId, {}}, t);
 }
 
 /* -------------------------------------------------------------------------- */
 
 void toggleArmChannel(ID channelId, Thread t)
 {
-       pushEvent_({m::eventDispatcher::EventType::CHANNEL_TOGGLE_ARM, 0, channelId, {}}, t);
+       pushEvent_({m::EventDispatcher::EventType::CHANNEL_TOGGLE_ARM, 0, channelId, {}}, t);
 }
 
 void toggleReadActionsChannel(ID channelId, Thread t)
 {
-       pushEvent_({m::eventDispatcher::EventType::CHANNEL_TOGGLE_READ_ACTIONS, 0, channelId, {}}, t);
+       pushEvent_({m::EventDispatcher::EventType::CHANNEL_TOGGLE_READ_ACTIONS, 0, channelId, {}}, t);
 }
 
 void killReadActionsChannel(ID channelId, Thread t)
 {
-       pushEvent_({m::eventDispatcher::EventType::CHANNEL_KILL_READ_ACTIONS, 0, channelId, {}}, t);
+       pushEvent_({m::EventDispatcher::EventType::CHANNEL_KILL_READ_ACTIONS, 0, channelId, {}}, t);
 }
 
 /* -------------------------------------------------------------------------- */
 
 void sendMidiToChannel(ID channelId, m::MidiEvent e, Thread t)
 {
-       pushEvent_({m::eventDispatcher::EventType::MIDI, 0, channelId, m::Action{0, channelId, 0, e}}, t);
+       pushEvent_({m::EventDispatcher::EventType::MIDI, 0, channelId, m::Action{0, channelId, 0, e}}, t);
 }
 
 /* -------------------------------------------------------------------------- */
 
 void toggleMetronome()
 {
-       m::sequencer::toggleMetronome();
+       g_engine.sequencer.toggleMetronome();
 }
 
 /* -------------------------------------------------------------------------- */
 
 void setMasterInVolume(float v, Thread t)
 {
-       pushEvent_({m::eventDispatcher::EventType::CHANNEL_VOLUME, 0, m::mixer::MASTER_IN_CHANNEL_ID, v}, t);
+       pushEvent_({m::EventDispatcher::EventType::CHANNEL_VOLUME, 0, m::Mixer::MASTER_IN_CHANNEL_ID, v}, t);
 
        if (t != Thread::MAIN)
        {
                Fl::lock();
-               G_MainWin->mainIO->setInVol(v);
+               g_ui.mainWindow->mainIO->setInVol(v);
                Fl::unlock();
        }
 }
 
 void setMasterOutVolume(float v, Thread t)
 {
-       pushEvent_({m::eventDispatcher::EventType::CHANNEL_VOLUME, 0, m::mixer::MASTER_OUT_CHANNEL_ID, v}, t);
+       pushEvent_({m::EventDispatcher::EventType::CHANNEL_VOLUME, 0, m::Mixer::MASTER_OUT_CHANNEL_ID, v}, t);
 
        if (t != Thread::MAIN)
        {
                Fl::lock();
-               G_MainWin->mainIO->setOutVol(v);
+               g_ui.mainWindow->mainIO->setOutVol(v);
                Fl::unlock();
        }
 }
@@ -211,47 +214,56 @@ void setMasterOutVolume(float v, Thread t)
 
 void multiplyBeats()
 {
-       main::setBeats(m::clock::getBeats() * 2, m::clock::getBars());
+       main::setBeats(g_engine.sequencer.getBeats() * 2, g_engine.sequencer.getBars());
 }
 
 void divideBeats()
 {
-       main::setBeats(m::clock::getBeats() / 2, m::clock::getBars());
+       main::setBeats(g_engine.sequencer.getBeats() / 2, g_engine.sequencer.getBars());
 }
 
 /* -------------------------------------------------------------------------- */
 
 void startSequencer(Thread t)
 {
-       pushEvent_({m::eventDispatcher::EventType::SEQUENCER_START, 0, 0, {}}, t);
-       m::conf::conf.recTriggerMode = RecTriggerMode::NORMAL;
+       pushEvent_({m::EventDispatcher::EventType::SEQUENCER_START, 0, 0, {}}, t);
 }
 
 void stopSequencer(Thread t)
 {
-       pushEvent_({m::eventDispatcher::EventType::SEQUENCER_STOP, 0, 0, {}}, t);
+       pushEvent_({m::EventDispatcher::EventType::SEQUENCER_STOP, 0, 0, {}}, t);
 }
 
 void toggleSequencer(Thread t)
 {
-       m::clock::isRunning() ? stopSequencer(t) : startSequencer(t);
+       g_engine.sequencer.isRunning() ? stopSequencer(t) : startSequencer(t);
 }
 
 void rewindSequencer(Thread t)
 {
-       pushEvent_({m::eventDispatcher::EventType::SEQUENCER_REWIND, 0, 0, {}}, t);
+       pushEvent_({m::EventDispatcher::EventType::SEQUENCER_REWIND, 0, 0, {}}, t);
 }
 
 /* -------------------------------------------------------------------------- */
 
 void toggleActionRecording()
 {
-       m::recManager::toggleActionRec(m::conf::conf.recTriggerMode);
+       if (!g_engine.kernelAudio.isReady())
+               return;
+       if (g_engine.recorder.isRecordingAction())
+               g_engine.recorder.stopActionRec(g_engine.actionRecorder);
+       else
+               g_engine.recorder.prepareActionRec(g_engine.conf.data.recTriggerMode);
 }
 
 void toggleInputRecording()
 {
-       m::recManager::toggleInputRec(m::conf::conf.recTriggerMode, m::conf::conf.inputRecMode);
+       if (!g_engine.kernelAudio.isReady() || !g_engine.kernelAudio.isInputEnabled() || !g_engine.mixerHandler.hasInputRecordableChannels())
+               return;
+       if (g_engine.recorder.isRecordingInput())
+               g_engine.recorder.stopInputRec(g_engine.conf.data.inputRecMode, g_engine.kernelAudio.getSampleRate());
+       else
+               g_engine.recorder.prepareInputRec(g_engine.conf.data.recTriggerMode, g_engine.conf.data.inputRecMode);
 }
 
 /* -------------------------------------------------------------------------- */
@@ -259,7 +271,7 @@ void toggleInputRecording()
 #ifdef WITH_VST
 void setPluginParameter(ID pluginId, int paramIndex, float value, bool gui)
 {
-       m::pluginHost::setPluginParameter(pluginId, paramIndex, value);
+       g_engine.pluginHost.setPluginParameter(pluginId, paramIndex, value);
        c::plugin::updateWindow(pluginId, gui);
 }
 #endif
index 64a3f5b83d8b414c9014e6b617506ea93a2657a4..6816afa8a5df071cd64264c59008e5120a1ad9d1 100644 (file)
@@ -38,6 +38,7 @@ namespace giada::m
 {
 class MidiEvent;
 }
+
 namespace giada::c::events
 {
 /* Channel*
index aad617f489399cfa3b9adb274d7006848aac3510..4555a6d1617de18481643e5b6ed5e8fadd645ff3 100644 (file)
  *
  * -------------------------------------------------------------------------- */
 
-#include "io.h"
-#include "channel.h"
-#include "core/clock.h"
+#include "glue/io.h"
 #include "core/conf.h"
+#include "core/engine.h"
 #include "core/kernelAudio.h"
 #include "core/midiDispatcher.h"
 #include "core/mixer.h"
 #include "core/mixerHandler.h"
 #include "core/model/model.h"
-#include "core/recManager.h"
 #include "core/recorder.h"
-#include "core/recorderHandler.h"
 #include "core/wave.h"
+#include "glue/channel.h"
+#include "glue/main.h"
 #include "gui/dialogs/mainWindow.h"
 #include "gui/dialogs/midiIO/midiInputBase.h"
 #include "gui/dialogs/warnings.h"
 #include "gui/elems/mainWindow/keyboard/sampleChannel.h"
 #include "gui/elems/mainWindow/mainTimer.h"
 #include "gui/elems/mainWindow/mainTransport.h"
-#include "main.h"
-#include "utils/gui.h"
+#include "gui/ui.h"
+#include "src/core/actions/actionRecorder.h"
+#include "src/core/actions/actions.h"
 #include "utils/log.h"
 #include "utils/math.h"
 #include <FL/Fl.H>
 
-extern giada::v::gdMainWindow* G_MainWin;
+extern giada::v::Ui     g_ui;
+extern giada::m::Engine g_engine;
 
 namespace giada::c::io
 {
@@ -61,8 +62,8 @@ namespace
 {
 void rebuildMidiWindows_()
 {
-       u::gui::rebuildSubWindow(WID_MIDI_INPUT);
-       u::gui::rebuildSubWindow(WID_MIDI_OUTPUT);
+       g_ui.rebuildSubWindow(WID_MIDI_INPUT);
+       g_ui.rebuildSubWindow(WID_MIDI_OUTPUT);
 }
 } // namespace
 
@@ -70,7 +71,7 @@ void rebuildMidiWindows_()
 /* -------------------------------------------------------------------------- */
 /* -------------------------------------------------------------------------- */
 
-Channel_InputData::Channel_InputData(const m::channel::Data& c)
+Channel_InputData::Channel_InputData(const m::Channel& c)
 : channelId(c.id)
 , channelType(c.type)
 , enabled(c.midiLearner.enabled)
@@ -101,7 +102,7 @@ Channel_InputData::Channel_InputData(const m::channel::Data& c)
 
 /* -------------------------------------------------------------------------- */
 
-MidiChannel_OutputData::MidiChannel_OutputData(const m::midiSender::Data& s)
+MidiChannel_OutputData::MidiChannel_OutputData(const m::MidiSender& s)
 : enabled(s.enabled)
 , filter(s.filter)
 {
@@ -109,7 +110,7 @@ MidiChannel_OutputData::MidiChannel_OutputData(const m::midiSender::Data& s)
 
 /* -------------------------------------------------------------------------- */
 
-Channel_OutputData::Channel_OutputData(const m::channel::Data& c)
+Channel_OutputData::Channel_OutputData(const m::Channel& c)
 : channelId(c.id)
 , lightningEnabled(c.midiLighter.enabled)
 , lightningPlaying(c.midiLighter.playing.getValue())
@@ -143,29 +144,29 @@ Master_InputData::Master_InputData(const m::model::MidiIn& midiIn)
 
 Channel_InputData channel_getInputData(ID channelId)
 {
-       return Channel_InputData(m::model::get().getChannel(channelId));
+       return Channel_InputData(g_engine.model.get().getChannel(channelId));
 }
 
 /* -------------------------------------------------------------------------- */
 
 Channel_OutputData channel_getOutputData(ID channelId)
 {
-       return Channel_OutputData(m::model::get().getChannel(channelId));
+       return Channel_OutputData(g_engine.model.get().getChannel(channelId));
 }
 
 /* -------------------------------------------------------------------------- */
 
 Master_InputData master_getInputData()
 {
-       return Master_InputData(m::model::get().midiIn);
+       return Master_InputData(g_engine.model.get().midiIn);
 }
 
 /* -------------------------------------------------------------------------- */
 
 void channel_enableMidiLearn(ID channelId, bool v)
 {
-       m::model::get().getChannel(channelId).midiLearner.enabled = v;
-       m::model::swap(m::model::SwapType::NONE);
+       g_engine.model.get().getChannel(channelId).midiLearner.enabled = v;
+       g_engine.model.swap(m::model::SwapType::NONE);
        rebuildMidiWindows_();
 }
 
@@ -173,8 +174,8 @@ void channel_enableMidiLearn(ID channelId, bool v)
 
 void channel_enableMidiLightning(ID channelId, bool v)
 {
-       m::model::get().getChannel(channelId).midiLighter.enabled = v;
-       m::model::swap(m::model::SwapType::NONE);
+       g_engine.model.get().getChannel(channelId).midiLighter.enabled = v;
+       g_engine.model.swap(m::model::SwapType::NONE);
        rebuildMidiWindows_();
 }
 
@@ -182,8 +183,8 @@ void channel_enableMidiLightning(ID channelId, bool v)
 
 void channel_enableMidiOutput(ID channelId, bool v)
 {
-       m::model::get().getChannel(channelId).midiSender->enabled = v;
-       m::model::swap(m::model::SwapType::NONE);
+       g_engine.model.get().getChannel(channelId).midiSender->enabled = v;
+       g_engine.model.swap(m::model::SwapType::NONE);
        rebuildMidiWindows_();
 }
 
@@ -191,49 +192,49 @@ void channel_enableMidiOutput(ID channelId, bool v)
 
 void channel_enableVelocityAsVol(ID channelId, bool v)
 {
-       m::model::get().getChannel(channelId).samplePlayer->velocityAsVol = v;
-       m::model::swap(m::model::SwapType::NONE);
+       g_engine.model.get().getChannel(channelId).samplePlayer->velocityAsVol = v;
+       g_engine.model.swap(m::model::SwapType::NONE);
 }
 
 /* -------------------------------------------------------------------------- */
 
 void channel_setMidiInputFilter(ID channelId, int ch)
 {
-       m::model::get().getChannel(channelId).midiLearner.filter = ch;
-       m::model::swap(m::model::SwapType::NONE);
+       g_engine.model.get().getChannel(channelId).midiLearner.filter = ch;
+       g_engine.model.swap(m::model::SwapType::NONE);
 }
 
 void channel_setMidiOutputFilter(ID channelId, int ch)
 {
-       m::model::get().getChannel(channelId).midiSender->filter = ch;
-       m::model::swap(m::model::SwapType::NONE);
+       g_engine.model.get().getChannel(channelId).midiSender->filter = ch;
+       g_engine.model.swap(m::model::SwapType::NONE);
 }
 
 /* -------------------------------------------------------------------------- */
 
 void channel_setKey(ID channelId, int k)
 {
-       m::model::get().getChannel(channelId).key = k;
-       m::model::swap(m::model::SwapType::HARD);
+       g_engine.model.get().getChannel(channelId).key = k;
+       g_engine.model.swap(m::model::SwapType::HARD);
 }
 
 /* -------------------------------------------------------------------------- */
 
 void channel_startMidiLearn(int param, ID channelId)
 {
-       m::midiDispatcher::startChannelLearn(param, channelId, rebuildMidiWindows_);
+       g_engine.midiDispatcher.startChannelLearn(param, channelId, rebuildMidiWindows_);
 }
 
 void master_startMidiLearn(int param)
 {
-       m::midiDispatcher::startMasterLearn(param, rebuildMidiWindows_);
+       g_engine.midiDispatcher.startMasterLearn(param, rebuildMidiWindows_);
 }
 
 #ifdef WITH_VST
 
 void plugin_startMidiLearn(int paramIndex, ID pluginId)
 {
-       m::midiDispatcher::startPluginLearn(paramIndex, pluginId, rebuildMidiWindows_);
+       g_engine.midiDispatcher.startPluginLearn(paramIndex, pluginId, rebuildMidiWindows_);
 }
 
 #endif
@@ -242,7 +243,7 @@ void plugin_startMidiLearn(int paramIndex, ID pluginId)
 
 void stopMidiLearn()
 {
-       m::midiDispatcher::stopLearn();
+       g_engine.midiDispatcher.stopLearn();
        rebuildMidiWindows_();
 }
 
@@ -250,19 +251,19 @@ void stopMidiLearn()
 
 void channel_clearMidiLearn(int param, ID channelId)
 {
-       m::midiDispatcher::clearChannelLearn(param, channelId, rebuildMidiWindows_);
+       g_engine.midiDispatcher.clearChannelLearn(param, channelId, rebuildMidiWindows_);
 }
 
 void master_clearMidiLearn(int param)
 {
-       m::midiDispatcher::clearMasterLearn(param, rebuildMidiWindows_);
+       g_engine.midiDispatcher.clearMasterLearn(param, rebuildMidiWindows_);
 }
 
 #ifdef WITH_VST
 
 void plugin_clearMidiLearn(int param, ID pluginId)
 {
-       m::midiDispatcher::clearPluginLearn(param, pluginId, rebuildMidiWindows_);
+       g_engine.midiDispatcher.clearPluginLearn(param, pluginId, rebuildMidiWindows_);
 }
 
 #endif
@@ -271,8 +272,8 @@ void plugin_clearMidiLearn(int param, ID pluginId)
 
 void master_enableMidiLearn(bool v)
 {
-       m::model::get().midiIn.enabled = v;
-       m::model::swap(m::model::SwapType::NONE);
+       g_engine.model.get().midiIn.enabled = v;
+       g_engine.model.swap(m::model::SwapType::NONE);
        rebuildMidiWindows_();
 }
 
@@ -280,7 +281,7 @@ void master_enableMidiLearn(bool v)
 
 void master_setMidiFilter(int c)
 {
-       m::model::get().midiIn.filter = c;
-       m::model::swap(m::model::SwapType::NONE);
+       g_engine.model.get().midiIn.filter = c;
+       g_engine.model.swap(m::model::SwapType::NONE);
 }
 } // namespace giada::c::io
index 69f24f9f5082de9cc0ee14612951d03fc9fe7ca8..f8624723bfa3a5c19278325ed292614e4d9cb395 100644 (file)
 #include "core/model/model.h"
 #include "core/types.h"
 
-namespace giada::m::channel
+namespace giada::m
 {
-struct Data;
+class Channel;
 }
+
 namespace giada::c::io
 {
 struct PluginParamData
@@ -54,7 +55,7 @@ struct PluginData
 struct Channel_InputData
 {
        Channel_InputData() = default;
-       Channel_InputData(const m::channel::Data&);
+       Channel_InputData(const m::Channel&);
 
        ID          channelId;
        ChannelType channelType;
@@ -96,7 +97,7 @@ struct Master_InputData
 
 struct MidiChannel_OutputData
 {
-       MidiChannel_OutputData(const m::midiSender::Data&);
+       MidiChannel_OutputData(const m::MidiSender&);
 
        bool enabled;
        int  filter;
@@ -105,7 +106,7 @@ struct MidiChannel_OutputData
 struct Channel_OutputData
 {
        Channel_OutputData() = default;
-       Channel_OutputData(const m::channel::Data&);
+       Channel_OutputData(const m::Channel&);
 
        ID       channelId;
        bool     lightningEnabled;
diff --git a/src/glue/layout.cpp b/src/glue/layout.cpp
new file mode 100644 (file)
index 0000000..711aa24
--- /dev/null
@@ -0,0 +1,240 @@
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+#include "glue/layout.h"
+#include "core/conf.h"
+#include "core/engine.h"
+#include "core/patch.h"
+#include "core/sequencer.h"
+#include "glue/config.h"
+#include "glue/storage.h"
+#include "gui/dialogs/about.h"
+#include "gui/dialogs/actionEditor/midiActionEditor.h"
+#include "gui/dialogs/actionEditor/sampleActionEditor.h"
+#include "gui/dialogs/beatsInput.h"
+#include "gui/dialogs/bpmInput.h"
+#include "gui/dialogs/browser/browserDir.h"
+#include "gui/dialogs/browser/browserLoad.h"
+#include "gui/dialogs/browser/browserSave.h"
+#include "gui/dialogs/channelNameInput.h"
+#include "gui/dialogs/config.h"
+#include "gui/dialogs/keyGrabber.h"
+#include "gui/dialogs/mainWindow.h"
+#include "gui/dialogs/midiIO/midiInputChannel.h"
+#include "gui/dialogs/midiIO/midiInputMaster.h"
+#include "gui/dialogs/midiIO/midiOutputMidiCh.h"
+#include "gui/dialogs/midiIO/midiOutputSampleCh.h"
+#include "gui/dialogs/pluginChooser.h"
+#include "gui/dialogs/pluginList.h"
+#include "gui/dialogs/sampleEditor.h"
+#include "gui/ui.h"
+
+extern giada::v::Ui     g_ui;
+extern giada::m::Engine g_engine;
+
+namespace giada::c::layout
+{
+void openBrowserForProjectLoad()
+{
+       v::gdWindow* childWin = new v::gdBrowserLoad("Open project", g_engine.conf.data.patchPath,
+           c::storage::loadProject, 0, g_engine.conf.data);
+       g_ui.openSubWindow(*g_ui.mainWindow.get(), childWin, WID_FILE_BROWSER);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void openBrowserForProjectSave()
+{
+       v::gdWindow* childWin = new v::gdBrowserSave("Save project", g_engine.conf.data.patchPath,
+           g_engine.patch.data.name, c::storage::saveProject, 0, g_engine.conf.data);
+       g_ui.openSubWindow(*g_ui.mainWindow.get(), childWin, WID_FILE_BROWSER);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void openBrowserForSampleLoad(ID channelId)
+{
+       v::gdWindow* w = new v::gdBrowserLoad("Browse sample",
+           g_engine.conf.data.samplePath.c_str(), c::storage::loadSample, channelId, g_engine.conf.data);
+       g_ui.openSubWindow(*g_ui.mainWindow.get(), w, WID_FILE_BROWSER);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void openBrowserForSampleSave(ID channelId)
+{
+       v::gdWindow* w = new v::gdBrowserSave("Save sample",
+           g_engine.conf.data.samplePath.c_str(), "", c::storage::saveSample, channelId, g_engine.conf.data);
+       g_ui.openSubWindow(*g_ui.mainWindow.get(), w, WID_FILE_BROWSER);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void openAboutWindow()
+{
+       g_ui.openSubWindow(*g_ui.mainWindow.get(), new v::gdAbout(), WID_ABOUT);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void openKeyGrabberWindow(const c::channel::Data& data)
+{
+       g_ui.openSubWindow(*g_ui.mainWindow.get(), new v::gdKeyGrabber(data), WID_KEY_GRABBER);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void openBpmWindow(std::string bpmValue)
+{
+       g_ui.openSubWindow(*g_ui.mainWindow.get(), new v::gdBpmInput(bpmValue.c_str()), WID_BPM);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void openBeatsWindow(int beats, int bars)
+{
+       g_ui.openSubWindow(*g_ui.mainWindow.get(), new v::gdBeatsInput(beats, bars), WID_BEATS);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void openConfigWindow()
+{
+       g_ui.openSubWindow(*g_ui.mainWindow.get(), new v::gdConfig(400, 370, g_engine.conf.data), WID_CONFIG);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void openMasterMidiInputWindow()
+{
+       g_ui.openSubWindow(*g_ui.mainWindow.get(), new v::gdMidiInputMaster(g_engine.conf.data), WID_MIDI_INPUT);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void openChannelMidiInputWindow(ID channelId)
+{
+       g_ui.openSubWindow(*g_ui.mainWindow.get(), new v::gdMidiInputChannel(channelId, g_engine.conf.data),
+           WID_MIDI_INPUT);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void openSampleChannelMidiOutputWindow(ID channelId)
+{
+       g_ui.openSubWindow(*g_ui.mainWindow.get(), new v::gdMidiOutputSampleCh(channelId),
+           WID_MIDI_OUTPUT);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void openMidiChannelMidiOutputWindow(ID channelId)
+{
+       g_ui.openSubWindow(*g_ui.mainWindow.get(), new v::gdMidiOutputMidiCh(channelId), WID_MIDI_OUTPUT);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void openSampleActionEditor(ID channelId)
+{
+       g_ui.openSubWindow(*g_ui.mainWindow.get(),
+           new v::gdSampleActionEditor(channelId, g_engine.conf.data, g_engine.sequencer.getFramesInBeat()),
+           WID_ACTION_EDITOR);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void openMidiActionEditor(ID channelId)
+{
+       g_ui.openSubWindow(*g_ui.mainWindow.get(),
+           new v::gdMidiActionEditor(channelId, g_engine.conf.data, g_engine.sequencer.getFramesInBeat()),
+           WID_ACTION_EDITOR);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void openSampleEditor(ID channelId)
+{
+       g_ui.openSubWindow(*g_ui.mainWindow.get(), new v::gdSampleEditor(channelId, g_engine.conf.data),
+           WID_SAMPLE_EDITOR);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void openRenameChannelWindow(const c::channel::Data& data)
+{
+       g_ui.openSubWindow(*g_ui.mainWindow.get(), new v::gdChannelNameInput(data),
+           WID_SAMPLE_NAME);
+}
+
+/* -------------------------------------------------------------------------- */
+
+#ifdef WITH_VST
+
+void openBrowserForPlugins(v::gdWindow& parent)
+{
+       v::gdBrowserDir* browser = new v::gdBrowserDir("Add plug-ins directory",
+           g_engine.conf.data.patchPath, c::config::setPluginPathCb, g_engine.conf.data);
+       parent.addSubWindow(browser);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void openChannelPluginListWindow(ID channelId)
+{
+       g_ui.openSubWindow(*g_ui.mainWindow.get(), new v::gdPluginList(channelId, g_engine.conf.data),
+           WID_FX_LIST);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void openMasterInPluginListWindow()
+{
+       g_ui.openSubWindow(*g_ui.mainWindow.get(), new v::gdPluginList(m::Mixer::MASTER_IN_CHANNEL_ID, g_engine.conf.data),
+           WID_FX_LIST);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void openMasterOutPluginListWindow()
+{
+       g_ui.openSubWindow(*g_ui.mainWindow.get(), new v::gdPluginList(m::Mixer::MASTER_OUT_CHANNEL_ID, g_engine.conf.data),
+           WID_FX_LIST);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void openPluginChooser(ID channelId)
+{
+       g_ui.openSubWindow(*g_ui.mainWindow.get(),
+           new v::gdPluginChooser(g_engine.conf.data.pluginChooserX,
+               g_engine.conf.data.pluginChooserY, g_engine.conf.data.pluginChooserW,
+               g_engine.conf.data.pluginChooserH, channelId, g_engine.conf.data),
+           WID_FX_CHOOSER);
+}
+
+#endif
+} // namespace giada::c::layout
diff --git a/src/glue/layout.h b/src/glue/layout.h
new file mode 100644 (file)
index 0000000..a286f94
--- /dev/null
@@ -0,0 +1,71 @@
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+#ifndef G_GLUE_LAYOUT_H
+#define G_GLUE_LAYOUT_H
+
+#include "core/types.h"
+#include <string>
+
+namespace giada::v
+{
+class gdWindow;
+}
+
+namespace giada::c::channel
+{
+struct Data;
+}
+
+namespace giada::c::layout
+{
+void openBrowserForProjectLoad();
+void openBrowserForProjectSave();
+void openBrowserForSampleLoad(ID channelId);
+void openBrowserForSampleSave(ID channelId);
+void openAboutWindow();
+void openKeyGrabberWindow(const c::channel::Data&);
+void openBpmWindow(std::string bpmValue);
+void openBeatsWindow(int beats, int bars);
+void openConfigWindow();
+void openMasterMidiInputWindow();
+void openChannelMidiInputWindow(ID channelId);
+void openSampleChannelMidiOutputWindow(ID channelId);
+void openMidiChannelMidiOutputWindow(ID channelId);
+void openSampleActionEditor(ID channelId);
+void openMidiActionEditor(ID channelId);
+void openSampleEditor(ID channelId);
+void openRenameChannelWindow(const c::channel::Data&);
+#ifdef WITH_VST
+void openBrowserForPlugins(v::gdWindow& parent);
+void openChannelPluginListWindow(ID channelId);
+void openMasterInPluginListWindow();
+void openMasterOutPluginListWindow();
+void openPluginChooser(ID channelId);
+#endif
+} // namespace giada::c::layout
+
+#endif
index 761e94c0784154b98b9dabd30e6e195ce79400c8..3b6874775da9700fd3dd1ee5363edb28159a74dd 100644 (file)
  *
  * -------------------------------------------------------------------------- */
 
-#include "main.h"
-#include "core/clock.h"
+#include "glue/main.h"
 #include "core/conf.h"
 #include "core/const.h"
+#include "core/engine.h"
 #include "core/init.h"
 #include "core/kernelAudio.h"
 #include "core/kernelMidi.h"
 #include "core/model/model.h"
 #include "core/plugins/pluginHost.h"
 #include "core/plugins/pluginManager.h"
-#include "core/recManager.h"
 #include "core/recorder.h"
-#include "core/recorderHandler.h"
+#include "core/sequencer.h"
+#include "core/synchronizer.h"
 #include "gui/dialogs/mainWindow.h"
 #include "gui/dialogs/warnings.h"
 #include "gui/elems/mainWindow/keyboard/keyboard.h"
 #include "gui/elems/mainWindow/keyboard/sampleChannel.h"
 #include "gui/elems/mainWindow/mainIO.h"
 #include "gui/elems/mainWindow/mainTimer.h"
+#include "gui/ui.h"
+#include "src/core/actions/actionRecorder.h"
+#include "src/core/actions/actions.h"
 #include "utils/gui.h"
 #include "utils/log.h"
 #include "utils/string.h"
 #include <cassert>
 #include <cmath>
 
-extern giada::v::gdMainWindow* G_MainWin;
+extern giada::v::Ui     g_ui;
+extern giada::m::Engine g_engine;
 
 namespace giada::c::main
 {
-Timer::Timer(const m::model::Clock& c)
+Timer::Timer(const m::model::Sequencer& c)
 : bpm(c.bpm)
 , beats(c.beats)
 , bars(c.bars)
 , quantize(c.quantize)
-, isUsingJack(m::kernelAudio::getAPI() == G_SYS_API_JACK)
-, isRecordingInput(m::recManager::isRecordingInput())
+, isUsingJack(g_engine.kernelAudio.getAPI() == G_SYS_API_JACK)
+, isRecordingInput(g_engine.recorder.isRecordingInput())
 {
 }
 
@@ -70,7 +74,7 @@ Timer::Timer(const m::model::Clock& c)
 /* -------------------------------------------------------------------------- */
 /* -------------------------------------------------------------------------- */
 
-IO::IO(const m::channel::Data& out, const m::channel::Data& in, const m::model::Mixer& m)
+IO::IO(const m::Channel& out, const m::Channel& in, const m::model::Mixer& m)
 : masterOutVol(out.volume)
 , masterInVol(in.volume)
 #ifdef WITH_VST
@@ -83,14 +87,21 @@ IO::IO(const m::channel::Data& out, const m::channel::Data& in, const m::model::
 
 /* -------------------------------------------------------------------------- */
 
-float IO::getMasterOutPeak()
+Peak IO::getMasterOutPeak()
 {
-       return m::mixer::getPeakOut();
+       return g_engine.mixer.getPeakOut();
 }
 
-float IO::getMasterInPeak()
+Peak IO::getMasterInPeak()
 {
-       return m::mixer::getPeakIn();
+       return g_engine.mixer.getPeakIn();
+}
+
+/* -------------------------------------------------------------------------- */
+
+bool IO::isKernelReady()
+{
+       return g_engine.kernelAudio.isReady();
 }
 
 /* -------------------------------------------------------------------------- */
@@ -99,16 +110,16 @@ float IO::getMasterInPeak()
 
 Timer getTimer()
 {
-       return Timer(m::model::get().clock);
+       return Timer(g_engine.model.get().sequencer);
 }
 
 /* -------------------------------------------------------------------------- */
 
 IO getIO()
 {
-       return IO(m::model::get().getChannel(m::mixer::MASTER_OUT_CHANNEL_ID),
-           m::model::get().getChannel(m::mixer::MASTER_IN_CHANNEL_ID),
-           m::model::get().mixer);
+       return IO(g_engine.model.get().getChannel(m::Mixer::MASTER_OUT_CHANNEL_ID),
+           g_engine.model.get().getChannel(m::Mixer::MASTER_IN_CHANNEL_ID),
+           g_engine.model.get().mixer);
 }
 
 /* -------------------------------------------------------------------------- */
@@ -117,13 +128,13 @@ Sequencer getSequencer()
 {
        Sequencer out;
 
-       m::mixer::RecordInfo recInfo = m::mixer::getRecordInfo();
+       m::Mixer::RecordInfo recInfo = g_engine.mixer.getRecordInfo();
 
-       out.isFreeModeInputRec = m::recManager::isRecordingInput() && m::conf::conf.inputRecMode == InputRecMode::FREE;
-       out.shouldBlink        = u::gui::shouldBlink() && (m::clock::getStatus() == ClockStatus::WAITING || out.isFreeModeInputRec);
-       out.beats              = m::clock::getBeats();
-       out.bars               = m::clock::getBars();
-       out.currentBeat        = m::clock::getCurrentBeat();
+       out.isFreeModeInputRec = g_engine.recorder.isRecordingInput() && g_engine.conf.data.inputRecMode == InputRecMode::FREE;
+       out.shouldBlink        = g_ui.shouldBlink() && (g_engine.sequencer.getStatus() == SeqStatus::WAITING || out.isFreeModeInputRec);
+       out.beats              = g_engine.sequencer.getBeats();
+       out.bars               = g_engine.sequencer.getBars();
+       out.currentBeat        = g_engine.sequencer.getCurrentBeat();
        out.recPosition        = recInfo.position;
        out.recMaxLength       = recInfo.maxLength;
 
@@ -132,14 +143,38 @@ Sequencer getSequencer()
 
 /* -------------------------------------------------------------------------- */
 
+Transport getTransport()
+{
+       Transport transport;
+       transport.isRunning         = g_engine.sequencer.isRunning();
+       transport.isRecordingAction = g_engine.recorder.isRecordingAction();
+       transport.isRecordingInput  = g_engine.recorder.isRecordingInput();
+       transport.isMetronomeOn     = g_engine.sequencer.isMetronomeOn();
+       transport.recTriggerMode    = g_engine.conf.data.recTriggerMode;
+       transport.inputRecMode      = g_engine.conf.data.inputRecMode;
+       return transport;
+}
+
+/* -------------------------------------------------------------------------- */
+
+MainMenu getMainMenu()
+{
+       MainMenu mainMenu;
+       mainMenu.hasAudioData = g_engine.mixerHandler.hasAudioData();
+       mainMenu.hasActions   = g_engine.mixerHandler.hasActions();
+       return mainMenu;
+}
+
+/* -------------------------------------------------------------------------- */
+
 void setBpm(const char* i, const char* f)
 {
        /* Never change this stuff while recording audio. */
 
-       if (m::recManager::isRecordingInput())
+       if (g_engine.recorder.isRecordingInput())
                return;
 
-       m::clock::setBpm(std::atof(i) + (std::atof(f) / 10.0f));
+       g_engine.sequencer.setBpm(std::atof(i) + (std::atof(f) / 10.0f), g_engine.kernelAudio.getSampleRate());
 }
 
 /* -------------------------------------------------------------------------- */
@@ -148,10 +183,10 @@ void setBpm(float f)
 {
        /* Never change this stuff while recording audio. */
 
-       if (m::recManager::isRecordingInput())
+       if (g_engine.recorder.isRecordingInput())
                return;
 
-       m::clock::setBpm(f);
+       g_engine.sequencer.setBpm(f, g_engine.kernelAudio.getSampleRate());
 }
 
 /* -------------------------------------------------------------------------- */
@@ -160,18 +195,18 @@ void setBeats(int beats, int bars)
 {
        /* Never change this stuff while recording audio. */
 
-       if (m::recManager::isRecordingInput())
+       if (g_engine.recorder.isRecordingInput())
                return;
 
-       m::clock::setBeats(beats, bars);
-       m::mixer::allocRecBuffer(m::clock::getMaxFramesInLoop());
+       g_engine.sequencer.setBeats(beats, bars, g_engine.kernelAudio.getSampleRate());
+       g_engine.mixer.allocRecBuffer(g_engine.sequencer.getMaxFramesInLoop(g_engine.kernelAudio.getSampleRate()));
 }
 
 /* -------------------------------------------------------------------------- */
 
 void quantize(int val)
 {
-       m::clock::setQuantize(val);
+       g_engine.sequencer.setQuantize(val, g_engine.kernelAudio.getSampleRate());
 }
 
 /* -------------------------------------------------------------------------- */
@@ -180,10 +215,11 @@ void clearAllSamples()
 {
        if (!v::gdConfirmWin("Warning", "Free all Sample channels: are you sure?"))
                return;
-       G_MainWin->delSubWindow(WID_SAMPLE_EDITOR);
-       m::clock::setStatus(ClockStatus::STOPPED);
-       m::mh::freeAllChannels();
-       m::recorderHandler::clearAllActions();
+       g_ui.closeSubWindow(WID_SAMPLE_EDITOR);
+       g_engine.sequencer.setStatus(SeqStatus::STOPPED);
+       g_engine.synchronizer.sendMIDIstop();
+       g_engine.mixerHandler.freeAllChannels();
+       g_engine.actionRecorder.clearAllActions();
 }
 
 /* -------------------------------------------------------------------------- */
@@ -192,48 +228,66 @@ void clearAllActions()
 {
        if (!v::gdConfirmWin("Warning", "Clear all actions: are you sure?"))
                return;
-       G_MainWin->delSubWindow(WID_ACTION_EDITOR);
-       m::recorderHandler::clearAllActions();
+       g_ui.closeSubWindow(WID_ACTION_EDITOR);
+       g_engine.actionRecorder.clearAllActions();
 }
 
 /* -------------------------------------------------------------------------- */
 
 void setInToOut(bool v)
 {
-       m::mh::setInToOut(v);
+       g_engine.mixerHandler.setInToOut(v);
 }
 
 /* -------------------------------------------------------------------------- */
 
 void toggleRecOnSignal()
 {
-       if (!m::recManager::canEnableRecOnSignal())
-       {
-               m::conf::conf.recTriggerMode = RecTriggerMode::NORMAL;
-               return;
-       }
-       m::conf::conf.recTriggerMode = m::conf::conf.recTriggerMode == RecTriggerMode::NORMAL ? RecTriggerMode::SIGNAL : RecTriggerMode::NORMAL;
+       if (!g_engine.recorder.canEnableRecOnSignal())
+               g_engine.conf.data.recTriggerMode = RecTriggerMode::NORMAL;
+       else
+               g_engine.conf.data.recTriggerMode = g_engine.conf.data.recTriggerMode == RecTriggerMode::NORMAL ? RecTriggerMode::SIGNAL : RecTriggerMode::NORMAL;
+       g_engine.updateMixerModel();
 }
 
 /* -------------------------------------------------------------------------- */
 
 void toggleFreeInputRec()
 {
-       if (!m::recManager::canEnableFreeInputRec())
-       {
-               m::conf::conf.inputRecMode = InputRecMode::RIGID;
-               return;
-       }
-       m::conf::conf.inputRecMode = m::conf::conf.inputRecMode == InputRecMode::FREE ? InputRecMode::RIGID : InputRecMode::FREE;
+       if (!g_engine.recorder.canEnableFreeInputRec())
+               g_engine.conf.data.inputRecMode = InputRecMode::RIGID;
+       else
+               g_engine.conf.data.inputRecMode = g_engine.conf.data.inputRecMode == InputRecMode::FREE ? InputRecMode::RIGID : InputRecMode::FREE;
+       g_engine.updateMixerModel();
 }
 
 /* -------------------------------------------------------------------------- */
 
+#ifdef G_DEBUG_MODE
+
+void printDebugInfo()
+{
+       g_engine.model.debug();
+}
+
+#endif
+
+/* -------------------------------------------------------------------------- */
+
 void closeProject()
 {
        if (!v::gdConfirmWin("Warning", "Close project: are you sure?"))
                return;
-       m::init::reset();
-       m::mixer::enable();
+       g_engine.mixer.disable();
+       g_engine.reset();
+       g_ui.reset();
+       g_engine.mixer.enable();
+}
+
+/* -------------------------------------------------------------------------- */
+
+void quitGiada()
+{
+       m::init::closeMainWindow();
 }
 } // namespace giada::c::main
index 098fbfc7db3fb4ee24489e79878fa758cf9eb976..8757c5011ae4344f9a5ccb5afa33160a922e9f8f 100644 (file)
 
 #include "core/types.h"
 
-namespace giada::m::channel
+namespace giada::m
 {
-struct Data;
+class Channel;
 }
+
 namespace giada::m::model
 {
-struct Clock;
-struct Clock;
-struct Mixer;
-struct Mixer;
+class Sequencer;
+class Mixer;
 } // namespace giada::m::model
+
 namespace giada::c::main
 {
 struct Timer
 {
        Timer() = default;
-       Timer(const m::model::Clock& c);
+       Timer(const m::model::Sequencer& c);
 
        float bpm;
        int   beats;
@@ -58,7 +58,7 @@ struct Timer
 struct IO
 {
        IO() = default;
-       IO(const m::channel::Data& out, const m::channel::Data& in, const m::model::Mixer& m);
+       IO(const m::Channel& out, const m::Channel& in, const m::model::Mixer& m);
 
        float masterOutVol;
        float masterInVol;
@@ -68,8 +68,9 @@ struct IO
 #endif
        bool inToOut;
 
-       float getMasterOutPeak();
-       float getMasterInPeak();
+       Peak getMasterOutPeak();
+       Peak getMasterInPeak();
+       bool isKernelReady();
 };
 
 struct Sequencer
@@ -83,12 +84,30 @@ struct Sequencer
        Frame recMaxLength;
 };
 
+struct Transport
+{
+       bool           isRunning;
+       bool           isRecordingAction;
+       bool           isRecordingInput;
+       bool           isMetronomeOn;
+       RecTriggerMode recTriggerMode;
+       InputRecMode   inputRecMode;
+};
+
+struct MainMenu
+{
+       bool hasAudioData;
+       bool hasActions;
+};
+
 /* get*
 Returns viewModel objects filled with data. */
 
 Timer     getTimer();
 IO        getIO();
 Sequencer getSequencer();
+Transport getTransport();
+MainMenu  getMainMenu();
 
 /* setBpm (1)
 Sets bpm value from string to float. */
@@ -112,11 +131,16 @@ void setInToOut(bool v);
 
 void toggleRecOnSignal();
 void toggleFreeInputRec();
+#ifdef G_DEBUG_MODE
+void printDebugInfo();
+#endif
 
 /* closeProject
 Resets Giada to init state. If resetGui also refresh all widgets. */
 
 void closeProject();
+
+void quitGiada();
 } // namespace giada::c::main
 
 #endif
index 7cc9934c73a053a2024993c6f023793c6a92912c..a7010e270e59a338eb10e8f334d02a2d0c82d1a7 100644 (file)
 #include "core/plugins/plugin.h"
 #include "core/conf.h"
 #include "core/const.h"
+#include "core/engine.h"
+#include "core/kernelAudio.h"
 #include "core/mixer.h"
 #include "core/model/model.h"
 #include "core/plugins/pluginHost.h"
 #include "core/plugins/pluginManager.h"
-#include "gui/dialogs/browser/browserDir.h"
 #include "gui/dialogs/config.h"
 #include "gui/dialogs/mainWindow.h"
 #include "gui/dialogs/pluginList.h"
 #include "gui/dialogs/pluginWindow.h"
-#include "gui/dialogs/warnings.h"
+#include "gui/ui.h"
 #include "plugin.h"
 #include "utils/gui.h"
 #include <FL/Fl.H>
 #include <cassert>
 
-extern giada::v::gdMainWindow* G_MainWin;
+extern giada::v::Ui     g_ui;
+extern giada::m::Engine g_engine;
 
 namespace giada::c::plugin
 {
@@ -102,7 +104,7 @@ void Plugin::setResizeCallback(std::function<void(int, int)> f)
 /* -------------------------------------------------------------------------- */
 /* -------------------------------------------------------------------------- */
 
-Plugins::Plugins(const m::channel::Data& c)
+Plugins::Plugins(const m::Channel& c)
 : channelId(c.id)
 , plugins(c.plugins)
 {
@@ -114,7 +116,7 @@ Plugins::Plugins(const m::channel::Data& c)
 
 Plugins getPlugins(ID channelId)
 {
-       return Plugins(m::model::get().getChannel(channelId));
+       return Plugins(g_engine.model.get().getChannel(channelId));
 }
 
 Plugin getPlugin(m::Plugin& plugin, ID channelId)
@@ -127,11 +129,16 @@ Param getParam(int index, const m::Plugin& plugin, ID channelId)
        return Param(plugin, index, channelId);
 }
 
+std::vector<m::PluginManager::PluginInfo> getPluginsInfo()
+{
+       return g_engine.pluginManager.getPluginsInfo();
+}
+
 /* -------------------------------------------------------------------------- */
 
 void updateWindow(ID pluginId, bool gui)
 {
-       m::Plugin* p = m::model::find<m::Plugin>(pluginId);
+       m::Plugin* p = g_engine.model.findShared<m::Plugin>(pluginId);
 
        assert(p != nullptr);
 
@@ -141,10 +148,10 @@ void updateWindow(ID pluginId, bool gui)
        /* Get the parent window first: the plug-in list. Then, if it exists, get
     the child window - the actual pluginWindow. */
 
-       v::gdPluginList* parent = static_cast<v::gdPluginList*>(u::gui::getSubwindow(G_MainWin, WID_FX_LIST));
+       v::gdPluginList* parent = static_cast<v::gdPluginList*>(g_ui.getSubwindow(*g_ui.mainWindow.get(), WID_FX_LIST));
        if (parent == nullptr)
                return;
-       v::gdPluginWindow* child = static_cast<v::gdPluginWindow*>(u::gui::getSubwindow(parent, pluginId + 1));
+       v::gdPluginWindow* child = static_cast<v::gdPluginWindow*>(g_ui.getSubwindow(*parent, pluginId + 1));
        if (child == nullptr)
                return;
 
@@ -159,32 +166,50 @@ void updateWindow(ID pluginId, bool gui)
 
 void addPlugin(int pluginListIndex, ID channelId)
 {
-       if (pluginListIndex >= m::pluginManager::countAvailablePlugins())
+       if (pluginListIndex >= g_engine.pluginManager.countAvailablePlugins())
                return;
-       std::unique_ptr<m::Plugin> p = m::pluginManager::makePlugin(pluginListIndex);
-       if (p != nullptr)
-               m::pluginHost::addPlugin(std::move(p), channelId);
+       std::unique_ptr<m::Plugin> plugin    = g_engine.pluginManager.makePlugin(pluginListIndex, g_engine.kernelAudio.getSampleRate(), g_engine.kernelAudio.getBufferSize(), g_engine.sequencer);
+       const m::Plugin*           pluginPtr = plugin.get();
+       if (plugin != nullptr)
+               g_engine.pluginHost.addPlugin(std::move(plugin));
+
+       /* TODO - unfortunately JUCE wants mutable plugin objects due to the
+       presence of the non-const processBlock() method. Why not const_casting
+       only in the Plugin class? */
+       g_engine.model.get().getChannel(channelId).plugins.push_back(const_cast<m::Plugin*>(pluginPtr));
+       g_engine.model.swap(m::model::SwapType::HARD);
 }
 
 /* -------------------------------------------------------------------------- */
 
 void swapPlugins(const m::Plugin& p1, const m::Plugin& p2, ID channelId)
 {
-       m::pluginHost::swapPlugin(p1, p2, channelId);
+       g_engine.pluginHost.swapPlugin(p1, p2, g_engine.model.get().getChannel(channelId).plugins);
+       g_engine.model.swap(m::model::SwapType::HARD);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void sortPlugins(m::PluginManager::SortMethod method)
+{
+       g_engine.pluginManager.sortPlugins(method);
 }
 
 /* -------------------------------------------------------------------------- */
 
 void freePlugin(const m::Plugin& plugin, ID channelId)
 {
-       m::pluginHost::freePlugin(plugin, channelId);
+       u::vector::remove(g_engine.model.get().getChannel(channelId).plugins, &plugin);
+       g_engine.model.swap(m::model::SwapType::HARD);
+
+       g_engine.pluginHost.freePlugin(plugin);
 }
 
 /* -------------------------------------------------------------------------- */
 
 void setProgram(ID pluginId, int programIndex)
 {
-       m::pluginHost::setPluginProgram(pluginId, programIndex);
+       g_engine.pluginHost.setPluginProgram(pluginId, programIndex);
        updateWindow(pluginId, /*gui=*/true);
 }
 
@@ -192,29 +217,19 @@ void setProgram(ID pluginId, int programIndex)
 
 void toggleBypass(ID pluginId)
 {
-       m::pluginHost::toggleBypass(pluginId);
+       g_engine.pluginHost.toggleBypass(pluginId);
 }
 
 /* -------------------------------------------------------------------------- */
 
-void setPluginPathCb(void* data)
+void startDispatchLoop()
 {
-       v::gdBrowserDir* browser = (v::gdBrowserDir*)data;
-
-       if (browser->getCurrentPath() == "")
-       {
-               v::gdAlert("Invalid path.");
-               return;
-       }
-
-       if (!m::conf::conf.pluginPath.empty() && m::conf::conf.pluginPath.back() != ';')
-               m::conf::conf.pluginPath += ";";
-       m::conf::conf.pluginPath += browser->getCurrentPath();
-
-       browser->do_callback();
+       g_ui.startJuceDispatchLoop();
+}
 
-       v::gdConfig* configWin = static_cast<v::gdConfig*>(u::gui::getSubwindow(G_MainWin, WID_CONFIG));
-       configWin->refreshVstPath();
+void stopDispatchLoop()
+{
+       g_ui.stopJuceDispatchLoop();
 }
 } // namespace giada::c::plugin
 
index f091937ae693a2cd840db619718269a7cc59c51f..dca83f4c8e42f732f682f259fd88629853aa03da 100644 (file)
@@ -30,6 +30,7 @@
 #ifdef WITH_VST
 
 #include "core/plugins/pluginHost.h"
+#include "core/plugins/pluginManager.h"
 #include "core/types.h"
 #include <string>
 #include <vector>
@@ -38,14 +39,13 @@ namespace juce
 {
 class AudioProcessorEditor;
 }
+
 namespace giada::m
 {
 class Plugin;
-}
-namespace giada::m::channel
-{
-struct Data;
-}
+class Channel;
+} // namespace giada::m
+
 namespace giada::c::plugin
 {
 struct Program
@@ -89,14 +89,14 @@ struct Plugin
        std::vector<Program> programs;
        std::vector<int>     paramIndexes;
 
-  private:
+private:
        m::Plugin& m_plugin;
 };
 
 struct Plugins
 {
        Plugins() = default;
-       Plugins(const m::channel::Data&);
+       Plugins(const m::Channel&);
 
        ID                      channelId;
        std::vector<m::Plugin*> plugins;
@@ -109,6 +109,8 @@ Plugins getPlugins(ID channelId);
 Plugin  getPlugin(m::Plugin& plugin, ID channelId);
 Param   getParam(int index, const m::Plugin& plugin, ID channelId);
 
+std::vector<m::PluginManager::PluginInfo> getPluginsInfo();
+
 /* updateWindow
 Updates the editor-less plug-in window. This is useless if the plug-in has an
 editor. */
@@ -117,15 +119,12 @@ void updateWindow(ID pluginId, bool gui);
 
 void addPlugin(int pluginListIndex, ID channelId);
 void swapPlugins(const m::Plugin& p1, const m::Plugin& p2, ID channelId);
+void sortPlugins(m::PluginManager::SortMethod);
 void freePlugin(const m::Plugin& plugin, ID channelId);
 void setProgram(ID pluginId, int programIndex);
 void toggleBypass(ID pluginId);
-
-/* setPluginPathCb
-Callback attached to the DirBrowser for adding new Plug-in search paths in the
-configuration window. */
-
-void setPluginPathCb(void* data);
+void startDispatchLoop();
+void stopDispatchLoop();
 } // namespace giada::c::plugin
 
 #endif
index 03e38ea4f3777067d5ae6c8b774219a2aa9d4621..1c2d22a4c043925f89390c58a0a0ca6e13be8540 100644 (file)
  *
  * -------------------------------------------------------------------------- */
 
-#include "core/recorder.h"
-#include "core/action.h"
+#include "glue/recorder.h"
+#include "core/actions/actionRecorder.h"
 #include "core/channels/channel.h"
-#include "core/clock.h"
 #include "core/const.h"
+#include "core/engine.h"
 #include "core/kernelMidi.h"
 #include "core/mixer.h"
 #include "core/model/model.h"
-#include "core/recorderHandler.h"
 #include "gui/dialogs/warnings.h"
 #include "gui/elems/mainWindow/keyboard/channel.h"
 #include "gui/elems/mainWindow/keyboard/sampleChannel.h"
-#include "recorder.h"
+#include "gui/ui.h"
+#include "src/core/actions/action.h"
+#include "src/core/actions/actionRecorder.h"
+#include "src/core/actions/actions.h"
 #include "utils/gui.h"
 #include "utils/log.h"
 #include <cassert>
 
+extern giada::m::Engine g_engine;
+extern giada::v::Ui     g_ui;
+
 namespace giada::c::recorder
 {
 void clearAllActions(ID channelId)
 {
        if (!v::gdConfirmWin("Warning", "Clear all actions: are you sure?"))
                return;
-       m::recorder::clearChannel(channelId);
+       g_engine.actionRecorder.clearChannel(channelId);
        updateChannel(channelId, /*updateActionEditor=*/true);
 }
 
@@ -57,7 +62,7 @@ void clearVolumeActions(ID channelId)
 {
        if (!v::gdConfirmWin("Warning", "Clear all volume actions: are you sure?"))
                return;
-       m::recorder::clearActions(channelId, m::MidiEvent::ENVELOPE);
+       g_engine.actionRecorder.clearActions(channelId, m::MidiEvent::ENVELOPE);
        updateChannel(channelId, /*updateActionEditor=*/true);
 }
 
@@ -67,9 +72,9 @@ void clearStartStopActions(ID channelId)
 {
        if (!v::gdConfirmWin("Warning", "Clear all start/stop actions: are you sure?"))
                return;
-       m::recorder::clearActions(channelId, m::MidiEvent::NOTE_ON);
-       m::recorder::clearActions(channelId, m::MidiEvent::NOTE_OFF);
-       m::recorder::clearActions(channelId, m::MidiEvent::NOTE_KILL);
+       g_engine.actionRecorder.clearActions(channelId, m::MidiEvent::NOTE_ON);
+       g_engine.actionRecorder.clearActions(channelId, m::MidiEvent::NOTE_OFF);
+       g_engine.actionRecorder.clearActions(channelId, m::MidiEvent::NOTE_KILL);
        updateChannel(channelId, /*updateActionEditor=*/true);
 }
 
@@ -78,10 +83,10 @@ void clearStartStopActions(ID channelId)
 void updateChannel(ID channelId, bool updateActionEditor)
 {
        /* TODO - move somewhere else in the core area */
-       m::model::get().getChannel(channelId).hasActions = m::recorder::hasActions(channelId);
-       m::model::swap(m::model::SwapType::HARD);
+       g_engine.model.get().getChannel(channelId).hasActions = g_engine.actionRecorder.hasActions(channelId);
+       g_engine.model.swap(m::model::SwapType::HARD);
 
        if (updateActionEditor)
-               u::gui::refreshActionEditor();
+               g_ui.refreshSubWindow(WID_ACTION_EDITOR);
 }
 } // namespace giada::c::recorder
index cb5bb74c8acb642c61be56e3494e570cafe3f4d8..6d784328ce8b1e1462a8b700b021e4ddb1232426 100644 (file)
@@ -27,6 +27,8 @@
 #ifndef G_GLUE_RECORDER_H
 #define G_GLUE_RECORDER_H
 
+#include "core/types.h"
+
 namespace giada::c::recorder
 {
 void clearAllActions(ID channelId);
index 5f5f4ea3da1b75eb25d946bea5c75873289b1a96..11a1e99203ee75522d1ec10d46c70b792599f292 100644 (file)
 #include "gui/dialogs/sampleEditor.h"
 #include "channel.h"
 #include "core/const.h"
+#include "core/engine.h"
+#include "core/kernelAudio.h"
 #include "core/mixerHandler.h"
 #include "core/model/model.h"
+#include "core/sequencer.h"
 #include "core/wave.h"
 #include "core/waveManager.h"
 #include "glue/events.h"
 #include "gui/elems/sampleEditor/volumeTool.h"
 #include "gui/elems/sampleEditor/waveTools.h"
 #include "gui/elems/sampleEditor/waveform.h"
+#include "gui/ui.h"
 #include "sampleEditor.h"
 #include "utils/gui.h"
 #include "utils/log.h"
 #include <FL/Fl.H>
 #include <cassert>
 
-extern giada::v::gdMainWindow* G_MainWin;
+extern giada::v::Ui     g_ui;
+extern giada::m::Engine g_engine;
 
 namespace giada::c::sampleEditor
 {
 namespace
 {
-m::channel::Data& getChannel_(ID channelId)
+m::Channel& getChannel_(ID channelId)
 {
-       return m::model::get().getChannel(channelId);
+       return g_engine.model.get().getChannel(channelId);
 }
 
-m::samplePlayer::Data& getSamplePlayer_(ID channelId)
+m::SamplePlayer& getSamplePlayer_(ID channelId)
 {
        return getChannel_(channelId).samplePlayer.value();
 }
@@ -98,7 +103,7 @@ void resetBeginEnd_(ID channelId)
 /* -------------------------------------------------------------------------- */
 /* -------------------------------------------------------------------------- */
 
-Data::Data(const m::channel::Data& c)
+Data::Data(const m::Channel& c)
 : channelId(c.id)
 , name(c.name)
 , volume(c.volume)
@@ -119,12 +124,12 @@ Data::Data(const m::channel::Data& c)
 
 ChannelStatus Data::a_getPreviewStatus() const
 {
-       return getChannel_(m::mixer::PREVIEW_CHANNEL_ID).state->playStatus.load();
+       return getChannel_(m::Mixer::PREVIEW_CHANNEL_ID).shared->playStatus.load();
 }
 
 Frame Data::a_getPreviewTracker() const
 {
-       return getChannel_(m::mixer::PREVIEW_CHANNEL_ID).state->tracker.load();
+       return getChannel_(m::Mixer::PREVIEW_CHANNEL_ID).shared->tracker.load();
 }
 
 const m::Wave& Data::getWaveRef() const
@@ -132,6 +137,16 @@ const m::Wave& Data::getWaveRef() const
        return *m_channel->samplePlayer->getWave();
 }
 
+Frame Data::getFramesInBar() const
+{
+       return g_engine.sequencer.getFramesInBar();
+}
+
+Frame Data::getFramesInLoop() const
+{
+       return g_engine.sequencer.getFramesInLoop();
+}
+
 /* -------------------------------------------------------------------------- */
 /* -------------------------------------------------------------------------- */
 /* -------------------------------------------------------------------------- */
@@ -139,8 +154,9 @@ const m::Wave& Data::getWaveRef() const
 Data getData(ID channelId)
 {
        /* Prepare the preview channel first, then return Data object. */
-       m::samplePlayer::loadWave(getChannel_(m::mixer::PREVIEW_CHANNEL_ID), &getWave_(channelId));
-       m::model::swap(m::model::SwapType::SOFT);
+       m::Channel& previewChannel = getChannel_(m::Mixer::PREVIEW_CHANNEL_ID);
+       previewChannel.samplePlayer->loadWave(previewChannel, &getWave_(channelId));
+       g_engine.model.swap(m::model::SwapType::SOFT);
 
        return Data(getChannel_(channelId));
 }
@@ -149,7 +165,7 @@ Data getData(ID channelId)
 
 void onRefresh(bool gui, std::function<void(v::gdSampleEditor&)> f)
 {
-       v::gdSampleEditor* se = static_cast<v::gdSampleEditor*>(u::gui::getSubwindow(G_MainWin, WID_SAMPLE_EDITOR));
+       v::gdSampleEditor* se = static_cast<v::gdSampleEditor*>(g_ui.getSubwindow(*g_ui.mainWindow.get(), WID_SAMPLE_EDITOR));
        if (se == nullptr)
                return;
        if (!gui)
@@ -161,7 +177,7 @@ void onRefresh(bool gui, std::function<void(v::gdSampleEditor&)> f)
 
 v::gdSampleEditor* getSampleEditorWindow()
 {
-       v::gdSampleEditor* se = static_cast<v::gdSampleEditor*>(u::gui::getSubwindow(G_MainWin, WID_SAMPLE_EDITOR));
+       v::gdSampleEditor* se = static_cast<v::gdSampleEditor*>(g_ui.getSubwindow(*g_ui.mainWindow.get(), WID_SAMPLE_EDITOR));
        assert(se != nullptr);
        return se;
 }
@@ -170,7 +186,7 @@ v::gdSampleEditor* getSampleEditorWindow()
 
 void setBeginEnd(ID channelId, Frame b, Frame e)
 {
-       m::channel::Data& c = getChannel_(channelId);
+       m::Channel& c = getChannel_(channelId);
 
        b = std::clamp(b, 0, c.samplePlayer->getWaveSize() - 1);
        e = std::clamp(e, 1, c.samplePlayer->getWaveSize() - 1);
@@ -179,12 +195,12 @@ void setBeginEnd(ID channelId, Frame b, Frame e)
        else if (e < b)
                e = b + 1;
 
-       if (c.state->tracker.load() < b)
-               c.state->tracker.store(b);
+       if (c.shared->tracker.load() < b)
+               c.shared->tracker.store(b);
 
        getSamplePlayer_(channelId).begin = b;
        getSamplePlayer_(channelId).end   = e;
-       m::model::swap(m::model::SwapType::SOFT);
+       g_engine.model.swap(m::model::SwapType::SOFT);
 
        /* TODO waveform widget is dumb and wants a rebuild. Refactoring needed! */
        getSampleEditorWindow()->rebuild();
@@ -195,7 +211,7 @@ void setBeginEnd(ID channelId, Frame b, Frame e)
 void cut(ID channelId, Frame a, Frame b)
 {
        copy(channelId, a, b);
-       m::model::DataLock lock;
+       m::model::DataLock lock = g_engine.model.lockData();
        m::wfx::cut(getWave_(channelId), a, b);
        resetBeginEnd_(channelId);
 }
@@ -204,7 +220,7 @@ void cut(ID channelId, Frame a, Frame b)
 
 void copy(ID channelId, Frame a, Frame b)
 {
-       waveBuffer_ = m::waveManager::createFromWave(getWave_(channelId), a, b);
+       waveBuffer_ = g_engine.waveManager.createFromWave(getWave_(channelId), a, b);
 }
 
 /* -------------------------------------------------------------------------- */
@@ -224,7 +240,7 @@ void paste(ID channelId, Frame a)
        /* Temporary disable wave reading in channel. From now on, the audio thread
        won't be reading any wave, so editing it is safe.  */
 
-       m::model::DataLock lock;
+       m::model::DataLock lock = g_engine.model.lockData();
 
        /* Paste copied data to destination wave. */
 
@@ -232,7 +248,7 @@ void paste(ID channelId, Frame a)
 
        /* Pass the old wave that contains the pasted data to channel. */
 
-       m::samplePlayer::setWave(getChannel_(channelId), &wave, 1.0f);
+       getChannel_(channelId).samplePlayer->setWave(&wave, 1.0f);
 
        /* In the meantime, shift begin/end points to keep the previous position. */
 
@@ -252,7 +268,7 @@ void paste(ID channelId, Frame a)
 
 void silence(ID channelId, int a, int b)
 {
-       m::model::DataLock lock;
+       m::model::DataLock lock = g_engine.model.lockData();
        m::wfx::silence(getWave_(channelId), a, b);
 }
 
@@ -260,7 +276,7 @@ void silence(ID channelId, int a, int b)
 
 void fade(ID channelId, int a, int b, m::wfx::Fade type)
 {
-       m::model::DataLock lock;
+       m::model::DataLock lock = g_engine.model.lockData();
        m::wfx::fade(getWave_(channelId), a, b, type);
 }
 
@@ -268,7 +284,7 @@ void fade(ID channelId, int a, int b, m::wfx::Fade type)
 
 void smoothEdges(ID channelId, int a, int b)
 {
-       m::model::DataLock lock;
+       m::model::DataLock lock = g_engine.model.lockData();
        m::wfx::smooth(getWave_(channelId), a, b);
 }
 
@@ -276,7 +292,7 @@ void smoothEdges(ID channelId, int a, int b)
 
 void reverse(ID channelId, Frame a, Frame b)
 {
-       m::model::DataLock lock;
+       m::model::DataLock lock = g_engine.model.lockData();
        m::wfx::reverse(getWave_(channelId), a, b);
 }
 
@@ -284,7 +300,7 @@ void reverse(ID channelId, Frame a, Frame b)
 
 void normalize(ID channelId, int a, int b)
 {
-       m::model::DataLock lock;
+       m::model::DataLock lock = g_engine.model.lockData();
        m::wfx::normalize(getWave_(channelId), a, b);
 }
 
@@ -292,7 +308,7 @@ void normalize(ID channelId, int a, int b)
 
 void trim(ID channelId, int a, int b)
 {
-       m::model::DataLock lock;
+       m::model::DataLock lock = g_engine.model.lockData();
        m::wfx::trim(getWave_(channelId), a, b);
        resetBeginEnd_(channelId);
 }
@@ -306,8 +322,8 @@ the One-shot pause mode is implemented:
 void playPreview(bool loop)
 {
        setPreviewTracker(previewTracker_);
-       channel::setSamplePlayerMode(m::mixer::PREVIEW_CHANNEL_ID, loop ? SamplePlayerMode::SINGLE_ENDLESS : SamplePlayerMode::SINGLE_BASIC);
-       events::pressChannel(m::mixer::PREVIEW_CHANNEL_ID, G_MAX_VELOCITY, Thread::MAIN);
+       channel::setSamplePlayerMode(m::Mixer::PREVIEW_CHANNEL_ID, loop ? SamplePlayerMode::SINGLE_ENDLESS : SamplePlayerMode::SINGLE_BASIC);
+       events::pressChannel(m::Mixer::PREVIEW_CHANNEL_ID, G_MAX_VELOCITY, Thread::MAIN);
 }
 
 void stopPreview()
@@ -316,15 +332,13 @@ void stopPreview()
        channel. */
        setPreviewTracker(previewTracker_);
        getSampleEditorWindow()->refresh();
-       events::killChannel(m::mixer::PREVIEW_CHANNEL_ID, Thread::MAIN);
+       events::killChannel(m::Mixer::PREVIEW_CHANNEL_ID, Thread::MAIN);
 }
 
 void setPreviewTracker(Frame f)
 {
-       namespace mm = m::model;
-
-       mm::get().getChannel(m::mixer::PREVIEW_CHANNEL_ID).state->tracker.store(f);
-       mm::swap(mm::SwapType::SOFT);
+       g_engine.model.get().getChannel(m::Mixer::PREVIEW_CHANNEL_ID).shared->tracker.store(f);
+       g_engine.model.swap(m::model::SwapType::SOFT);
 
        previewTracker_ = f;
 
@@ -333,18 +347,19 @@ void setPreviewTracker(Frame f)
 
 void cleanupPreview()
 {
-       namespace mm = m::model;
+       m::Channel& channel = getChannel_(m::Mixer::PREVIEW_CHANNEL_ID);
 
-       m::samplePlayer::loadWave(mm::get().getChannel(m::mixer::PREVIEW_CHANNEL_ID), nullptr);
-       mm::swap(mm::SwapType::SOFT);
+       channel.samplePlayer->loadWave(channel, nullptr);
+       g_engine.model.swap(m::model::SwapType::SOFT);
 }
 
 /* -------------------------------------------------------------------------- */
 
 void toNewChannel(ID channelId, Frame a, Frame b)
 {
-       ID columnId = G_MainWin->keyboard->getChannel(channelId)->getColumnId();
-       m::mh::addAndLoadChannel(columnId, m::waveManager::createFromWave(getWave_(channelId), a, b));
+       ID columnId = g_ui.mainWindow->keyboard->getChannel(channelId)->getColumnId();
+       g_engine.mixerHandler.addAndLoadChannel(columnId, g_engine.waveManager.createFromWave(getWave_(channelId), a, b),
+           g_engine.kernelAudio.getBufferSize(), g_engine.channelManager);
 }
 
 /* -------------------------------------------------------------------------- */
@@ -374,11 +389,10 @@ void reload(ID channelId)
 
 void shift(ID channelId, Frame offset)
 {
-       namespace mm = m::model;
-
        Frame shift = getSamplePlayer_(channelId).shift;
 
-       mm::DataLock lock();
+       m::model::DataLock lock = g_engine.model.lockData();
+
        m::wfx::shift(getWave_(channelId), offset - shift);
        getSamplePlayer_(channelId).shift = offset;
 
index 4a1473b90940eb6d4d081818fb13d6b8e6f06960..fe93099c28c6d27d9d928f16747f8658aa48d517 100644 (file)
 namespace giada::m
 {
 class Wave;
-}
-namespace giada::m::channel
-{
-struct Data;
-}
+class Channel;
+} // namespace giada::m
+
 namespace giada::v
 {
 class gdSampleEditor;
 }
+
 namespace giada::c::sampleEditor
 {
 struct Data
 {
        Data() = default;
-       Data(const m::channel::Data&);
+       Data(const m::Channel&);
 
        ChannelStatus  a_getPreviewStatus() const;
        Frame          a_getPreviewTracker() const;
        const m::Wave& getWaveRef() const; // TODO - getWaveData (or public ptr member to Wave::data)
+       Frame          getFramesInBar() const;
+       Frame          getFramesInLoop() const;
 
        ID          channelId;
        std::string name;
@@ -70,8 +71,8 @@ struct Data
        std::string wavePath;
        bool        isLogical;
 
-  private:
-       const m::channel::Data* m_channel;
+private:
+       const m::Channel* m_channel;
 };
 
 /* onRefresh --- TODO - wrong name */
index e468facf8a16c2df40089415d5b1e0894472283e..8e842ae9069228386dbb14eb399c228c9cc0b914 100644 (file)
@@ -26,8 +26,8 @@
 
 #include "core/model/storage.h"
 #include "channel.h"
-#include "core/clock.h"
 #include "core/conf.h"
+#include "core/engine.h"
 #include "core/init.h"
 #include "core/mixer.h"
 #include "core/mixerHandler.h"
@@ -36,7 +36,7 @@
 #include "core/plugins/plugin.h"
 #include "core/plugins/pluginHost.h"
 #include "core/plugins/pluginManager.h"
-#include "core/recorderHandler.h"
+#include "core/sequencer.h"
 #include "core/wave.h"
 #include "core/waveManager.h"
 #include "gui/dialogs/browser/browserLoad.h"
@@ -46,8 +46,9 @@
 #include "gui/elems/basics/progress.h"
 #include "gui/elems/mainWindow/keyboard/column.h"
 #include "gui/elems/mainWindow/keyboard/keyboard.h"
-#include "gui/model.h"
+#include "gui/ui.h"
 #include "main.h"
+#include "src/core/actions/actionRecorder.h"
 #include "storage.h"
 #include "utils/fs.h"
 #include "utils/gui.h"
 #include "utils/string.h"
 #include <cassert>
 
-extern giada::v::gdMainWindow* G_MainWin;
+extern giada::m::Engine g_engine;
+extern giada::v::Ui     g_ui;
 
-namespace giada
+namespace giada::c::storage
 {
-namespace c
-{
-namespace storage
-{
-namespace
-{
-std::string makeWavePath_(const std::string& base, const m::Wave& w, int k)
-{
-       return base + G_SLASH + w.getBasename(/*ext=*/false) + "-" + std::to_string(k) + w.getExtension();
-}
-
-bool isWavePathUnique_(const m::Wave& skip, const std::string& path)
-{
-       for (const auto& w : m::model::getAll<m::model::WavePtrs>())
-               if (w->id != skip.id && w->getPath() == path)
-                       return false;
-       return true;
-}
-
-std::string makeUniqueWavePath_(const std::string& base, const m::Wave& w)
-{
-       std::string path = base + G_SLASH + w.getBasename(/*ext=*/true);
-       if (isWavePathUnique_(w, path))
-               return path;
-
-       // TODO - just use a timestamp. e.g. makeWavePath_(..., ..., getTimeStamp())
-       int k = 0;
-       path  = makeWavePath_(base, w, k);
-       while (!isWavePathUnique_(w, path))
-               path = makeWavePath_(base, w, k++);
-
-       return path;
-}
-
-/* -------------------------------------------------------------------------- */
-
-bool savePatch_(const std::string& path, const std::string& name)
-{
-       m::patch::init();
-       m::patch::patch.name = name;
-       m::model::store(m::patch::patch);
-       v::model::store(m::patch::patch);
-
-       if (!m::patch::write(path))
-               return false;
-
-       u::gui::updateMainWinLabel(name);
-       m::conf::conf.patchPath = u::fs::getUpDir(u::fs::getUpDir(path));
-       u::log::print("[savePatch] patch saved as %s\n", path);
-
-       return true;
-}
-
-/* -------------------------------------------------------------------------- */
-
-void saveWavesToProject_(const std::string& basePath)
-{
-       for (const std::unique_ptr<m::Wave>& w : m::model::getAll<m::model::WavePtrs>())
-       {
-               w->setPath(makeUniqueWavePath_(basePath, *w));
-               m::waveManager::save(*w, w->getPath()); // TODO - error checking
-       }
-}
-} // namespace
-
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-
 void loadProject(void* data)
 {
-       v::gdBrowserLoad* browser  = static_cast<v::gdBrowserLoad*>(data);
-       std::string       fullPath = browser->getSelectedItem();
+       v::gdBrowserLoad* browser = static_cast<v::gdBrowserLoad*>(data);
 
-       browser->showStatusBar();
-
-       u::log::print("[loadProject] load from %s\n", fullPath);
+       const std::string projectPath = browser->getSelectedItem();
+       const std::string patchPath   = projectPath + G_SLASH + u::fs::stripExt(u::fs::basename(projectPath)) + ".gptc";
 
-       std::string fileToLoad = fullPath + G_SLASH + u::fs::stripExt(u::fs::basename(fullPath)) + ".gptc";
-       std::string basePath   = fullPath + G_SLASH;
-
-       /* Read the patch from file. */
+       browser->showStatusBar();
 
-       m::patch::init();
-       int res = m::patch::read(fileToLoad, basePath);
-       if (res != G_PATCH_OK)
+       if (int res = g_engine.load(projectPath, patchPath); res != G_PATCH_OK)
        {
                if (res == G_PATCH_UNREADABLE)
                        v::gdAlert("This patch is unreadable.");
@@ -156,35 +82,14 @@ void loadProject(void* data)
                return;
        }
 
-       /* Then reset the system (it disables mixer) and fill the model. */
-
-       m::init::reset();
-       v::model::load(m::patch::patch);
-       m::model::load(m::patch::patch);
-
-       /* Prepare the engine. Recorder has to recompute the actions positions if 
-       the current samplerate != patch samplerate. Clock needs to update frames
-       in sequencer. */
-
-       m::mh::updateSoloCount();
-       m::recorderHandler::updateSamplerate(m::conf::conf.samplerate, m::patch::patch.samplerate);
-       m::clock::recomputeFrames();
-       m::mixer::allocRecBuffer(m::clock::getMaxFramesInLoop());
+       /* Update UI. */
 
-       /* Mixer is ready to go back online. */
-
-       m::mixer::enable();
-
-       /* Utilities and cosmetics. Save patchPath by taking the last dir of the 
-       broswer, in order to reuse it the next time. Also update UI. */
-
-       m::conf::conf.patchPath = u::fs::dirname(fullPath);
-       u::gui::updateMainWinLabel(m::patch::patch.name);
+       g_ui.load(g_engine.patch.data);
 
 #ifdef WITH_VST
 
-       if (m::pluginManager::hasMissingPlugins())
-               v::gdAlert("Some plugins were not loaded successfully.\nCheck the plugin browser to know more.");
+       if (g_engine.pluginManager.hasMissingPlugins())
+               v::gdAlert("Some plug-ins were not loaded successfully.\nCheck the Plug-in Browser to know more.");
 
 #endif
 
@@ -195,35 +100,30 @@ void loadProject(void* data)
 
 void saveProject(void* data)
 {
-       v::gdBrowserSave* browser    = static_cast<v::gdBrowserSave*>(data);
-       std::string       name       = u::fs::stripExt(browser->getName());
-       std::string       folderPath = browser->getCurrentPath();
-       std::string       fullPath   = folderPath + G_SLASH + name + ".gprj";
-       std::string       gptcPath   = fullPath + G_SLASH + name + ".gptc";
+       v::gdBrowserSave* browser = static_cast<v::gdBrowserSave*>(data);
 
-       if (name == "")
+       const std::string projectName = u::fs::stripExt(browser->getName());
+       const std::string projectPath = browser->getCurrentPath() + G_SLASH + projectName + ".gprj";
+       const std::string patchPath   = projectPath + G_SLASH + projectName + ".gptc";
+
+       if (projectName == "")
        {
                v::gdAlert("Please choose a project name.");
                return;
        }
 
-       if (u::fs::dirExists(fullPath) && !v::gdConfirmWin("Warning", "Project exists: overwrite?"))
+       if (u::fs::dirExists(projectPath) && !v::gdConfirmWin("Warning", "Project exists: overwrite?"))
                return;
 
-       if (!u::fs::mkdir(fullPath))
+       g_ui.store(g_engine.patch.data);
+
+       if (!g_engine.store(projectName, projectPath, patchPath))
        {
-               u::log::print("[saveProject] Unable to make project directory!\n");
+               v::gdAlert("Unable to save the project!");
                return;
        }
 
-       u::log::print("[saveProject] Project dir created: %s\n", fullPath);
-
-       saveWavesToProject_(fullPath);
-
-       if (savePatch_(gptcPath, name))
-               browser->do_callback();
-       else
-               v::gdAlert("Unable to save the project!");
+       browser->do_callback();
 }
 
 /* -------------------------------------------------------------------------- */
@@ -240,9 +140,9 @@ void loadSample(void* data)
 
        if (res == G_RES_OK)
        {
-               m::conf::conf.samplePath = u::fs::dirname(fullPath);
+               g_engine.conf.data.samplePath = u::fs::dirname(fullPath);
                browser->do_callback();
-               G_MainWin->delSubWindow(WID_SAMPLE_EDITOR); // if editor is open
+               g_ui.mainWindow->delSubWindow(WID_SAMPLE_EDITOR); // if editor is open
        }
 }
 
@@ -266,12 +166,12 @@ void saveSample(void* data)
        if (u::fs::fileExists(filePath) && !v::gdConfirmWin("Warning", "File exists: overwrite?"))
                return;
 
-       ID       waveId = m::model::get().getChannel(channelId).samplePlayer->getWaveId();
-       m::Wave* wave   = m::model::find<m::Wave>(waveId);
+       ID       waveId = g_engine.model.get().getChannel(channelId).samplePlayer->getWaveId();
+       m::Wave* wave   = g_engine.model.findShared<m::Wave>(waveId);
 
        assert(wave != nullptr);
 
-       if (!m::waveManager::save(*wave, filePath))
+       if (!g_engine.waveManager.save(*wave, filePath))
        {
                v::gdAlert("Unable to save this sample!");
                return;
@@ -281,11 +181,11 @@ void saveSample(void* data)
 
        /* Update last used path in conf, so that it can be reused next time. */
 
-       m::conf::conf.samplePath = u::fs::dirname(filePath);
+       g_engine.conf.data.samplePath = u::fs::dirname(filePath);
 
        /* Update logical and edited states in Wave. */
 
-       m::model::DataLock lock;
+       m::model::DataLock lock = g_engine.model.lockData();
        wave->setLogical(false);
        wave->setEdited(false);
 
@@ -293,6 +193,4 @@ void saveSample(void* data)
 
        browser->do_callback();
 }
-} // namespace storage
-} // namespace c
-} // namespace giada
+} // namespace giada::c::storage
\ No newline at end of file
index 75352f7029c635a8340dfdce488768fc283e73d8..661130045347cf3c95d60c129f9841f09765db31 100644 (file)
 #ifndef G_GLUE_STORAGE_H
 #define G_GLUE_STORAGE_H
 
-namespace giada
-{
-namespace c
-{
-namespace storage
+namespace giada::c::storage
 {
 void loadProject(void* data);
 void saveProject(void* data);
 void saveSample(void* data);
 void loadSample(void* data);
-} // namespace storage
-} // namespace c
-} // namespace giada
+} // namespace giada::c::storage
 
 #endif
index 9acb522097698bcbccbdab02847fced9adafef76..dd8b480aaae8e9c91a6fcace8368afb343d6b6a4 100644 (file)
  *
  * -------------------------------------------------------------------------- */
 
-#include "baseActionEditor.h"
-#include "core/action.h"
-#include "core/clock.h"
+#include "gui/dialogs/actionEditor/baseActionEditor.h"
 #include "core/conf.h"
 #include "core/const.h"
-#include "core/midiEvent.h"
+#include "core/graphics.h"
 #include "glue/channel.h"
+#include "gui/drawing.h"
 #include "gui/elems/actionEditor/gridTool.h"
+#include "gui/elems/basics/button.h"
 #include "gui/elems/basics/choice.h"
 #include "gui/elems/basics/scrollPack.h"
+#include "src/core/actions/action.h"
 #include "utils/gui.h"
 #include "utils/string.h"
 #include <FL/Fl.H>
 #include <FL/fl_draw.H>
 #include <cassert>
+#include <limits>
 #include <string>
 
-namespace giada
+namespace giada::v
 {
-namespace v
-{
-gdBaseActionEditor::gdBaseActionEditor(ID channelId)
-: gdWindow(640, 284)
+gdBaseActionEditor::gdBaseActionEditor(ID channelId, m::Conf::Data& conf, Frame framesInBeat)
+: gdWindow(conf.actionEditorX, conf.actionEditorY, conf.actionEditorW, conf.actionEditorH)
 , channelId(channelId)
-, ratio(G_DEFAULT_ZOOM_RATIO)
+, gridTool(0, 0, conf, framesInBeat)
+, zoomInBtn(0, 0, G_GUI_UNIT, G_GUI_UNIT, "", zoomInOff_xpm, zoomInOn_xpm)
+, zoomOutBtn(0, 0, G_GUI_UNIT, G_GUI_UNIT, "", zoomOutOff_xpm, zoomOutOn_xpm)
+, m_barTop(0, 0, Direction::HORIZONTAL)
+, m_splitScroll(0, 0, 0, 0)
+, m_conf(conf)
+, m_playhead(0)
+, m_ratio(conf.actionEditorZoom)
 {
-       using namespace giada::m;
+       end();
 
-       if (conf::conf.actionEditorW)
-       {
-               resize(conf::conf.actionEditorX, conf::conf.actionEditorY,
-                   conf::conf.actionEditorW, conf::conf.actionEditorH);
-               ratio = conf::conf.actionEditorZoom;
-       }
+       m_barTop.position(G_GUI_OUTER_MARGIN, G_GUI_OUTER_MARGIN);
+
+       m_splitScroll.resize(
+           G_GUI_OUTER_MARGIN,
+           (G_GUI_OUTER_MARGIN * 2) + 20,
+           w() - G_GUI_OUTER_MARGIN * 2,
+           h() - (G_GUI_OUTER_MARGIN * 3) - 20);
+
+       zoomInBtn.callback(cb_zoomIn, this);
+       zoomInBtn.copy_tooltip("Zoom in");
+       zoomOutBtn.callback(cb_zoomOut, this);
+       zoomOutBtn.copy_tooltip("Zoom out");
+
+       add(m_barTop);
+       add(m_splitScroll);
 }
 
 /* -------------------------------------------------------------------------- */
@@ -66,11 +82,19 @@ gdBaseActionEditor::~gdBaseActionEditor()
 {
        using namespace giada::m;
 
-       conf::conf.actionEditorX    = x();
-       conf::conf.actionEditorY    = y();
-       conf::conf.actionEditorW    = w();
-       conf::conf.actionEditorH    = h();
-       conf::conf.actionEditorZoom = ratio;
+       m_conf.actionEditorX      = x();
+       m_conf.actionEditorY      = y();
+       m_conf.actionEditorW      = w();
+       m_conf.actionEditorH      = h();
+       m_conf.actionEditorSplitH = m_splitScroll.getTopContentH();
+       m_conf.actionEditorZoom   = m_ratio;
+}
+
+/* -------------------------------------------------------------------------- */
+
+int gdBaseActionEditor::getMouseOverContent() const
+{
+       return m_splitScroll.getScrollX() + (Fl::event_x() - G_GUI_OUTER_MARGIN);
 }
 
 /* -------------------------------------------------------------------------- */
@@ -80,89 +104,38 @@ void gdBaseActionEditor::cb_zoomOut(Fl_Widget* /*w*/, void* p) { ((gdBaseActionE
 
 /* -------------------------------------------------------------------------- */
 
-void gdBaseActionEditor::computeWidth()
+void gdBaseActionEditor::computeWidth(Frame framesInSeq, Frame framesInLoop)
 {
-       fullWidth = frameToPixel(m::clock::getFramesInSeq());
-       loopWidth = frameToPixel(m::clock::getFramesInLoop());
+       fullWidth = frameToPixel(framesInSeq);
+       loopWidth = frameToPixel(framesInLoop);
 }
 
 /* -------------------------------------------------------------------------- */
 
 Pixel gdBaseActionEditor::frameToPixel(Frame f) const
 {
-       return f / ratio;
+       return f / m_ratio;
 }
 
 Frame gdBaseActionEditor::pixelToFrame(Pixel p, bool snap) const
 {
-       return snap ? gridTool->getSnapFrame(p * ratio) : p * ratio;
+       return snap ? gridTool.getSnapFrame(p * m_ratio) : p * m_ratio;
 }
 
 /* -------------------------------------------------------------------------- */
 
 void gdBaseActionEditor::zoomIn()
 {
-       float ratioPrev = ratio;
-
-       ratio /= 2;
-       if (ratio < MIN_RATIO)
-               ratio = MIN_RATIO;
-
-       if (ratioPrev != ratio)
-       {
-               rebuild();
-               centerViewportIn();
-               redraw();
-       }
+       // Explicit type std::max<int> to fix MINMAX macro hell on Windows
+       zoomAbout([&r = m_ratio]() { return std::max<int>(r / RATIO_STEP, MIN_RATIO); });
 }
 
 /* -------------------------------------------------------------------------- */
 
 void gdBaseActionEditor::zoomOut()
 {
-       float ratioPrev = ratio;
-
-       ratio *= 2;
-       if (ratio > MAX_RATIO)
-               ratio = MAX_RATIO;
-
-       if (ratioPrev != ratio)
-       {
-               rebuild();
-               centerViewportOut();
-               redraw();
-       }
-}
-
-/* -------------------------------------------------------------------------- */
-
-void gdBaseActionEditor::centerViewportIn()
-{
-       Pixel sx = Fl::event_x() + (viewport->xposition() * 2);
-       viewport->scroll_to(sx, viewport->yposition());
-}
-
-void gdBaseActionEditor::centerViewportOut()
-{
-       Pixel sx = -((Fl::event_x() + viewport->xposition()) / 2) + viewport->xposition();
-       if (sx < 0)
-               sx = 0;
-       viewport->scroll_to(sx, viewport->yposition());
-}
-
-/* -------------------------------------------------------------------------- */
-
-int gdBaseActionEditor::getActionType() const
-{
-       if (actionType->value() == 0)
-               return m::MidiEvent::NOTE_ON;
-       else if (actionType->value() == 1)
-               return m::MidiEvent::NOTE_OFF;
-       else if (actionType->value() == 2)
-               return m::MidiEvent::NOTE_KILL;
-
-       assert(false);
-       return -1;
+       // Explicit type std::max<int> to fix MINMAX macro hell on Windows
+       zoomAbout([&r = m_ratio]() { return std::min<int>(r * RATIO_STEP, MAX_RATIO); });
 }
 
 /* -------------------------------------------------------------------------- */
@@ -178,7 +151,6 @@ void gdBaseActionEditor::prepareWindow()
 
        set_non_modal();
        size_range(640, 284);
-       resizable(viewport);
 
        show();
 }
@@ -196,5 +168,64 @@ int gdBaseActionEditor::handle(int e)
                return Fl_Group::handle(e);
        }
 }
-} // namespace v
-} // namespace giada
+
+/* -------------------------------------------------------------------------- */
+
+void gdBaseActionEditor::draw()
+{
+       gdWindow::draw();
+
+       const geompp::Rect splitBounds = m_splitScroll.getBoundsNoScrollbar();
+       const geompp::Line playhead    = splitBounds.getHeightAsLine().withX(m_playhead);
+
+       if (splitBounds.contains(playhead))
+               drawLine(playhead, G_COLOR_LIGHT_2);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void gdBaseActionEditor::zoomAbout(std::function<float()> f)
+{
+       const float ratioPrev = m_ratio;
+       const int   minWidth  = w() - (G_GUI_OUTER_MARGIN * 2);
+
+       m_ratio = f();
+
+       /* Make sure the new content width doesn't underflow the window space (i.e. 
+       the minimum width allowed). */
+
+       if (frameToPixel(m_data.framesInSeq) < minWidth)
+       {
+               m_ratio = m_data.framesInSeq / static_cast<float>(minWidth);
+               m_splitScroll.setScrollX(0);
+       }
+
+       /* 1. Store the current x-position, then the new x-position affected by the
+       zoom change. */
+
+       const int mpre = getMouseOverContent();
+       const int mnow = mpre / (m_ratio / ratioPrev);
+
+       /* 2. Rebuild everything and adjust scrolling given the change occurred in
+       the x-position. This effectively centers the view on the mouse cursor. */
+
+       rebuild();
+       m_splitScroll.setScrollX(m_splitScroll.getScrollX() + (mnow - mpre));
+       redraw();
+}
+
+/* -------------------------------------------------------------------------- */
+
+void gdBaseActionEditor::refresh()
+{
+       m_playhead = m_data.isChannelPlaying() ? currentFrameToPixel() : 0;
+       redraw();
+}
+
+/* -------------------------------------------------------------------------- */
+
+Pixel gdBaseActionEditor::currentFrameToPixel() const
+{
+       return (frameToPixel(m_data.getCurrentFrame()) + m_splitScroll.x()) - m_splitScroll.getScrollX();
+}
+} // namespace giada::v
\ No newline at end of file
index c76143d9425e41080429d78f74e21f6a4643c37f..b25a6a32acb21d92007120aa4ef5cafc73ae75a2 100644 (file)
 #ifndef GD_BASE_ACTION_EDITOR_H
 #define GD_BASE_ACTION_EDITOR_H
 
-#include "core/types.h"
+#include "core/conf.h"
 #include "glue/actionEditor.h"
 #include "gui/dialogs/window.h"
+#include "gui/elems/actionEditor/gridTool.h"
+#include "gui/elems/actionEditor/splitScroll.h"
+#include "gui/elems/basics/button.h"
+#include "gui/elems/basics/pack.h"
 
-class geButton;
-
-namespace giada
-{
-namespace m
+namespace giada::m
 {
 class Channel;
 struct Action;
-} // namespace m
-namespace v
+} // namespace giada::m
+
+namespace giada::v
 {
-class geChoice;
-class geGridTool;
-class geScrollPack;
 class gdBaseActionEditor : public gdWindow
 {
 public:
        virtual ~gdBaseActionEditor();
 
-       int handle(int e) override;
+       int  handle(int e) override;
+       void draw() override;
 
        Pixel frameToPixel(Frame f) const;
        Frame pixelToFrame(Pixel p, bool snap = true) const;
-       int   getActionType() const;
 
        ID channelId;
 
-       geChoice*     actionType;
-       geGridTool*   gridTool;
-       geButton*     zoomInBtn;
-       geButton*     zoomOutBtn;
-       geScrollPack* viewport; // widget container
+       geGridTool gridTool;
+       geButton   zoomInBtn;
+       geButton   zoomOutBtn;
 
-       float ratio;
        Pixel fullWidth; // Full widgets width, i.e. scaled-down full sequencer
        Pixel loopWidth; // Loop width, i.e. scaled-down sequencer range
 
-  protected:
-       static constexpr Pixel RESIZER_BAR_H = 20;
-       static constexpr Pixel MIN_WIDGET_H  = 10;
-       static constexpr float MIN_RATIO     = 25.0f;
-       static constexpr float MAX_RATIO     = 40000.0f;
+protected:
+       static constexpr float MIN_RATIO  = 25.0f;
+       static constexpr float MAX_RATIO  = 40000.0f;
+       static constexpr float RATIO_STEP = 1.5f;
 
-       gdBaseActionEditor(ID channelId);
+       gdBaseActionEditor(ID channelId, m::Conf::Data&, Frame framesInBeat);
 
+       /* getMouseOverContent
+       Returns mouse x-position relative to the viewport content. */
+
+       int getMouseOverContent() const;
+
+       static void cb_zoomIn(Fl_Widget* w, void* p);
+       static void cb_zoomOut(Fl_Widget* w, void* p);
        void        zoomIn();
        void        zoomOut();
-       static void cb_zoomIn(Fl_Widget* /*w*/, void* p);
-       static void cb_zoomOut(Fl_Widget* /*w*/, void* p);
 
        /* computeWidth
        Computes total width, in pixel. */
 
-       void computeWidth();
+       void computeWidth(Frame framesInSeq, Frame framesInLoop);
 
-       void centerViewportIn();
-       void centerViewportOut();
+       /* prepareWindow
+       Initializes window (favicon, limits, ...). */
 
        void prepareWindow();
 
+       gePack        m_barTop;
+       geSplitScroll m_splitScroll;
+
        c::actionEditor::Data m_data;
-};
-} // namespace v
-} // namespace giada
+       m::Conf::Data&        m_conf;
+
+private:
+       void refresh() override;
+
+       /* zoomAbout
+       Zooms and centers the viewport around the mouse cursor. Wants a function to 
+       apply to the current ratio. */
 
+       void zoomAbout(std::function<float()> f);
+
+       Pixel currentFrameToPixel() const;
+
+       Pixel m_playhead;
+       float m_ratio;
+};
+} // namespace giada::v
 #endif
index 851e71639beba694e39de161d2ac05d2ea1624a9..911dfdfc3c66956ec24c3f3cc309c3057092aef7 100644 (file)
  * -------------------------------------------------------------------------- */
 
 #include "midiActionEditor.h"
-#include "core/graphics.h"
+#include "core/conf.h"
 #include "glue/actionEditor.h"
 #include "glue/channel.h"
-#include "gui/elems/actionEditor/gridTool.h"
-#include "gui/elems/actionEditor/noteEditor.h"
-#include "gui/elems/actionEditor/pianoRoll.h"
-#include "gui/elems/actionEditor/velocityEditor.h"
 #include "gui/elems/basics/box.h"
-#include "gui/elems/basics/button.h"
-#include "gui/elems/basics/resizerBar.h"
-#include "gui/elems/basics/scrollPack.h"
-#include <string>
 
-namespace giada
+namespace giada::v
 {
-namespace v
-{
-gdMidiActionEditor::gdMidiActionEditor(ID channelId)
-: gdBaseActionEditor(channelId)
+gdMidiActionEditor::gdMidiActionEditor(ID channelId, m::Conf::Data& conf, Frame framesInBeat)
+: gdBaseActionEditor(channelId, conf, framesInBeat)
+, m_barPadding(0, 0, w() - 150, G_GUI_UNIT)
+, m_pianoRoll(0, 0, this)
+, m_velocityEditor(0, 0, this)
 {
        end();
 
-       computeWidth();
-
-       gePack* upperArea = new gePack(G_GUI_OUTER_MARGIN, G_GUI_OUTER_MARGIN, Direction::HORIZONTAL);
-       gridTool          = new geGridTool(0, 0);
-       geBox* b1         = new geBox(0, 0, w() - 150, G_GUI_UNIT); // padding actionType - zoomButtons
-       zoomInBtn         = new geButton(0, 0, G_GUI_UNIT, G_GUI_UNIT, "", zoomInOff_xpm, zoomInOn_xpm);
-       zoomOutBtn        = new geButton(0, 0, G_GUI_UNIT, G_GUI_UNIT, "", zoomOutOff_xpm, zoomOutOn_xpm);
-       upperArea->add(gridTool);
-       upperArea->add(b1);
-       upperArea->add(zoomInBtn);
-       upperArea->add(zoomOutBtn);
-       upperArea->resizable(b1);
-
-       /* Main viewport: contains all widgets. */
+       m_barTop.add(&gridTool);
+       m_barTop.add(&m_barPadding);
+       m_barTop.add(&zoomInBtn);
+       m_barTop.add(&zoomOutBtn);
+       m_barTop.resizable(m_barPadding);
 
-       viewport = new geScrollPack(G_GUI_OUTER_MARGIN, upperArea->y() + upperArea->h() + G_GUI_OUTER_MARGIN,
-           upperArea->w(), h() - 44, Fl_Scroll::BOTH, Direction::VERTICAL, /*gutter=*/0);
-       m_ne     = new geNoteEditor(0, 0, this);
-       m_ner    = new geResizerBar(0, 0, viewport->w(), RESIZER_BAR_H, MIN_WIDGET_H, geResizerBar::VERTICAL);
-       m_ve     = new geVelocityEditor(0, 0, this);
-       m_ver    = new geResizerBar(0, 0, viewport->w(), RESIZER_BAR_H, MIN_WIDGET_H, geResizerBar::VERTICAL);
-       viewport->add(m_ne);
-       viewport->add(m_ner);
-       viewport->add(m_ve);
-       viewport->add(m_ver);
+       m_splitScroll.addWidgets(m_pianoRoll, m_velocityEditor, conf.actionEditorSplitH);
 
-       zoomInBtn->callback(cb_zoomIn, (void*)this);
-       zoomInBtn->copy_tooltip("Zoom in");
-       zoomOutBtn->callback(cb_zoomOut, (void*)this);
-       zoomOutBtn->copy_tooltip("Zoom out");
+       if (conf.actionEditorPianoRollY != -1)
+               m_splitScroll.setScrollY(conf.actionEditorPianoRollY);
 
-       add(upperArea);
-       add(viewport);
-       resizable(upperArea);
+       resizable(m_splitScroll); // Make it resizable only once filled with widgets
 
        prepareWindow();
        rebuild();
@@ -88,15 +59,23 @@ gdMidiActionEditor::gdMidiActionEditor(ID channelId)
 
 /* -------------------------------------------------------------------------- */
 
+gdMidiActionEditor::~gdMidiActionEditor()
+{
+       m_conf.actionEditorPianoRollY = m_splitScroll.getScrollY();
+       m_barTop.remove(gridTool);
+       m_barTop.remove(zoomInBtn);
+       m_barTop.remove(zoomOutBtn);
+}
+
+/* -------------------------------------------------------------------------- */
+
 void gdMidiActionEditor::rebuild()
 {
        m_data = c::actionEditor::getData(channelId);
 
-       computeWidth();
-       m_ne->rebuild(m_data);
-       m_ner->size(m_ne->w(), m_ner->h());
-       m_ve->rebuild(m_data);
-       m_ver->size(m_ve->w(), m_ver->h());
+       computeWidth(m_data.framesInSeq, m_data.framesInLoop);
+
+       m_pianoRoll.rebuild(m_data);
+       m_velocityEditor.rebuild(m_data);
 }
-} // namespace v
-} // namespace giada
+} // namespace giada::v
\ No newline at end of file
index 85390274d2a7c90b0ef974c2b68fbf428a10aa4c..75749cf762cbfb91c5a1182700dbc224c9650274 100644 (file)
 #define GD_MIDI_ACTION_EDITOR_H
 
 #include "baseActionEditor.h"
+#include "gui/elems/actionEditor/pianoRoll.h"
+#include "gui/elems/actionEditor/velocityEditor.h"
+#include "gui/elems/basics/box.h"
 
-class geResizerBar;
-
-namespace giada
-{
-namespace v
+namespace giada::v
 {
-class geNoteEditor;
-class geVelocityEditor;
-
 class gdMidiActionEditor : public gdBaseActionEditor
 {
 public:
-       gdMidiActionEditor(ID channelId);
+       gdMidiActionEditor(ID channelId, m::Conf::Data&, Frame framesInBeat);
+       ~gdMidiActionEditor();
 
        void rebuild() override;
 
-  private:
-       geNoteEditor* m_ne;
-       geResizerBar* m_ner;
-
-       geVelocityEditor* m_ve;
-       geResizerBar*     m_ver;
+private:
+       geBox            m_barPadding;
+       gePianoRoll      m_pianoRoll;
+       geVelocityEditor m_velocityEditor;
 };
-} // namespace v
-} // namespace giada
+} // namespace giada::v
 
 #endif
index a6aea68819a3de724ade8e3e454289dd49c85192..5c75220fc5c10e97730d448f0e220f63eccc779b 100644 (file)
  *
  * -------------------------------------------------------------------------- */
 
-#include "gui/elems/actionEditor/sampleActionEditor.h"
+#include "sampleActionEditor.h"
+#include "core/conf.h"
 #include "core/const.h"
-#include "core/graphics.h"
 #include "core/midiEvent.h"
 #include "core/model/model.h"
 #include "glue/actionEditor.h"
 #include "glue/channel.h"
-#include "gui/elems/actionEditor/envelopeEditor.h"
-#include "gui/elems/actionEditor/gridTool.h"
 #include "gui/elems/basics/box.h"
-#include "gui/elems/basics/button.h"
-#include "gui/elems/basics/choice.h"
-#include "gui/elems/basics/pack.h"
-#include "gui/elems/basics/resizerBar.h"
-#include "gui/elems/basics/scrollPack.h"
-#include "sampleActionEditor.h"
 #include <string>
 
-namespace giada
+namespace giada::v
 {
-namespace v
-{
-gdSampleActionEditor::gdSampleActionEditor(ID channelId)
-: gdBaseActionEditor(channelId)
+gdSampleActionEditor::gdSampleActionEditor(ID channelId, m::Conf::Data& conf, Frame framesInBeat)
+: gdBaseActionEditor(channelId, conf, framesInBeat)
+, m_barPadding(0, 0, w() - 232, G_GUI_UNIT)
+, m_sampleActionEditor(0, 0, this)
+, m_envelopeEditor(0, 0, "Volume", this)
+, m_actionType(0, 0, 80, G_GUI_UNIT)
 {
        end();
 
-       computeWidth();
-
-       /* Container with zoom buttons and the action type selector. Scheme of the 
-       resizable boxes: |[--b1--][actionType][--b2--][+][-]| */
-
-       gePack* upperArea = new gePack(G_GUI_OUTER_MARGIN, G_GUI_OUTER_MARGIN, Direction::HORIZONTAL);
-       actionType        = new geChoice(0, 0, 80, 20);
-       gridTool          = new geGridTool(0, 0);
-       geBox* b1         = new geBox(0, 0, w() - 232, 20); // padding actionType - zoomButtons
-       zoomInBtn         = new geButton(0, 0, 20, 20, "", zoomInOff_xpm, zoomInOn_xpm);
-       zoomOutBtn        = new geButton(0, 0, 20, 20, "", zoomOutOff_xpm, zoomOutOn_xpm);
-       upperArea->add(actionType);
-       upperArea->add(gridTool);
-       upperArea->add(b1);
-       upperArea->add(zoomInBtn);
-       upperArea->add(zoomOutBtn);
-       upperArea->resizable(b1);
-
-       actionType->add("Key press");
-       actionType->add("Key release");
-       actionType->add("Kill chan");
-       actionType->value(0);
-       actionType->copy_tooltip("Action type to add");
+       m_barTop.add(&m_actionType);
+       m_barTop.add(&gridTool);
+       m_barTop.add(&m_barPadding);
+       m_barTop.add(&zoomInBtn);
+       m_barTop.add(&zoomOutBtn);
+       m_barTop.resizable(m_barPadding);
+
+       m_actionType.add("Key press");
+       m_actionType.add("Key release");
+       m_actionType.add("Kill chan");
+       m_actionType.value(0);
+       m_actionType.copy_tooltip("Action type to add");
        if (!canChangeActionType())
-               actionType->deactivate();
-
-       zoomInBtn->callback(cb_zoomIn, (void*)this);
-       zoomInBtn->copy_tooltip("Zoom in");
-       zoomOutBtn->callback(cb_zoomOut, (void*)this);
-       zoomOutBtn->copy_tooltip("Zoom out");
-
-       /* Main viewport: contains all widgets. */
-
-       viewport = new geScrollPack(G_GUI_OUTER_MARGIN, upperArea->y() + upperArea->h() + G_GUI_OUTER_MARGIN,
-           upperArea->w(), h() - 44, Fl_Scroll::BOTH, Direction::VERTICAL, /*gutter=*/0);
-       m_ae     = new geSampleActionEditor(0, 0, this);
-       m_aer    = new geResizerBar(0, 0, viewport->w(), RESIZER_BAR_H, MIN_WIDGET_H, geResizerBar::VERTICAL);
-       m_ee     = new geEnvelopeEditor(0, 0, "volume", this);
-       m_eer    = new geResizerBar(0, 0, viewport->w(), RESIZER_BAR_H, MIN_WIDGET_H, geResizerBar::VERTICAL);
-       viewport->add(m_ae);
-       viewport->add(m_aer);
-       viewport->add(m_ee);
-       viewport->add(m_eer);
-
-       add(upperArea);
-       add(viewport);
-       resizable(upperArea);
+               m_actionType.deactivate();
+
+       m_splitScroll.addWidgets(m_sampleActionEditor, m_envelopeEditor, conf.actionEditorSplitH);
+
+       resizable(m_splitScroll); // Make it resizable only once filled with widgets
 
        prepareWindow();
        rebuild();
@@ -105,6 +70,16 @@ gdSampleActionEditor::gdSampleActionEditor(ID channelId)
 
 /* -------------------------------------------------------------------------- */
 
+gdSampleActionEditor::~gdSampleActionEditor()
+{
+       m_barTop.remove(m_actionType);
+       m_barTop.remove(gridTool);
+       m_barTop.remove(zoomInBtn);
+       m_barTop.remove(zoomOutBtn);
+}
+
+/* -------------------------------------------------------------------------- */
+
 bool gdSampleActionEditor::canChangeActionType()
 {
        return m_data.sample->channelMode != SamplePlayerMode::SINGLE_PRESS &&
@@ -117,13 +92,25 @@ void gdSampleActionEditor::rebuild()
 {
        m_data = c::actionEditor::getData(channelId);
 
-       canChangeActionType() ? actionType->activate() : actionType->deactivate();
-       computeWidth();
+       canChangeActionType() ? m_actionType.activate() : m_actionType.deactivate();
+       computeWidth(m_data.framesInSeq, m_data.framesInLoop);
+
+       m_sampleActionEditor.rebuild(m_data);
+       m_envelopeEditor.rebuild(m_data);
+}
 
-       m_ae->rebuild(m_data);
-       m_aer->size(m_ae->w(), m_aer->h());
-       m_ee->rebuild(m_data);
-       m_eer->size(m_ee->w(), m_eer->h());
+/* -------------------------------------------------------------------------- */
+
+int gdSampleActionEditor::getActionType() const
+{
+       if (m_actionType.value() == 0)
+               return m::MidiEvent::NOTE_ON;
+       else if (m_actionType.value() == 1)
+               return m::MidiEvent::NOTE_OFF;
+       else if (m_actionType.value() == 2)
+               return m::MidiEvent::NOTE_KILL;
+
+       assert(false);
+       return -1;
 }
-} // namespace v
-} // namespace giada
+} // namespace giada::v
\ No newline at end of file
index 03d167b4e5300495197116c04df48f78f5636154..202133112e1aa5121537571bc6bc60b280df03c2 100644 (file)
 #define GD_SAMPLE_ACTION_EDITOR_H
 
 #include "baseActionEditor.h"
+#include "gui/elems/actionEditor/envelopeEditor.h"
+#include "gui/elems/actionEditor/sampleActionEditor.h"
+#include "gui/elems/basics/box.h"
+#include "gui/elems/basics/choice.h"
 
-class geResizerBar;
-
-namespace giada
-{
-namespace v
+namespace giada::v
 {
-class geSampleActionEditor;
-class geEnvelopeEditor;
 class gdSampleActionEditor : public gdBaseActionEditor
 {
 public:
-       gdSampleActionEditor(ID channelId);
+       gdSampleActionEditor(ID channelId, m::Conf::Data&, Frame framesInBeat);
+       ~gdSampleActionEditor();
 
        void rebuild() override;
 
-  private:
-       bool canChangeActionType();
+       int getActionType() const;
 
-       geSampleActionEditor* m_ae;
-       geResizerBar*         m_aer;
+private:
+       bool canChangeActionType();
 
-       geEnvelopeEditor* m_ee;
-       geResizerBar*     m_eer;
+       geBox                m_barPadding;
+       geSampleActionEditor m_sampleActionEditor;
+       geEnvelopeEditor     m_envelopeEditor;
+       geChoice             m_actionType;
 };
-} // namespace v
-} // namespace giada
+} // namespace giada::v
 
 #endif
index e022b89ca01b72e5ef3e1ab2f907ede15cb1d206..7951b7d301e3bd458c37b57b66f7f9e0328843ca 100644 (file)
  * -------------------------------------------------------------------------- */
 
 #include "beatsInput.h"
-#include "core/clock.h"
-#include "core/conf.h"
 #include "core/const.h"
-#include "core/mixer.h"
 #include "glue/main.h"
 #include "gui/elems/basics/button.h"
 #include "gui/elems/basics/check.h"
 
 extern giada::v::gdMainWindow* mainWin;
 
-namespace giada
+namespace giada::v
 {
-namespace v
-{
-gdBeatsInput::gdBeatsInput()
+gdBeatsInput::gdBeatsInput(int beats, int bars)
 : gdWindow(u::gui::centerWindowX(180), u::gui::centerWindowY(36), 180, 36, "Beats")
 {
        set_modal();
 
-       beats = new geInput(8, 8, 43, G_GUI_UNIT);
-       bars  = new geInput(beats->x() + beats->w() + 4, 8, 43, G_GUI_UNIT);
-       ok    = new geButton(bars->x() + bars->w() + 4, 8, 70, G_GUI_UNIT, "Ok");
+       m_beats = new geInput(8, 8, 43, G_GUI_UNIT);
+       m_bars  = new geInput(m_beats->x() + m_beats->w() + 4, 8, 43, G_GUI_UNIT);
+       m_ok    = new geButton(m_bars->x() + m_bars->w() + 4, 8, 70, G_GUI_UNIT, "Ok");
        end();
 
-       beats->maximum_size(2);
-       beats->value(std::to_string(m::clock::getBeats()).c_str());
-       beats->type(FL_INT_INPUT);
+       m_beats->maximum_size(2);
+       m_beats->value(std::to_string(beats).c_str());
+       m_beats->type(FL_INT_INPUT);
 
-       bars->maximum_size(2);
-       bars->value(std::to_string(m::clock::getBars()).c_str());
-       bars->type(FL_INT_INPUT);
+       m_bars->maximum_size(2);
+       m_bars->value(std::to_string(bars).c_str());
+       m_bars->type(FL_INT_INPUT);
 
-       ok->shortcut(FL_Enter);
-       ok->callback(cb_update, (void*)this);
+       m_ok->shortcut(FL_Enter);
+       m_ok->callback(cb_update, (void*)this);
 
        u::gui::setFavicon(this);
        setId(WID_BEATS);
@@ -78,11 +73,9 @@ void gdBeatsInput::cb_update(Fl_Widget* /*w*/, void* p) { ((gdBeatsInput*)p)->cb
 
 void gdBeatsInput::cb_update()
 {
-       if (!strcmp(beats->value(), "") || !strcmp(bars->value(), ""))
+       if (!strcmp(m_beats->value(), "") || !strcmp(m_bars->value(), ""))
                return;
-       c::main::setBeats(atoi(beats->value()), atoi(bars->value()));
+       c::main::setBeats(atoi(m_beats->value()), atoi(m_bars->value()));
        do_callback();
 }
-
-} // namespace v
-} // namespace giada
\ No newline at end of file
+} // namespace giada::v
\ No newline at end of file
index 5cae21cf94f0a3551f2a7446aefed66860781ca4..19ed954ab136d06420f5c1db6df9a8b487cd9597 100644 (file)
@@ -33,24 +33,21 @@ class geInput;
 class geButton;
 class geCheck;
 
-namespace giada
-{
-namespace v
+namespace giada::v
 {
 class gdBeatsInput : public gdWindow
 {
 public:
-       gdBeatsInput();
+       gdBeatsInput(int beats, int bars);
 
-  private:
+private:
        static void cb_update(Fl_Widget* /*w*/, void* p);
        void        cb_update();
 
-       geInput*  beats;
-       geInput*  bars;
-       geButton* ok;
+       geInput*  m_beats;
+       geInput*  m_bars;
+       geButton* m_ok;
 };
-} // namespace v
-} // namespace giada
+} // namespace giada::v
 
 #endif
index 5c076390fecf95edd74d989bd314ce5c546b3e94..0364ae4a4ef2655a63595c5db215da22bc1ff992 100644 (file)
@@ -25,7 +25,6 @@
  * -------------------------------------------------------------------------- */
 
 #include "bpmInput.h"
-#include "core/clock.h"
 #include "core/conf.h"
 #include "core/const.h"
 #include "core/mixer.h"
index 90c4fa41419ef0451d884de81f115a5630ea0225..eb9701935ae5730e5d6935f7990f2d52471483ef 100644 (file)
@@ -24,7 +24,7 @@
  *
  * -------------------------------------------------------------------------- */
 
-#include "browserBase.h"
+#include "gui/dialogs/browser/browserBase.h"
 #include "core/conf.h"
 #include "core/const.h"
 #include "core/graphics.h"
 #include "utils/fs.h"
 #include "utils/gui.h"
 
-namespace giada
-{
-namespace v
+namespace giada::v
 {
 gdBrowserBase::gdBrowserBase(const std::string& title, const std::string& path,
-    std::function<void(void*)> callback, ID channelId)
-: gdWindow(m::conf::conf.browserX, m::conf::conf.browserY, m::conf::conf.browserW,
-      m::conf::conf.browserH, title.c_str())
+    std::function<void(void*)> callback, ID channelId, m::Conf::Data& c)
+: gdWindow(c.browserX, c.browserY, c.browserW,
+      c.browserH, title.c_str())
 , m_callback(callback)
+, m_conf(c)
 , m_channelId(channelId)
 {
        set_non_modal();
@@ -66,8 +65,8 @@ gdBrowserBase::gdBrowserBase(const std::string& title, const std::string& path,
 
        browser = new geBrowser(8, groupTop->y() + groupTop->h() + 8, w() - 16, h() - 101);
        browser->loadDir(path);
-       if (path == m::conf::conf.browserLastPath)
-               browser->preselect(m::conf::conf.browserPosition, m::conf::conf.browserLastValue);
+       if (path == m_conf.browserLastPath)
+               browser->preselect(m_conf.browserPosition, m_conf.browserLastValue);
 
        Fl_Group* groupButtons = new Fl_Group(8, browser->y() + browser->h() + 8, w() - 16, 20);
        ok                     = new geButton(w() - 88, groupButtons->y(), 80, 20);
@@ -94,13 +93,13 @@ gdBrowserBase::gdBrowserBase(const std::string& title, const std::string& path,
 
 gdBrowserBase::~gdBrowserBase()
 {
-       m::conf::conf.browserX         = x();
-       m::conf::conf.browserY         = y();
-       m::conf::conf.browserW         = w();
-       m::conf::conf.browserH         = h();
-       m::conf::conf.browserPosition  = browser->position();
-       m::conf::conf.browserLastPath  = browser->getCurrentDir();
-       m::conf::conf.browserLastValue = browser->value();
+       m_conf.browserX         = x();
+       m_conf.browserY         = y();
+       m_conf.browserW         = w();
+       m_conf.browserH         = h();
+       m_conf.browserPosition  = browser->position();
+       m_conf.browserLastPath  = browser->getCurrentDir();
+       m_conf.browserLastValue = browser->value();
 }
 
 /* -------------------------------------------------------------------------- */
@@ -176,6 +175,4 @@ void gdBrowserBase::fireCallback() const
 {
        m_callback((void*)this);
 }
-
-} // namespace v
-} // namespace giada
+} // namespace giada::v
\ No newline at end of file
index 4781d732275214f49fb5ac29eab548ad56eb29eb..6244cda6ee591a10f14dbed9190088244b6aa98e 100644 (file)
@@ -27,6 +27,7 @@
 #ifndef GD_BROWSER_BASE_H
 #define GD_BROWSER_BASE_H
 
+#include "core/conf.h"
 #include "core/types.h"
 #include "gui/dialogs/window.h"
 #include <functional>
@@ -38,13 +39,12 @@ class geButton;
 class geInput;
 class geProgress;
 
-namespace giada
-{
-namespace m
+namespace giada::m
 {
 class Channel;
 }
-namespace v
+
+namespace giada::v
 {
 class geBrowser;
 class gdBrowserBase : public gdWindow
@@ -69,9 +69,9 @@ public:
        void showStatusBar();
        void hideStatusBar();
 
-  protected:
+protected:
        gdBrowserBase(const std::string& title, const std::string& path,
-           std::function<void(void*)> f, ID channelId);
+           std::function<void(void*)> f, ID channelId, m::Conf::Data&);
 
        static void cb_up(Fl_Widget* /*w*/, void* p);
        static void cb_close(Fl_Widget* /*w*/, void* p);
@@ -85,7 +85,8 @@ public:
 
        std::function<void(void*)> m_callback;
 
-       ID m_channelId;
+       m::Conf::Data& m_conf;
+       ID             m_channelId;
 
        Fl_Group*   groupTop;
        geCheck*    hiddenFiles;
@@ -96,7 +97,6 @@ public:
        geButton*   updir;
        geProgress* status;
 };
-} // namespace v
-} // namespace giada
+} // namespace giada::v
 
 #endif
index 6b2a1fb1136e2569c015a05211a19735013922e4..46ee90351df3d3b0d38e4975bf50261327926793 100644 (file)
 #include "gui/elems/browser.h"
 #include "utils/fs.h"
 
-namespace giada
-{
-namespace v
+namespace giada::v
 {
 gdBrowserDir::gdBrowserDir(const std::string& title, const std::string& path,
-    std::function<void(void*)> cb)
-: gdBrowserBase(title, path, cb, 0)
+    std::function<void(void*)> cb, m::Conf::Data& conf)
+: gdBrowserBase(title, path, cb, 0, conf)
 {
        where->size(groupTop->w() - updir->w() - 8, 20);
 
@@ -76,6 +74,4 @@ void gdBrowserDir::cb_down()
        browser->loadDir(path);
        where->value(browser->getCurrentDir().c_str());
 }
-
-} // namespace v
-} // namespace giada
+} // namespace giada::v
\ No newline at end of file
index cc140206d0ae694171ff8d7f01c69611dfafb273..daf93fd2ac069d455a529728b485a6be661bff07 100644 (file)
 #ifndef GD_BROWSER_DIR_H
 #define GD_BROWSER_DIR_H
 
+#include "core/conf.h"
 #include "browserBase.h"
 
-namespace giada
-{
-namespace v
+namespace giada::v
 {
 class gdBrowserDir : public gdBrowserBase
 {
 public:
        gdBrowserDir(const std::string& title, const std::string& path,
-           std::function<void(void*)> cb);
+           std::function<void(void*)> cb, m::Conf::Data&);
 
-  private:
+private:
        static void cb_load(Fl_Widget* /*w*/, void* p);
        static void cb_down(Fl_Widget* /*w*/, void* p);
        void        cb_load();
        void        cb_down();
 };
-} // namespace v
-} // namespace giada
+} // namespace giada::v
 
 #endif
index edaa07d0cd14db5346f8dc8ff6fe396798b00b5d..4b1d7e8135ef04ebffef6132aa8347b28deef8a2 100644 (file)
 #include "gui/elems/browser.h"
 #include "utils/fs.h"
 
-namespace giada
-{
-namespace v
+namespace giada::v
 {
 gdBrowserLoad::gdBrowserLoad(const std::string& title, const std::string& path,
-    std::function<void(void*)> cb, ID channelId)
-: gdBrowserBase(title, path, cb, channelId)
+    std::function<void(void*)> cb, ID channelId, m::Conf::Data& conf)
+: gdBrowserBase(title, path, cb, channelId, conf)
 {
        where->size(groupTop->w() - updir->w() - 8, 20);
 
@@ -76,6 +74,4 @@ void gdBrowserLoad::cb_down()
        browser->loadDir(path);
        where->value(browser->getCurrentDir().c_str());
 }
-
-} // namespace v
-} // namespace giada
+} // namespace giada::v
\ No newline at end of file
index 3c6070a29f68d89191d30e0fa9e8aa62c262582a..c01965b65892de4d3209bdacf640d2f658f898a6 100644 (file)
 #define GD_BROWSER_LOAD_H
 
 #include "browserBase.h"
+#include "core/conf.h"
 
-namespace giada
-{
-namespace m
+namespace giada::m
 {
 class Channel;
 }
-namespace v
+
+namespace giada::v
 {
 class gdBrowserLoad : public gdBrowserBase
 {
 public:
        gdBrowserLoad(const std::string& title, const std::string& path,
-           std::function<void(void*)> cb, ID channelId);
+           std::function<void(void*)> cb, ID channelId, m::Conf::Data&);
 
-  private:
+private:
        static void cb_load(Fl_Widget* /*w*/, void* p);
        static void cb_down(Fl_Widget* /*w*/, void* p);
        void        cb_load();
        void        cb_down();
 };
-} // namespace v
-} // namespace giada
+} // namespace giada::v
 
 #endif
index bfcf3fe875467598a69eace082bb37675e083192..eb9e358d3743ed1c8e75becbfb76748f2f80a830 100644 (file)
 #include "gui/elems/browser.h"
 #include "utils/fs.h"
 
-namespace giada
-{
-namespace v
+namespace giada::v
 {
 gdBrowserSave::gdBrowserSave(const std::string& title, const std::string& path,
-    const std::string& name_, std::function<void(void*)> cb, ID channelId)
-: gdBrowserBase(title, path, cb, channelId)
+    const std::string& name_, std::function<void(void*)> cb, ID channelId,
+    m::Conf::Data& conf)
+: gdBrowserBase(title, path, cb, channelId, conf)
 {
        where->size(groupTop->w() - 236, 20);
 
@@ -96,6 +95,4 @@ void gdBrowserSave::cb_save()
 {
        fireCallback();
 }
-
-} // namespace v
-} // namespace giada
+} // namespace giada::v
\ No newline at end of file
index 92f334240c86177562b31dacd8ecbb307eb3c848..c8d7c86e70e15847fadde8613cab5e8390fe82e2 100644 (file)
 #ifndef GD_BROWSER_SAVE_H
 #define GD_BROWSER_SAVE_H
 
+#include "core/conf.h"
 #include "browserBase.h"
 
 class geInput;
 
-namespace giada
-{
-namespace m
+namespace giada::m
 {
 class Channel;
 }
-namespace v
+
+namespace giada::v
 {
 class gdBrowserSave : public gdBrowserBase
 {
 public:
        gdBrowserSave(const std::string& title, const std::string& path,
            const std::string& name, std::function<void(void*)> cb,
-           ID channelId);
+           ID channelId, m::Conf::Data&);
 
        std::string getName() const;
 
-  private:
+private:
        geInput* name;
 
        static void cb_down(Fl_Widget* /*w*/, void* p);
@@ -56,7 +56,6 @@ public:
        void        cb_down();
        void        cb_save();
 };
-} // namespace v
-} // namespace giada
+} // namespace giada::v
 
 #endif
index f1933f1dc1e6f907181b56edc643e25d1ca3266d..fee616fecc00daa041ed7cabe70d8c5be9dfe0d9 100644 (file)
 #include "utils/gui.h"
 #include <FL/Fl_Tabs.H>
 
-namespace giada
+namespace giada::v
 {
-namespace v
-{
-gdConfig::gdConfig(int w, int h)
+gdConfig::gdConfig(int w, int h, m::Conf::Data& conf)
 : gdWindow(u::gui::centerWindowX(w), u::gui::centerWindowY(h), w, h, "Configuration")
 {
        Fl_Tabs* tabs = new Fl_Tabs(8, 8, w - 16, h - 44);
@@ -51,8 +49,8 @@ gdConfig::gdConfig(int w, int h)
 
        tabAudio     = new geTabAudio(tabs->x() + 10, tabs->y() + 20, tabs->w() - 20, tabs->h() - 40);
        tabMidi      = new geTabMidi(tabs->x() + 10, tabs->y() + 20, tabs->w() - 20, tabs->h() - 40);
-       tabBehaviors = new geTabBehaviors(tabs->x() + 10, tabs->y() + 20, tabs->w() - 20, tabs->h() - 40);
-       tabMisc      = new geTabMisc(tabs->x() + 10, tabs->y() + 20, tabs->w() - 20, tabs->h() - 40);
+       tabBehaviors = new geTabBehaviors(tabs->x() + 10, tabs->y() + 20, tabs->w() - 20, tabs->h() - 40, conf);
+       tabMisc      = new geTabMisc(tabs->x() + 10, tabs->y() + 20, tabs->w() - 20);
 #ifdef WITH_VST
        tabPlugins = new geTabPlugins(tabs->x() + 10, tabs->y() + 20, tabs->w() - 20, tabs->h() - 40);
 #endif
@@ -97,17 +95,4 @@ void gdConfig::cb_cancel()
 {
        do_callback();
 }
-
-/* -------------------------------------------------------------------------- */
-
-#ifdef WITH_VST
-
-void gdConfig::refreshVstPath()
-{
-       tabPlugins->refreshVstPath();
-}
-
-#endif
-
-} // namespace v
-} // namespace giada
\ No newline at end of file
+} // namespace giada::v
index 041016245bea99e918d027c735d232bf178cd9e2..de259114b7468afa381724d2e8db216eaf43246f 100644 (file)
@@ -27,6 +27,7 @@
 #ifndef GD_CONFIG_H
 #define GD_CONFIG_H
 
+#include "core/conf.h"
 #include "window.h"
 
 class geButton;
@@ -34,9 +35,7 @@ class geCheck;
 class geInput;
 class geBox;
 
-namespace giada
-{
-namespace v
+namespace giada::v
 {
 class geChoice;
 class geTabAudio;
@@ -49,11 +48,7 @@ class geTabPlugins;
 class gdConfig : public gdWindow
 {
 public:
-       gdConfig(int w, int h);
-
-#ifdef WITH_VST
-       void refreshVstPath();
-#endif
+       gdConfig(int w, int h, m::Conf::Data&);
 
        geTabAudio*     tabAudio;
        geTabBehaviors* tabBehaviors;
@@ -65,13 +60,12 @@ public:
        geButton* save;
        geButton* cancel;
 
-  private:
+private:
        static void cb_save_config(Fl_Widget* /*w*/, void* p);
        static void cb_cancel(Fl_Widget* /*w*/, void* p);
        void        cb_save_config();
        void        cb_cancel();
 };
-} // namespace v
-} // namespace giada
+} // namespace giada::v
 
 #endif
index a4c5feb956e6490cee21faee52c3b68492047522..bd66e8f9f795acb376fc0933e95565d01bd784f5 100644 (file)
 class geBox;
 class geButton;
 
-namespace giada
-{
-namespace c
-{
-namespace channel
+namespace giada::c::channel
 {
 struct Data;
 }
-} // namespace c
-namespace v
+
+namespace giada::v
 {
 class gdKeyGrabber : public gdWindow
 {
@@ -52,7 +48,7 @@ public:
        int  handle(int e) override;
        void rebuild() override;
 
-  private:
+private:
        static void cb_clear(Fl_Widget* /*w*/, void* p);
        static void cb_cancel(Fl_Widget* /*w*/, void* p);
        void        cb_clear();
@@ -67,7 +63,6 @@ public:
        geButton* m_clear;
        geButton* m_cancel;
 };
-} // namespace v
-} // namespace giada
+} // namespace giada::v
 
 #endif
index f27d2ca0c062be06255cd30662b79db540bdddf3..ef5072c69ed332cfd205f9f327e66cbaa19b67e9 100644 (file)
@@ -25,7 +25,6 @@
  * -------------------------------------------------------------------------- */
 
 #include "mainWindow.h"
-#include "core/clock.h"
 #include "core/conf.h"
 #include "core/const.h"
 #include "core/init.h"
 
 namespace giada::v
 {
-gdMainWindow::gdMainWindow(int W, int H, const char* title, int argc, char** argv)
+gdMainWindow::gdMainWindow(int W, int H, const char* title, int argc, char** argv, m::Conf::Data& c)
 : gdWindow(W, H, title)
+, m_conf(c)
 {
+
        Fl::visible_focus(0);
 
        Fl::background(25, 25, 25); // TODO use G_COLOR_GREY_1
@@ -61,7 +62,7 @@ gdMainWindow::gdMainWindow(int W, int H, const char* title, int argc, char** arg
        Fl_Tooltip::color(G_COLOR_GREY_1);
        Fl_Tooltip::textcolor(G_COLOR_LIGHT_2);
        Fl_Tooltip::size(G_GUI_FONT_SIZE_BASE);
-       Fl_Tooltip::enable(m::conf::conf.showTooltips);
+       Fl_Tooltip::enable(m_conf.showTooltips);
 
        size_range(G_MIN_GUI_WIDTH, G_MIN_GUI_HEIGHT);
 
@@ -118,10 +119,10 @@ gdMainWindow::gdMainWindow(int W, int H, const char* title, int argc, char** arg
 
 gdMainWindow::~gdMainWindow()
 {
-       m::conf::conf.mainWindowX = x();
-       m::conf::conf.mainWindowY = y();
-       m::conf::conf.mainWindowW = w();
-       m::conf::conf.mainWindowH = h();
+       m_conf.mainWindowX = x();
+       m_conf.mainWindowY = y();
+       m_conf.mainWindowW = w();
+       m_conf.mainWindowH = h();
 }
 
 /* -------------------------------------------------------------------------- */
index a99689eacb7d28f63ee1d2a3225c657ceba733a4..4cb9c26ff60e6cd0dbde7cbce59315f838de363b 100644 (file)
@@ -28,6 +28,7 @@
 #define GD_MAINWINDOW_H
 
 #include "window.h"
+#include "core/conf.h"
 
 namespace giada::v
 {
@@ -40,7 +41,7 @@ class geMainTimer;
 class gdMainWindow : public gdWindow
 {
 public:
-       gdMainWindow(int w, int h, const char* title, int argc, char** argv);
+       gdMainWindow(int w, int h, const char* title, int argc, char** argv, m::Conf::Data&);
        ~gdMainWindow();
 
        void refresh() override;
@@ -57,6 +58,9 @@ public:
        geMainIO*        mainIO;
        geMainTimer*     mainTimer;
        geMainTransport* mainTransport;
+
+private:
+       m::Conf::Data& m_conf;
 };
 } // namespace giada::v
 
index f5cce51608db171616c40d027db42ec75d8e0641..f2e4a6d67505038692ef755c29794dc3c57feafd 100644 (file)
 #include "core/conf.h"
 #include "glue/io.h"
 
-namespace giada
+namespace giada::v
 {
-namespace v
-{
-gdMidiInputBase::gdMidiInputBase(int x, int y, int w, int h, const char* title)
+gdMidiInputBase::gdMidiInputBase(int x, int y, int w, int h, const char* title,
+    m::Conf::Data& c)
 : gdWindow(x, y, w, h, title)
+, m_conf(c)
 {
 }
 
@@ -43,10 +43,10 @@ gdMidiInputBase::~gdMidiInputBase()
 {
        c::io::stopMidiLearn();
 
-       m::conf::conf.midiInputX = x();
-       m::conf::conf.midiInputY = y();
-       m::conf::conf.midiInputW = w();
-       m::conf::conf.midiInputH = h();
+       m_conf.midiInputX = x();
+       m_conf.midiInputY = y();
+       m_conf.midiInputW = w();
+       m_conf.midiInputH = h();
 }
 
 /* -------------------------------------------------------------------------- */
@@ -59,6 +59,4 @@ void gdMidiInputBase::cb_close()
 {
        do_callback();
 }
-
-} // namespace v
-} // namespace giada
+} // namespace giada::v
\ No newline at end of file
index 78dd91fb0b3f8b56dca2ada3dec11cdd8099d859..85463548aec2597d0297633b8fce8bd44ffe4e00 100644 (file)
 #ifndef GD_MIDI_INPUT_BASE_H
 #define GD_MIDI_INPUT_BASE_H
 
+#include "core/conf.h"
 #include "gui/dialogs/window.h"
 #include "gui/elems/midiIO/midiLearner.h"
 
 class geButton;
 class geCheck;
 
-namespace giada
-{
-namespace v
+namespace giada::v
 {
 class geChoice;
 class gdMidiInputBase : public gdWindow
@@ -43,17 +42,18 @@ class gdMidiInputBase : public gdWindow
 public:
        virtual ~gdMidiInputBase();
 
-  protected:
-       gdMidiInputBase(int x, int y, int w, int h, const char* title = "");
+protected:
+       gdMidiInputBase(int x, int y, int w, int h, const char* title, m::Conf::Data&);
 
        static void cb_close(Fl_Widget* /*w*/, void* p);
        void        cb_close();
 
+       m::Conf::Data& m_conf;
+
        geButton* m_ok;
        geCheck*  m_enable;
        geChoice* m_channel;
 };
-} // namespace v
-} // namespace giada
+} // namespace giada::v
 
 #endif
index 713070562dbb0a5930a6526dbc1265697736e916..84621f9b30e581965516b44f05b698bac5ed2050 100644 (file)
@@ -44,9 +44,7 @@
 #include "midiInputChannel.h"
 #include "utils/string.h"
 
-namespace giada
-{
-namespace v
+namespace giada::v
 {
 geChannelLearnerPack::geChannelLearnerPack(int x, int y, const c::io::Channel_InputData& channel)
 : geMidiLearnerPack(x, y, "Channel")
@@ -114,11 +112,8 @@ void gePluginLearnerPack::update(const c::io::PluginData& d, bool enabled)
 /* -------------------------------------------------------------------------- */
 /* -------------------------------------------------------------------------- */
 
-gdMidiInputChannel::gdMidiInputChannel(ID channelId)
-: gdMidiInputBase(m::conf::conf.midiInputX,
-      m::conf::conf.midiInputY,
-      m::conf::conf.midiInputW,
-      m::conf::conf.midiInputH)
+gdMidiInputChannel::gdMidiInputChannel(ID channelId, m::Conf::Data& c)
+: gdMidiInputBase(c.midiInputX, c.midiInputY, c.midiInputW, c.midiInputH, "", c)
 , m_channelId(channelId)
 , m_data(c::io::channel_getInputData(channelId))
 {
@@ -257,5 +252,4 @@ void gdMidiInputChannel::cb_setChannel()
        c::io::channel_setMidiInputFilter(m_data.channelId,
            m_channel->value() == 0 ? -1 : m_channel->value() - 1);
 }
-} // namespace v
-} // namespace giada
+} // namespace giada::v
\ No newline at end of file
index 88c883299bf2bb25902a0177fd82016e6efe370c..9266febb2988f0fd16ee76ac7e4a0f0d27e1b6dc 100644 (file)
 #ifndef GD_MIDI_INPUT_CHANNEL_H
 #define GD_MIDI_INPUT_CHANNEL_H
 
+#include "core/conf.h"
 #include "glue/io.h"
 #include "gui/elems/midiIO/midiLearnerPack.h"
 #include "midiInputBase.h"
 
 class geCheck;
 
-namespace giada
-{
-namespace v
+namespace giada::v
 {
 class geChoice;
 class geScrollPack;
@@ -68,11 +67,11 @@ public:
 class gdMidiInputChannel : public gdMidiInputBase
 {
 public:
-       gdMidiInputChannel(ID channelId);
+       gdMidiInputChannel(ID channelId, m::Conf::Data&);
 
        void rebuild() override;
 
-  private:
+private:
        static void cb_enable(Fl_Widget* /*w*/, void* p);
        static void cb_setChannel(Fl_Widget* /*w*/, void* p);
        static void cb_veloAsVol(Fl_Widget* /*w*/, void* p);
@@ -87,7 +86,6 @@ public:
        geScrollPack* m_container;
        geCheck*      m_veloAsVol;
 };
-} // namespace v
-} // namespace giada
+} // namespace giada::v
 
 #endif
index 33802931308a09610c7861d7c123323136d2fca3..e2bf9b0f278d2777f6a6f5c8cd9c042bb26c18bc 100644 (file)
@@ -36,9 +36,7 @@
 #include "utils/gui.h"
 #include <FL/Fl_Pack.H>
 
-namespace giada
-{
-namespace v
+namespace giada::v
 {
 geMasterLearnerPack::geMasterLearnerPack(int x, int y)
 : geMidiLearnerPack(x, y)
@@ -77,8 +75,8 @@ void geMasterLearnerPack::update(const c::io::Master_InputData& d)
 /* -------------------------------------------------------------------------- */
 /* -------------------------------------------------------------------------- */
 
-gdMidiInputMaster::gdMidiInputMaster()
-: gdMidiInputBase(m::conf::conf.midiInputX, m::conf::conf.midiInputY, 300, 284, "MIDI Input Setup (global)")
+gdMidiInputMaster::gdMidiInputMaster(m::Conf::Data& c)
+: gdMidiInputBase(c.midiInputX, c.midiInputY, 300, 284, "MIDI Input Setup (global)", c)
 {
        end();
 
@@ -156,5 +154,4 @@ void gdMidiInputMaster::cb_setChannel()
 {
        c::io::master_setMidiFilter(m_channel->value() == 0 ? -1 : m_channel->value() - 1);
 }
-} // namespace v
-} // namespace giada
+} // namespace giada::v
\ No newline at end of file
index d3031898796203078c528b91d894cde4b9c474cd..a4013b0988eb3b822acb0da06b33108300b94553 100644 (file)
 #ifndef GD_MIDI_INPUT_MASTER_H
 #define GD_MIDI_INPUT_MASTER_H
 
+#include "core/conf.h"
 #include "glue/io.h"
 #include "gui/elems/midiIO/midiLearnerPack.h"
 #include "midiInputBase.h"
 
 class geCheck;
 
-namespace giada
-{
-namespace v
+namespace giada::v
 {
 class geChoice;
 class geMasterLearnerPack : public geMidiLearnerPack
@@ -51,11 +50,11 @@ public:
 class gdMidiInputMaster : public gdMidiInputBase
 {
 public:
-       gdMidiInputMaster();
+       gdMidiInputMaster(m::Conf::Data&);
 
        void rebuild() override;
 
-  private:
+private:
        static void cb_enable(Fl_Widget* /*w*/, void* p);
        static void cb_setChannel(Fl_Widget* /*w*/, void* p);
        void        cb_enable();
@@ -65,7 +64,6 @@ public:
 
        geMasterLearnerPack* m_learners;
 };
-} // namespace v
-} // namespace giada
+} // namespace giada::v
 
 #endif
index e557b77ffb9af5ad1e40bc3ad624dd25547098b9..c0d94b3cc3d8dc780ad5f351e41b1d699dd85c85 100644 (file)
@@ -28,8 +28,6 @@
 
 #include "pluginChooser.h"
 #include "core/conf.h"
-#include "core/plugins/pluginHost.h"
-#include "core/plugins/pluginManager.h"
 #include "glue/plugin.h"
 #include "gui/elems/basics/box.h"
 #include "gui/elems/basics/button.h"
 #include "gui/elems/plugin/pluginBrowser.h"
 #include "utils/gui.h"
 
-namespace giada
+namespace giada::v
 {
-namespace v
-{
-gdPluginChooser::gdPluginChooser(int X, int Y, int W, int H, ID channelId)
+gdPluginChooser::gdPluginChooser(int X, int Y, int W, int H, ID channelId, m::Conf::Data& c)
 : gdWindow(X, Y, W, H, "Available plugins")
+, m_conf(c)
 , m_channelId(channelId)
 {
        /* top area */
@@ -69,7 +66,7 @@ gdPluginChooser::gdPluginChooser(int X, int Y, int W, int H, ID channelId)
        sortMethod->add("Category");
        sortMethod->add("Manufacturer");
        sortMethod->callback(cb_sort, (void*)this);
-       sortMethod->value(m::conf::conf.pluginSortMethod);
+       sortMethod->value(m_conf.pluginSortMethod);
 
        addBtn->callback(cb_add, (void*)this);
        addBtn->shortcut(FL_Enter);
@@ -84,11 +81,11 @@ gdPluginChooser::gdPluginChooser(int X, int Y, int W, int H, ID channelId)
 
 gdPluginChooser::~gdPluginChooser()
 {
-       m::conf::conf.pluginChooserX   = x();
-       m::conf::conf.pluginChooserY   = y();
-       m::conf::conf.pluginChooserW   = w();
-       m::conf::conf.pluginChooserH   = h();
-       m::conf::conf.pluginSortMethod = sortMethod->value();
+       m_conf.pluginChooserX   = x();
+       m_conf.pluginChooserY   = y();
+       m_conf.pluginChooserW   = w();
+       m_conf.pluginChooserH   = h();
+       m_conf.pluginSortMethod = sortMethod->value();
 }
 
 /* -------------------------------------------------------------------------- */
@@ -108,7 +105,7 @@ void gdPluginChooser::cb_close()
 
 void gdPluginChooser::cb_sort()
 {
-       m::pluginManager::sortPlugins(static_cast<m::pluginManager::SortMethod>(sortMethod->value()));
+       c::plugin::sortPlugins(static_cast<m::PluginManager::SortMethod>(sortMethod->value()));
        browser->refresh();
 }
 
@@ -122,7 +119,6 @@ void gdPluginChooser::cb_add()
        c::plugin::addPlugin(pluginIndex, m_channelId);
        do_callback();
 }
-} // namespace v
-} // namespace giada
+} // namespace giada::v
 
 #endif // #ifdef WITH_VST
index 201035e2481ffbe38e4889ddbbec5aeb8bbc5ef3..a5ab32325d0aa4d855f0e3f1b9c2d48bc8f7d5a5 100644 (file)
@@ -29,6 +29,7 @@
 #ifndef GD_PLUGIN_CHOOSER_H
 #define GD_PLUGIN_CHOOSER_H
 
+#include "core/conf.h"
 #include "core/types.h"
 #include "window.h"
 #include <FL/Fl.H>
@@ -37,9 +38,7 @@
 class geButton;
 class geButton;
 
-namespace giada
-{
-namespace v
+namespace giada::v
 {
 class geChoice;
 class gePluginBrowser;
@@ -47,10 +46,10 @@ class gePluginBrowser;
 class gdPluginChooser : public gdWindow
 {
 public:
-       gdPluginChooser(int x, int y, int w, int h, ID channelId);
+       gdPluginChooser(int x, int y, int w, int h, ID channelId, m::Conf::Data&);
        ~gdPluginChooser();
 
-  private:
+private:
        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);
@@ -58,6 +57,8 @@ public:
        void        cb_add();
        void        cb_sort();
 
+       m::Conf::Data& m_conf;
+
        geChoice*        sortMethod;
        geButton*        addBtn;
        geButton*        cancelBtn;
@@ -65,8 +66,7 @@ public:
 
        ID m_channelId;
 };
-} // namespace v
-} // namespace giada
+} // namespace giada::v
 
 #endif
 
index 514d9e18cc689957215535d691d13ed3914fae4e..12e73c4045ffbea6c0c712ff20df227139ac17d4 100644 (file)
 
 #ifdef WITH_VST
 
-#include "pluginList.h"
+#include "gui/dialogs/pluginList.h"
 #include "core/conf.h"
 #include "core/const.h"
+#include "glue/layout.h"
 #include "gui/elems/basics/boxtypes.h"
 #include "gui/elems/basics/button.h"
 #include "gui/elems/basics/liquidScroll.h"
 #include "gui/elems/mainWindow/keyboard/channel.h"
 #include "gui/elems/mainWindow/mainIO.h"
 #include "gui/elems/plugin/pluginElement.h"
-#include "mainWindow.h"
-#include "pluginChooser.h"
 #include "utils/gui.h"
 #include "utils/string.h"
 #include <cassert>
 #include <string>
 
-extern giada::v::gdMainWindow* G_MainWin;
-
-namespace giada
-{
-namespace v
+namespace giada::v
 {
-gdPluginList::gdPluginList(ID channelId)
-: gdWindow(m::conf::conf.pluginListX, m::conf::conf.pluginListY, 468, 204)
+gdPluginList::gdPluginList(ID channelId, m::Conf::Data& c)
+: gdWindow(c.pluginListX, c.pluginListY, 468, 204)
+, m_conf(c)
 , m_channelId(channelId)
 {
        end();
 
        list = new geLiquidScroll(G_GUI_OUTER_MARGIN, G_GUI_OUTER_MARGIN,
-           w() - (G_GUI_OUTER_MARGIN * 2), h() - (G_GUI_OUTER_MARGIN * 2));
+           w() - (G_GUI_OUTER_MARGIN * 2), h() - (G_GUI_OUTER_MARGIN * 2),
+           Direction::VERTICAL);
        list->end();
        add(list);
        resizable(list);
@@ -71,8 +68,8 @@ gdPluginList::gdPluginList(ID channelId)
 
 gdPluginList::~gdPluginList()
 {
-       m::conf::conf.pluginListX = x();
-       m::conf::conf.pluginListY = y();
+       m_conf.pluginListX = x();
+       m_conf.pluginListY = y();
 }
 
 /* -------------------------------------------------------------------------- */
@@ -85,9 +82,9 @@ void gdPluginList::rebuild()
 {
        m_plugins = c::plugin::getPlugins(m_channelId);
 
-       if (m_plugins.channelId == m::mixer::MASTER_OUT_CHANNEL_ID)
+       if (m_plugins.channelId == m::Mixer::MASTER_OUT_CHANNEL_ID)
                label("Master Out Plug-ins");
-       else if (m_plugins.channelId == m::mixer::MASTER_IN_CHANNEL_ID)
+       else if (m_plugins.channelId == m::Mixer::MASTER_IN_CHANNEL_ID)
                label("Master In Plug-ins");
        else
        {
@@ -112,11 +109,7 @@ void gdPluginList::rebuild()
 
 void gdPluginList::cb_addPlugin()
 {
-       int wx = m::conf::conf.pluginChooserX;
-       int wy = m::conf::conf.pluginChooserY;
-       int ww = m::conf::conf.pluginChooserW;
-       int wh = m::conf::conf.pluginChooserH;
-       u::gui::openSubWindow(G_MainWin, new v::gdPluginChooser(wx, wy, ww, wh, m_plugins.channelId), WID_FX_CHOOSER);
+       c::layout::openPluginChooser(m_plugins.channelId);
 }
 
 /* -------------------------------------------------------------------------- */
@@ -138,7 +131,6 @@ const gePluginElement& gdPluginList::getPrevElement(const gePluginElement& currE
                prev = 0;
        return *static_cast<gePluginElement*>(list->child(prev));
 }
-} // namespace v
-} // namespace giada
+} // namespace giada::v
 
 #endif // #ifdef WITH_VST
index 0e2d224d5f81936ef9cba06be09ac7aba60cf6ce..a1f4c10b6eeb23a039f0d8cbda5a915d32b74de6 100644 (file)
 #ifndef GD_PLUGINLIST_H
 #define GD_PLUGINLIST_H
 
+#include "core/conf.h"
 #include "glue/plugin.h"
 #include "window.h"
 
-class geLiquidScroll;
 class geButton;
 
-namespace giada
-{
-namespace v
+namespace giada::v
 {
+class geLiquidScroll;
 class gePluginElement;
 class gdPluginList : public gdWindow
 {
 public:
-       gdPluginList(ID channelId);
+       gdPluginList(ID channelId, m::Conf::Data&);
        ~gdPluginList();
 
        void rebuild() override;
@@ -51,18 +50,19 @@ public:
        const gePluginElement& getNextElement(const gePluginElement& curr) const;
        const gePluginElement& getPrevElement(const gePluginElement& curr) const;
 
-  private:
+private:
        static void cb_addPlugin(Fl_Widget* /*w*/, void* p);
        void        cb_addPlugin();
 
+       m::Conf::Data& m_conf;
+
        geButton*       addPlugin;
        geLiquidScroll* list;
 
        ID                 m_channelId;
        c::plugin::Plugins m_plugins;
 };
-} // namespace v
-} // namespace giada
+} // namespace giada::v
 
 #endif
 
index 31ea919c5713ed78c11ae7940e5a8aadcd0cf78d..a7f5f0717452cc5bf434f0ea262694a9d36184b7 100644 (file)
@@ -43,7 +43,8 @@ gdPluginWindow::gdPluginWindow(const c::plugin::Plugin& plugin)
        set_non_modal();
 
        m_list = new geLiquidScroll(G_GUI_OUTER_MARGIN, G_GUI_OUTER_MARGIN,
-           w() - (G_GUI_OUTER_MARGIN * 2), h() - (G_GUI_OUTER_MARGIN * 2));
+           w() - (G_GUI_OUTER_MARGIN * 2), h() - (G_GUI_OUTER_MARGIN * 2),
+           Direction::VERTICAL);
 
        m_list->type(Fl_Scroll::VERTICAL_ALWAYS);
        m_list->begin();
index babe30e0cf531c8d0c34f8c9659c22bd6abab21c..7613e4d05d99e57ece51c0fab6d59209451ce845 100644 (file)
@@ -33,7 +33,6 @@
 
 class geBox;
 class geSlider;
-class geLiquidScroll;
 
 namespace giada::c::plugin
 {
@@ -45,6 +44,7 @@ class Plugin;
 }
 namespace giada::v
 {
+class geLiquidScroll;
 class gdPluginWindow : public gdWindow
 {
 public:
@@ -52,7 +52,7 @@ public:
 
        void updateParameters(bool changeSlider = false);
 
-  private:
+private:
        const c::plugin::Plugin& m_plugin;
 
        geLiquidScroll* m_list;
index 03e417006c9000732a9d2cdbc339721996f27e11..3b21d6a16d24ad3f1632ed373a64e70e81316059 100644 (file)
@@ -26,7 +26,7 @@
 
 #ifdef WITH_VST
 
-#include "pluginWindowGUI.h"
+#include "gui/dialogs/pluginWindowGUI.h"
 #include "core/const.h"
 #include "glue/plugin.h"
 #include "utils/gui.h"
@@ -36,9 +36,7 @@
 #import "utils/cocoa.h" // objective-c
 #endif
 
-namespace giada
-{
-namespace v
+namespace giada::v
 {
 gdPluginWindowGUI::gdPluginWindowGUI(c::plugin::Plugin& p)
 #ifdef G_OS_MAC
@@ -47,109 +45,70 @@ gdPluginWindowGUI::gdPluginWindowGUI(c::plugin::Plugin& p)
 : gdWindow(320, 200)
 #endif
 , m_plugin(p)
-, m_ui(nullptr)
 {
-       show();
-
-#if defined(G_OS_LINUX) || defined(G_OS_MAC) || defined(G_OS_FREEBSD)
-
-       /*  Fl_Window::show() is not guaranteed to show and draw the window on all 
-       platforms immediately. Instead this is done in the background; particularly on 
-       X11 it will take a few messages (client server roundtrips) to display the 
-       window. Usually this small delay doesn't matter, but in some cases you may 
-       want to have the window instantiated and displayed synchronously. Currently 
-       (as of FLTK 1.3.4) this method has an effect on X11 and Mac OS. 
-
-       http://www.fltk.org/doc-1.3/classFl__Window.html#aafbec14ca8ff8abdaff77a35ebb23dd8 */
+       /* Make sure to wait_for_expose() before opening the editor: the window must
+       be exposed and visible first. Don't fuck with multithreading! */
 
+       copy_label(m_plugin.name.c_str());
+       show();
        wait_for_expose();
+       openEditor();
        Fl::flush();
-
-#endif
-
-       u::log::print("[gdPluginWindowGUI] opening GUI, this=%p, xid=%p\n",
-           (void*)this, (void*)fl_xid(this));
-
-#ifdef G_OS_MAC
-
-       void* cocoaWindow = (void*)fl_xid(this);
-       openEditor(cocoa_getViewFromWindow(cocoaWindow));
-
-#else
-
-       openEditor((void*)fl_xid(this));
-
-       int pluginW = m_ui->getWidth();
-       int pluginH = m_ui->getHeight();
-
-       resize((Fl::w() - pluginW) / 2, (Fl::h() - pluginH) / 2, pluginW, pluginH);
-
-       m_plugin.setResizeCallback([this](int w, int h) {
-               resize(x(), y(), w, h);
-       });
-
-#endif
-
-#ifdef G_OS_LINUX
-       Fl::add_timeout(G_GUI_PLUGIN_RATE, cb_refresh, (void*)this);
-#endif
-
-       copy_label(m_plugin.name.c_str());
 }
 
 /* -------------------------------------------------------------------------- */
 
 gdPluginWindowGUI::~gdPluginWindowGUI()
 {
-       cb_close();
-}
-
-/* -------------------------------------------------------------------------- */
-
-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()
-{
-#ifdef G_OS_LINUX
-       Fl::remove_timeout(cb_refresh);
-#endif
+       c::plugin::stopDispatchLoop();
        closeEditor();
        u::log::print("[gdPluginWindowGUI::__cb_close] GUI closed, this=%p\n", (void*)this);
 }
 
 /* -------------------------------------------------------------------------- */
 
-void gdPluginWindowGUI::cb_refresh()
+void gdPluginWindowGUI::openEditor()
 {
-       m::pluginHost::runDispatchLoop();
-       Fl::repeat_timeout(G_GUI_PLUGIN_RATE, cb_refresh, (void*)this);
-}
-
-/* -------------------------------------------------------------------------- */
+       u::log::print("[gdPluginWindowGUI] Opening editor, this=%p, xid=%p\n",
+           this, reinterpret_cast<void*>(fl_xid(this)));
 
-void gdPluginWindowGUI::openEditor(void* parent)
-{
-       m_ui = m_plugin.createEditor();
-       if (m_ui == nullptr)
+       m_editor.reset(m_plugin.createEditor());
+       if (m_editor == nullptr)
        {
                u::log::print("[gdPluginWindowGUI::openEditor] unable to create editor!\n");
                return;
        }
-       m_ui->setOpaque(true);
-       m_ui->addToDesktop(0, parent);
+       m_editor->setOpaque(true);
+
+#ifdef G_OS_MAC
+
+       void* cocoaWindow = (void*)fl_xid(this);
+       m_editor->addToDesktop(0, cocoa_getViewFromWindow(cocoaWindow));
+
+#else
+
+       m_editor->addToDesktop(0, reinterpret_cast<void*>(fl_xid(this)));
+
+#endif
+
+       const int pluginW = m_editor->getWidth();
+       const int pluginH = m_editor->getHeight();
+
+       resize((Fl::w() - pluginW) / 2, (Fl::h() - pluginH) / 2, pluginW, pluginH);
+
+       m_plugin.setResizeCallback([this](int w, int h) {
+               resize(x(), y(), w, h);
+       });
+
+       c::plugin::startDispatchLoop();
 }
 
 /* -------------------------------------------------------------------------- */
 
 void gdPluginWindowGUI::closeEditor()
 {
-       delete m_ui;
-       m_ui = nullptr;
+       m_editor.reset();
 }
-} // namespace v
-} // namespace giada
+} // namespace giada::v
 
 #endif // #ifdef WITH_VST
index a907f282f9a1e93671210423cbcb795946b72736..daf5127bdabd1634b4bb56b473c71ee1883f2b40 100644 (file)
 #include <FL/Fl.H>
 #include <FL/Fl_Window.H>
 
-namespace giada
-{
-namespace c
-{
-namespace plugin
+namespace giada::c::plugin
 {
 struct Plugin;
 }
-} // namespace c
-namespace v
+
+namespace giada::v
 {
 class gdPluginWindowGUI : public gdWindow
 {
@@ -54,22 +50,15 @@ public:
        gdPluginWindowGUI(c::plugin::Plugin&);
        ~gdPluginWindowGUI();
 
-  private:
-       static void cb_close(Fl_Widget* /*w*/, void* p);
-       static void cb_refresh(void* data);
-       void        cb_close();
-       void        cb_refresh();
-
-       void openEditor(void* parent);
+       void openEditor();
        void closeEditor();
 
-       c::plugin::Plugin& m_plugin;
-
-       juce::AudioProcessorEditor* m_ui;
+private:
+       c::plugin::Plugin&                          m_plugin;
+       std::unique_ptr<juce::AudioProcessorEditor> m_editor;
 };
-} // namespace v
-} // namespace giada
+} // namespace giada::v
 
-#endif // include guard
+#endif
 
 #endif // #ifdef WITH_VST
index a77994d3f5d666d4d9acde4201d2e5da8724e83f..1fcb0fc56a8da5629c824ba97300c69e31be01ff 100644 (file)
 #include <cassert>
 #include <cmath>
 
+#ifdef G_OS_WINDOWS
+#undef IN
+#undef OUT
+#endif
+
 namespace giada::v
 {
-gdSampleEditor::gdSampleEditor(ID channelId)
-: gdWindow(m::conf::conf.sampleEditorX, m::conf::conf.sampleEditorY,
-      m::conf::conf.sampleEditorW, m::conf::conf.sampleEditorH)
+gdSampleEditor::gdSampleEditor(ID channelId, m::Conf::Data& c)
+: gdWindow(c.sampleEditorX, c.sampleEditorY, c.sampleEditorW, c.sampleEditorH)
 , m_channelId(channelId)
+, m_conf(c)
 {
        end();
 
        gePack* upperBar = createUpperBar();
 
        waveTools = new geWaveTools(G_GUI_OUTER_MARGIN, upperBar->y() + upperBar->h() + G_GUI_OUTER_MARGIN,
-           w() - 16, h() - 168);
+           w() - 16, h() - 168, m_conf.sampleEditorGridOn, m_conf.sampleEditorGridVal);
 
        gePack* bottomBar = createBottomBar(G_GUI_OUTER_MARGIN, waveTools->y() + waveTools->h() + G_GUI_OUTER_MARGIN,
            h() - waveTools->h() - upperBar->h() - 32);
@@ -94,12 +99,12 @@ gdSampleEditor::gdSampleEditor(ID channelId)
 
 gdSampleEditor::~gdSampleEditor()
 {
-       m::conf::conf.sampleEditorX       = x();
-       m::conf::conf.sampleEditorY       = y();
-       m::conf::conf.sampleEditorW       = w();
-       m::conf::conf.sampleEditorH       = h();
-       m::conf::conf.sampleEditorGridVal = atoi(grid->text());
-       m::conf::conf.sampleEditorGridOn  = snap->value();
+       m_conf.sampleEditorX       = x();
+       m_conf.sampleEditorY       = y();
+       m_conf.sampleEditorW       = w();
+       m_conf.sampleEditorH       = h();
+       m_conf.sampleEditorGridVal = atoi(grid->text());
+       m_conf.sampleEditorGridOn  = snap->value();
 
        c::sampleEditor::stopPreview();
        c::sampleEditor::cleanupPreview();
@@ -158,13 +163,13 @@ gePack* gdSampleEditor::createUpperBar()
        grid->add("64");
        grid->copy_tooltip("Grid frequency");
 
-       if (m::conf::conf.sampleEditorGridVal == 0)
+       if (m_conf.sampleEditorGridVal == 0)
                grid->value(0);
        else
-               grid->value(grid->find_item(u::string::iToString(m::conf::conf.sampleEditorGridVal).c_str()));
+               grid->value(grid->find_item(u::string::iToString(m_conf.sampleEditorGridVal).c_str()));
        grid->callback(cb_changeGrid, (void*)this);
 
-       snap->value(m::conf::conf.sampleEditorGridOn);
+       snap->value(m_conf.sampleEditorGridOn);
        snap->copy_tooltip("Snap to grid");
        snap->callback(cb_enableSnap, (void*)this);
 
index 65e78ce3df0f5d25a5e60c7b056e742f992a25a9..23f0c014be84a9cf906cd5647df6c406491820b3 100644 (file)
@@ -28,6 +28,7 @@
 #define GD_EDITOR_H
 
 #include "core/types.h"
+#include "core/conf.h"
 #include "glue/sampleEditor.h"
 #include "window.h"
 
@@ -41,6 +42,7 @@ namespace giada::m
 {
 class Wave;
 }
+
 namespace giada::v
 {
 class geChoice;
@@ -58,7 +60,7 @@ class gdSampleEditor : public gdWindow
        friend class geWaveform;
 
 public:
-       gdSampleEditor(ID channelId);
+       gdSampleEditor(ID channelId, m::Conf::Data&);
        ~gdSampleEditor();
 
        void rebuild() override;
@@ -86,7 +88,7 @@ public:
        geCheck*        loop;
        geBox*          info;
 
-  private:
+private:
        gePack*  createUpperBar();
        gePack*  createBottomBar(int x, int y, int h);
        geGroup* createPreviewBox(int x, int y, int h);
@@ -112,6 +114,7 @@ public:
        ID m_channelId;
 
        c::sampleEditor::Data m_data;
+       m::Conf::Data&        m_conf;
 };
 } // namespace giada::v
 
index 5159f57b06ba08ec4f7b855c90b0cd9588ac516e..adec425371fe25d97c00edfcb0b342941c7531c9 100644 (file)
@@ -27,9 +27,7 @@
 #include "window.h"
 #include "utils/log.h"
 
-namespace giada
-{
-namespace v
+namespace giada::v
 {
 void cb_window_closer(Fl_Widget* /*v*/, void* p)
 {
@@ -168,5 +166,4 @@ gdWindow* gdWindow::getChild(int wid)
                        return subWindows.at(j);
        return nullptr;
 }
-} // namespace v
-} // namespace giada
\ No newline at end of file
+} // namespace giada::v
index ac22160f0348390c1966b9985803b5c81fcc304b..c2d46489000cddf8dae938a1d7dc77d2af9c2ebf 100644 (file)
@@ -30,9 +30,7 @@
 #include <FL/Fl_Double_Window.H>
 #include <vector>
 
-namespace giada
-{
-namespace v
+namespace giada::v
 {
 /* cb_window_closer
 Callback for closing windows. Deletes the widget (delete). */
@@ -72,12 +70,11 @@ public:
        gdWindow* getParent();
        gdWindow* getChild(int id);
 
-  protected:
+protected:
        std::vector<gdWindow*> subWindows;
        int                    id;
        gdWindow*              parent;
 };
-} // namespace v
-} // namespace giada
+} // namespace giada::v
 
 #endif
index 89df7e5c2eec7bc279be9718abceb9319df4b1e3..d7e594ed4bd507a24ef16d7bf457c250457d156c 100644 (file)
 #include "gui/dialogs/mainWindow.h"
 #include "gui/elems/mainWindow/keyboard/channel.h"
 #include "gui/elems/mainWindow/keyboard/keyboard.h"
+#include "gui/ui.h"
 #include <FL/Fl.H>
 #include <cassert>
 
-extern giada::v::gdMainWindow* G_MainWin;
+extern giada::v::Ui g_ui;
 
-namespace giada
+namespace giada::v
 {
-namespace v
+Dispatcher::Dispatcher()
+: m_backspace(false)
+, m_end(false)
+, m_enter(false)
+, m_space(false)
+, m_esc(false)
+, m_key(false)
 {
-namespace dispatcher
-{
-namespace
-{
-bool backspace_ = false;
-bool end_       = false;
-bool enter_     = false;
-bool space_     = false;
-bool esc_       = false;
-bool key_       = false;
-
-std::function<void()> signalCb_ = nullptr;
+}
 
 /* -------------------------------------------------------------------------- */
 
-void perform_(ID channelId, int event)
+void Dispatcher::perform(ID channelId, int event) const
 {
        if (event == FL_KEYDOWN)
        {
@@ -74,103 +70,84 @@ void perform_(ID channelId, int event)
 /* Walk channels array, trying to match button's bound key with the event. If 
 found, trigger the key-press/key-release function. */
 
-void dispatchChannels_(int event)
+void Dispatcher::dispatchChannels(int event) const
 {
-       G_MainWin->keyboard->forEachChannel([=](geChannel& c) {
+       g_ui.mainWindow->keyboard->forEachChannel([=](geChannel& c) {
                if (c.handleKey(event))
-                       perform_(c.getData().id, event);
+                       perform(c.getData().id, event);
        });
 }
 
 /* -------------------------------------------------------------------------- */
 
-void triggerSignalCb_()
+void Dispatcher::dispatchKey(int event)
 {
-       if (signalCb_ == nullptr)
-               return;
-       signalCb_();
-       signalCb_ = nullptr;
-}
-} // namespace
+       assert(onEventOccured != nullptr);
 
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-
-void dispatchKey(int event)
-{
        /* These events come from the keyboard, not from a direct interaction on the 
        UI with the mouse/touch. */
 
        if (event == FL_KEYDOWN)
        {
-               if (Fl::event_key() == FL_BackSpace && !backspace_)
+               if (Fl::event_key() == FL_BackSpace && !m_backspace)
                {
-                       backspace_ = true;
+                       m_backspace = true;
                        c::events::rewindSequencer(Thread::MAIN);
                }
-               else if (Fl::event_key() == FL_End && !end_)
+               else if (Fl::event_key() == FL_End && !m_end)
                {
-                       end_ = true;
+                       m_end = true;
                        c::events::toggleInputRecording();
                }
-               else if (Fl::event_key() == FL_Enter && !enter_)
+               else if (Fl::event_key() == FL_Enter && !m_enter)
                {
-                       enter_ = true;
+                       m_enter = true;
                        c::events::toggleActionRecording();
                }
-               else if (Fl::event_key() == ' ' && !space_)
+               else if (Fl::event_key() == ' ' && !m_space)
                {
-                       space_ = true;
+                       m_space = true;
                        c::events::toggleSequencer(Thread::MAIN);
                }
-               else if (Fl::event_key() == FL_Escape && !esc_)
+               else if (Fl::event_key() == FL_Escape && !m_esc)
                {
-                       esc_ = true;
+                       m_esc = true;
                        m::init::closeMainWindow();
                }
-               else if (!key_)
+               else if (!m_key)
                {
-                       key_ = true;
-                       triggerSignalCb_();
-                       dispatchChannels_(event);
+                       m_key = true;
+                       onEventOccured();
+                       dispatchChannels(event);
                }
        }
        else if (event == FL_KEYUP)
        {
                if (Fl::event_key() == FL_BackSpace)
-                       backspace_ = false;
+                       m_backspace = false;
                else if (Fl::event_key() == FL_End)
-                       end_ = false;
+                       m_end = false;
                else if (Fl::event_key() == ' ')
-                       space_ = false;
+                       m_space = false;
                else if (Fl::event_key() == FL_Enter)
-                       enter_ = false;
+                       m_enter = false;
                else if (Fl::event_key() == FL_Escape)
-                       esc_ = false;
+                       m_esc = false;
                else
                {
-                       key_ = false;
-                       dispatchChannels_(event);
+                       m_key = false;
+                       dispatchChannels(event);
                }
        }
 }
 
 /* -------------------------------------------------------------------------- */
 
-void dispatchTouch(const geChannel& gch, bool status)
+void Dispatcher::dispatchTouch(const geChannel& gch, bool status)
 {
-       triggerSignalCb_();
-       perform_(gch.getData().id, status ? FL_KEYDOWN : FL_KEYUP);
-}
+       assert(onEventOccured != nullptr);
 
-/* -------------------------------------------------------------------------- */
-
-void setSignalCallback(std::function<void()> f)
-{
-       signalCb_ = f;
+       onEventOccured();
+       perform(gch.getData().id, status ? FL_KEYDOWN : FL_KEYUP);
 }
-
-} // namespace dispatcher
-} // namespace v
-} // namespace giada
+} // namespace giada::v
\ No newline at end of file
index 2d4d7e19e4143ee5379f2dc055cb6fcec17aca9b..3521dc14aff75601e4e68116c6850110a9e00a63 100644 (file)
 #ifndef G_V_DISPATCHER_H
 #define G_V_DISPATCHER_H
 
+#include "core/types.h"
 #include <functional>
 
-namespace giada
-{
-namespace v
+namespace giada::v
 {
 class geChannel;
-
-namespace dispatcher
+class Dispatcher final
 {
-/* dispatchKey
-Processes a key pressed on the physical keyboard. */
+public:
+       Dispatcher();
+
+       /* dispatchKey
+    Processes a key pressed on the physical keyboard. */
+
+       void dispatchKey(int event);
+
+       /* dispatchTouch
+    Processes a mouse click/touch event. */
+
+       void dispatchTouch(const geChannel& gch, bool status);
+
+       /* onEventOccured
+    Callback fired when a key has been pressed or a mouse button clicked. */
+
+       std::function<void()> onEventOccured;
 
-void dispatchKey(int event);
+private:
+       void perform(ID channelId, int event) const;
 
-/* dispatchTouch
-Processes a mouse click/touch event. */
+       /* dispatchChannels
+    Walks channels array, trying to match button's bound key with the event. If 
+    found, trigger the key-press/key-release function. */
 
-void dispatchTouch(const geChannel& gch, bool status);
+       void dispatchChannels(int event) const;
 
-void setSignalCallback(std::function<void()> f);
-} // namespace dispatcher
-} // namespace v
-} // namespace giada
+       bool m_backspace;
+       bool m_end;
+       bool m_enter;
+       bool m_space;
+       bool m_esc;
+       bool m_key;
+};
+} // namespace giada::v
 
 #endif
\ No newline at end of file
index 664a7aa8e921558ff31021501051ead9f2fd5f7f..0fb7bb7d12ef47e297f97f77e8a44c561b839eda 100644 (file)
@@ -27,8 +27,8 @@
 #ifndef GE_BASE_ACTION_H
 #define GE_BASE_ACTION_H
 
-#include "core/recorder.h"
 #include "core/types.h"
+#include "src/core/actions/actions.h"
 #include <FL/Fl_Box.H>
 
 namespace giada::m
@@ -67,7 +67,7 @@ public:
        m::Action a1;
        m::Action a2;
 
-  protected:
+protected:
        bool m_resizable;
 };
 } // namespace giada::v
index dc79fe5d0cc1101198643cbbcc0f70f9b10a33ab..c8ffdbafd451a6b2d133826d15d6f1369f8f07e5 100644 (file)
 #include "gui/dialogs/actionEditor/baseActionEditor.h"
 #include "baseAction.h"
 #include "baseActionEditor.h"
-#include "core/clock.h"
 #include "core/const.h"
+#include "core/sequencer.h"
 #include "gridTool.h"
 #include <FL/Fl.H>
 #include <FL/fl_draw.H>
 
-namespace giada
-{
-namespace v
+namespace giada::v
 {
 geBaseActionEditor::geBaseActionEditor(Pixel x, Pixel y, Pixel w, Pixel h,
     gdBaseActionEditor* base)
@@ -76,17 +74,17 @@ void geBaseActionEditor::baseDraw(bool clear) const
        /* Draw grid, beats and bars. A grid set to 1 has a cell size == beat, so
        painting it is useless. */
 
-       if (m_base->gridTool->getValue() > 1)
+       if (m_base->gridTool.getValue() > 1)
        {
                fl_color(G_COLOR_GREY_3);
-               drawVerticals(m_base->gridTool->getCellSize());
+               drawVerticals(m_base->gridTool.getCellSize());
        }
 
        fl_color(G_COLOR_GREY_4);
-       drawVerticals(m::clock::getFramesInBeat());
+       drawVerticals(m_data->framesInBeat);
 
        fl_color(G_COLOR_LIGHT_1);
-       drawVerticals(m::clock::getFramesInBar());
+       drawVerticals(m_data->framesInBar);
 
        /* Cover unused area. Avoid drawing cover if width == 0 (i.e. beats are 32). */
 
@@ -101,7 +99,7 @@ void geBaseActionEditor::drawVerticals(int steps) const
 {
        /* Start drawing from steps, not from 0. The zero-th element is always 
        graphically useless. */
-       for (Frame i = steps; i < m::clock::getFramesInLoop(); i += steps)
+       for (Frame i = steps; i < m_data->framesInLoop; i += steps)
        {
                Pixel p = m_base->frameToPixel(i) + x();
                fl_line(p, y() + 1, p, y() + h() - 2);
@@ -181,5 +179,4 @@ int geBaseActionEditor::release()
        m_action = nullptr;
        return ret;
 }
-} // namespace v
-} // namespace giada
\ No newline at end of file
+} // namespace giada::v
\ No newline at end of file
index 028a88ea1bc9fb8a68a858780b6aea84278fd4ea..f973375799f725a469db7e7e90f72bc155e29d7c 100644 (file)
@@ -35,6 +35,7 @@ namespace giada::c::actionEditor
 {
 struct Data;
 }
+
 namespace giada::v
 {
 class gdBaseActionEditor;
@@ -58,7 +59,7 @@ public:
 
        geBaseAction* getActionAtCursor() const;
 
-  protected:
+protected:
        geBaseActionEditor(Pixel x, Pixel y, Pixel w, Pixel h, gdBaseActionEditor*);
 
        c::actionEditor::Data* m_data;
@@ -84,7 +85,7 @@ public:
        virtual void onResizeAction()  = 0;
        virtual void onRefreshAction() = 0;
 
-  private:
+private:
        /* drawVerticals
        Draws generic vertical lines (beats, bars, grid lines...). */
 
index b8e6d7814feeb1341d256306e5e28540630613df..9fa0d28c785eb1a80291c2b7e61a291941cd82e5 100644 (file)
  * -------------------------------------------------------------------------- */
 
 #include "envelopeEditor.h"
-#include "core/action.h"
 #include "core/conf.h"
 #include "core/const.h"
-#include "core/recorder.h"
 #include "envelopePoint.h"
 #include "glue/actionEditor.h"
 #include "glue/channel.h"
 #include "gui/dialogs/actionEditor/baseActionEditor.h"
+#include "src/core/actions/action.h"
+#include "src/core/actions/actions.h"
 #include "utils/log.h"
 #include "utils/math.h"
 #include <FL/Fl.H>
 #include <FL/fl_draw.H>
 #include <cassert>
 
-namespace giada
-{
-namespace v
+namespace giada::v
 {
 geEnvelopeEditor::geEnvelopeEditor(Pixel x, Pixel y, const char* l, gdBaseActionEditor* b)
-: geBaseActionEditor(x, y, 200, m::conf::conf.envelopeEditorH, b)
+: geBaseActionEditor(x, y, 200, 40, b)
 {
        copy_label(l);
 }
 
 /* -------------------------------------------------------------------------- */
 
-geEnvelopeEditor::~geEnvelopeEditor()
-{
-       m::conf::conf.envelopeEditorH = h();
-}
-
-/* -------------------------------------------------------------------------- */
-
 void geEnvelopeEditor::draw()
 {
        baseDraw();
@@ -214,5 +205,4 @@ void geEnvelopeEditor::onRefreshAction()
 
        m_base->rebuild();
 }
-} // namespace v
-} // namespace giada
+} // namespace giada::v
\ No newline at end of file
index f7663c7c0a86fc5552c8dc932b4bb7f15087b73e..49675fa8104b0963f14c1f1d2740aae0ebcc384f 100644 (file)
 
 #include "baseActionEditor.h"
 
-namespace giada
+namespace giada::v
 {
-namespace m
-{
-class SampleChannel;
-}
-namespace v
-{
-class geEnvelopePoint;
 class geEnvelopeEditor : public geBaseActionEditor
 {
 public:
        geEnvelopeEditor(Pixel x, Pixel y, const char* l, gdBaseActionEditor*);
-       ~geEnvelopeEditor();
-
        void draw() override;
 
        void rebuild(c::actionEditor::Data& d) override;
 
-  private:
+private:
        void onAddAction() override;
        void onDeleteAction() override;
        void onMoveAction() override;
@@ -62,7 +53,6 @@ public:
        bool isFirstPoint() const;
        bool isLastPoint() const;
 };
-} // namespace v
-} // namespace giada
+} // namespace giada::v
 
 #endif
index 4215d3916750b734bc8b5659cae02263a5c804c7..e362b55a73250caed40422f11d40e0d15983f0de 100644 (file)
@@ -28,9 +28,7 @@
 #include "core/const.h"
 #include <FL/fl_draw.H>
 
-namespace giada
-{
-namespace v
+namespace giada::v
 {
 geEnvelopePoint::geEnvelopePoint(Pixel X, Pixel Y, m::Action a)
 : geBaseAction(X, Y, SIDE, SIDE, /*resizable=*/false, a, {})
@@ -43,5 +41,4 @@ void geEnvelopePoint::draw()
 {
        fl_rectf(x(), y(), w(), h(), hovered ? G_COLOR_LIGHT_2 : G_COLOR_LIGHT_1);
 }
-} // namespace v
-} // namespace giada
\ No newline at end of file
+} // namespace giada::v
\ No newline at end of file
index 8f4d4f457fd6e62239a174f4736732fbb0c02af8..a83b309d0f19ad44355d9ac4c23f50044f0943a4 100644 (file)
 #define GE_ENVELOPE_POINT_H
 
 #include "baseAction.h"
-#include "core/recorder.h"
+#include "src/core/actions/actions.h"
 
-namespace giada
-{
-namespace v
+namespace giada::v
 {
 class geEnvelopePoint : public geBaseAction
 {
@@ -43,7 +41,6 @@ public:
 
        void draw() override;
 };
-} // namespace v
-} // namespace giada
+} // namespace giada::v
 
 #endif
index 622d46d72c3d7eb1d187e42f0197a5bd1bde5f83..68f5492d0891df2f5aec6b4b7a207c4468beebe4 100644 (file)
@@ -24,8 +24,7 @@
 *
 * --------------------------------------------------------------------------- */
 
-#include "gridTool.h"
-#include "core/clock.h"
+#include "gui/elems/actionEditor/gridTool.h"
 #include "core/conf.h"
 #include "gui/elems/basics/check.h"
 #include "gui/elems/basics/choice.h"
 
 namespace giada::v
 {
-geGridTool::geGridTool(Pixel x, Pixel y)
+geGridTool::geGridTool(Pixel x, Pixel y, m::Conf::Data& c, Frame framesInBeat)
 : Fl_Group(x, y, 80, 20)
+, m_conf(c)
+, m_framesInBeat(framesInBeat)
 {
        gridType = new geChoice(x, y, 40, 20);
        gridType->add("1");
@@ -51,8 +52,8 @@ geGridTool::geGridTool(Pixel x, Pixel y)
 
        active = new geCheck(gridType->x() + gridType->w() + 4, y, 20, 20);
 
-       gridType->value(m::conf::conf.actionEditorGridVal);
-       active->value(m::conf::conf.actionEditorGridOn);
+       gridType->value(m_conf.actionEditorGridVal);
+       active->value(m_conf.actionEditorGridOn);
 
        end();
 
@@ -64,8 +65,8 @@ geGridTool::geGridTool(Pixel x, Pixel y)
 
 geGridTool::~geGridTool()
 {
-       m::conf::conf.actionEditorGridVal = gridType->value();
-       m::conf::conf.actionEditorGridOn  = active->value();
+       m_conf.actionEditorGridVal = gridType->value();
+       m_conf.actionEditorGridOn  = active->value();
 }
 
 /* -------------------------------------------------------------------------- */
@@ -125,6 +126,6 @@ Frame geGridTool::getSnapFrame(Frame v) const
 
 Frame geGridTool::getCellSize() const
 {
-       return m::clock::getFramesInBeat() / getValue();
+       return m_framesInBeat / getValue();
 }
 } // namespace giada::v
\ No newline at end of file
index 04b4cf1b577796d5fa4c95b2852c3573f96f916d..a8cf2d33835b8db2a3d332ad948117fa7a2cd4ff 100644 (file)
@@ -27,6 +27,7 @@
 #ifndef GE_GRID_TOOL_H
 #define GE_GRID_TOOL_H
 
+#include "core/conf.h"
 #include "core/types.h"
 #include <FL/Fl_Group.H>
 
@@ -38,7 +39,7 @@ class geChoice;
 class geGridTool : public Fl_Group
 {
 public:
-       geGridTool(Pixel x, Pixel y);
+       geGridTool(Pixel x, Pixel y, m::Conf::Data&, Frame framesInBeat);
        ~geGridTool();
 
        int  getValue() const;
@@ -51,7 +52,10 @@ public:
 
        Frame getCellSize() const;
 
-  private:
+private:
+       m::Conf::Data& m_conf;
+       Frame          m_framesInBeat;
+
        geChoice* gridType;
        geCheck*  active;
 
diff --git a/src/gui/elems/actionEditor/noteEditor.cpp b/src/gui/elems/actionEditor/noteEditor.cpp
deleted file mode 100644 (file)
index 42936fe..0000000
+++ /dev/null
@@ -1,86 +0,0 @@
-/* -----------------------------------------------------------------------------
- *
- * Giada - Your Hardcore Loopmachine
- *
- * -----------------------------------------------------------------------------
- *
- * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
- *
- * This file is part of Giada - Your Hardcore Loopmachine.
- *
- * Giada - Your Hardcore Loopmachine is free software: you can
- * redistribute it and/or modify it under the terms of the GNU General
- * Public License as published by the Free Software Foundation, either
- * version 3 of the License, or (at your option) any later version.
- *
- * Giada - Your Hardcore Loopmachine is distributed in the hope that it
- * will be useful, but WITHOUT ANY WARRANTY; without even the implied
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Giada - Your Hardcore Loopmachine. If not, see
- * <http://www.gnu.org/licenses/>.
- *
- * -------------------------------------------------------------------------- */
-
-#include "noteEditor.h"
-#include "core/conf.h"
-#include "core/const.h"
-#include "gui/dialogs/actionEditor/midiActionEditor.h"
-#include "pianoRoll.h"
-#include <FL/Fl.H>
-
-namespace giada
-{
-namespace v
-{
-geNoteEditor::geNoteEditor(Pixel x, Pixel y, gdMidiActionEditor* base)
-: geScroll(x, y, 200, 422)
-, m_base(base)
-{
-       end();
-
-       type(Fl_Scroll::VERTICAL_ALWAYS);
-       size(m_base->fullWidth, m::conf::conf.pianoRollH);
-
-       pianoRoll = new gePianoRoll(x, y, m_base->fullWidth, base);
-       add(pianoRoll);
-}
-
-/* -------------------------------------------------------------------------- */
-
-geNoteEditor::~geNoteEditor()
-{
-       m::conf::conf.pianoRollH = h();
-       m::conf::conf.pianoRollY = pianoRoll->y();
-}
-
-/* -------------------------------------------------------------------------- */
-
-void geNoteEditor::scroll()
-{
-       Pixel ey = Fl::event_y() - pianoRoll->pick;
-
-       Pixel y1 = y();
-       Pixel y2 = (y() + h()) - pianoRoll->h();
-
-       if (ey > y1)
-               ey = y1;
-       else if (ey < y2)
-               ey = y2;
-
-       pianoRoll->position(x(), ey);
-
-       redraw();
-}
-
-/* -------------------------------------------------------------------------- */
-
-void geNoteEditor::rebuild(c::actionEditor::Data& d)
-{
-       size(m_base->fullWidth, h());
-       pianoRoll->rebuild(d);
-}
-} // namespace v
-} // namespace giada
\ No newline at end of file
diff --git a/src/gui/elems/actionEditor/noteEditor.h b/src/gui/elems/actionEditor/noteEditor.h
deleted file mode 100644 (file)
index 9c737d5..0000000
+++ /dev/null
@@ -1,57 +0,0 @@
-/* -----------------------------------------------------------------------------
- *
- * Giada - Your Hardcore Loopmachine
- *
- * -----------------------------------------------------------------------------
- *
- * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
- *
- * This file is part of Giada - Your Hardcore Loopmachine.
- *
- * Giada - Your Hardcore Loopmachine is free software: you can
- * redistribute it and/or modify it under the terms of the GNU General
- * Public License as published by the Free Software Foundation, either
- * version 3 of the License, or (at your option) any later version.
- *
- * Giada - Your Hardcore Loopmachine is distributed in the hope that it
- * will be useful, but WITHOUT ANY WARRANTY; without even the implied
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Giada - Your Hardcore Loopmachine. If not, see
- * <http://www.gnu.org/licenses/>.
- *
- * -------------------------------------------------------------------------- */
-
-#ifndef GE_NOTE_EDITOR_H
-#define GE_NOTE_EDITOR_H
-
-#include "core/types.h"
-#include "gui/elems/basics/scroll.h"
-
-namespace giada::c::actionEditor
-{
-struct Data;
-}
-namespace giada::v
-{
-class gdMidiActionEditor;
-class gePianoRoll;
-class geNoteEditor : public geScroll
-{
-public:
-       geNoteEditor(Pixel x, Pixel y, gdMidiActionEditor* base);
-       ~geNoteEditor();
-
-       void rebuild(c::actionEditor::Data& d);
-       void scroll();
-
-       gePianoRoll* pianoRoll;
-
-  private:
-       gdMidiActionEditor* m_base;
-};
-} // namespace giada::v
-
-#endif
index e44f9a69652a6cfc2cd7bd6ea1f642752a4764ee..2b46bca9bf7de540f923b3a42000e77dcab921a8 100644 (file)
@@ -25,9 +25,9 @@
  * -------------------------------------------------------------------------- */
 
 #include "pianoItem.h"
-#include "core/action.h"
 #include "core/const.h"
 #include "core/midiEvent.h"
+#include "src/core/actions/action.h"
 #include "utils/math.h"
 #include <FL/fl_draw.H>
 
index 00c25f120d698badc7f97c7cfba951413e89521c..ab448be7f1f9bec0470ab89b5fc7565626559c4b 100644 (file)
 
 #include "baseAction.h"
 
-namespace giada
-{
-namespace m
+namespace giada::m
 {
 struct Action;
 }
-namespace v
-{
-class gdActionEditor;
 
+namespace giada::v
+{
 class gePianoItem : public geBaseAction
 {
 public:
@@ -48,13 +45,12 @@ public:
 
        bool isResizable() const;
 
-  private:
+private:
        bool m_ringLoop;
        bool m_orphaned;
 
        Pixel calcVelocityH() const;
 };
-} // namespace v
-} // namespace giada
+} // namespace giada::v
 
 #endif
index 78c5ae3a03caba9e7a3bbca5e8627b2349c0ad6b..ba89dd7b189fb513a7a96ce9c42652c8d602d6f0 100644 (file)
  * -------------------------------------------------------------------------- */
 
 #include "pianoRoll.h"
-#include "core/action.h"
-#include "core/clock.h"
 #include "core/conf.h"
 #include "core/const.h"
 #include "core/midiEvent.h"
 #include "glue/actionEditor.h"
 #include "glue/channel.h"
 #include "gui/dialogs/actionEditor/baseActionEditor.h"
-#include "noteEditor.h"
-#include "pianoItem.h"
+#include "gui/elems/actionEditor/pianoItem.h"
+#include "src/core/actions/action.h"
 #include "utils/log.h"
 #include "utils/math.h"
 #include "utils/string.h"
 #include <FL/Fl.H>
 #include <cassert>
 
-namespace giada
+namespace giada::v
 {
-namespace v
+gePianoRoll::gePianoRoll(Pixel X, Pixel Y, gdBaseActionEditor* b)
+: geBaseActionEditor(X, Y, 200, CELL_H * MAX_KEYS, b)
+, m_pick(0)
 {
-gePianoRoll::gePianoRoll(Pixel X, Pixel Y, Pixel W, gdBaseActionEditor* b)
-: geBaseActionEditor(X, Y, W, 40, b)
-, pick(0)
-{
-       position(x(), m::conf::conf.pianoRollY == -1 ? y() - (h() / 2) : m::conf::conf.pianoRollY);
 }
 
 /* -------------------------------------------------------------------------- */
 
-void gePianoRoll::drawSurface1()
+void gePianoRoll::drawSurfaceY()
 {
-       surface1 = fl_create_offscreen(CELL_W, h());
-       fl_begin_offscreen(surface1);
+       surfaceY = fl_create_offscreen(CELL_W, h());
+       fl_begin_offscreen(surfaceY);
 
        /* Warning: only w() and h() come from this widget, x and y coordinates are 
        absolute, since we are writing in a memory chunk. */
@@ -71,7 +66,6 @@ void gePianoRoll::drawSurface1()
 
        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 */
 
                std::string note = u::string::iToString(octave);
@@ -139,11 +133,11 @@ void gePianoRoll::drawSurface1()
 
 /* -------------------------------------------------------------------------- */
 
-void gePianoRoll::drawSurface2()
+void gePianoRoll::drawSurfaceX()
 {
-       surface2 = fl_create_offscreen(CELL_W, h());
+       surfaceX = fl_create_offscreen(CELL_W, h());
 
-       fl_begin_offscreen(surface2);
+       fl_begin_offscreen(surfaceX);
        fl_rectf(0, 0, CELL_W, h(), G_COLOR_GREY_1);
        fl_color(G_COLOR_GREY_3);
        fl_line_style(FL_DASH, 0, nullptr);
@@ -152,11 +146,11 @@ void gePianoRoll::drawSurface2()
        {
                switch (i % KEYS)
                {
-               case (int)Notes::G:
-               case (int)Notes::E:
-               case (int)Notes::D:
-               case (int)Notes::B:
-               case (int)Notes::A:
+               case static_cast<int>(Notes::G):
+               case static_cast<int>(Notes::E):
+               case static_cast<int>(Notes::D):
+               case static_cast<int>(Notes::B):
+               case static_cast<int>(Notes::A):
                        fl_rectf(0, i * CELL_H, CELL_W, CELL_H, G_COLOR_GREY_2);
                        break;
                }
@@ -175,15 +169,15 @@ void gePianoRoll::drawSurface2()
 
 void gePianoRoll::draw()
 {
-       fl_copy_offscreen(x(), y(), CELL_W, h(), surface1, 0, 0);
+       fl_copy_offscreen(x(), y(), CELL_W, h(), surfaceY, 0, 0);
 
 // TODO - is this APPLE thing still useful?
 #if defined(__APPLE__)
        for (Pixel i = 36; i < m_base->fullWidth; i += 36) /// TODO: i < m_base->loopWidth is faster
-               fl_copy_offscreen(x() + i, y(), CELL_W, h(), surface2, 1, 0);
+               fl_copy_offscreen(x() + i, y(), CELL_W, h(), surfaceX, 1, 0);
 #else
        for (Pixel i = CELL_W; i < m_base->loopWidth; i += CELL_W)
-               fl_copy_offscreen(x() + i, y(), CELL_W, h(), surface2, 0, 0);
+               fl_copy_offscreen(x() + i, y(), CELL_W, h(), surfaceX, 0, 0);
 #endif
 
        baseDraw(false);
@@ -194,16 +188,28 @@ void gePianoRoll::draw()
 
 int gePianoRoll::handle(int e)
 {
-       if (e == FL_PUSH && Fl::event_button3())
-       {
-               pick = Fl::event_y() - y();
+       if (!Fl::event_button3())
                return geBaseActionEditor::handle(e);
+
+       switch (e)
+       {
+       case FL_PUSH:
+       {
+               m_pick = Fl::event_y() - y();
+               break;
        }
-       if (e == FL_DRAG && Fl::event_button3())
+       case FL_DRAG:
        {
-               static_cast<geNoteEditor*>(parent())->scroll();
-               return 1;
+               const int pos = Fl::event_y() - m_pick;
+               const int min = parent()->y();
+               const int max = -h() + (parent()->h() + parent()->y());
+               position(x(), std::clamp(pos, max, min));
+               break;
+       }
+       default:
+               break;
        }
+
        return geBaseActionEditor::handle(e);
 }
 
@@ -377,10 +383,9 @@ void gePianoRoll::rebuild(c::actionEditor::Data& d)
                add(new gePianoItem(px, py, pw, ph, a1, a2));
        }
 
-       drawSurface1();
-       drawSurface2();
+       drawSurfaceY();
+       drawSurfaceX();
 
        redraw();
 }
-} // namespace v
-} // namespace giada
+} // namespace giada::v
index 470359bc5c05d1430228cc205eef8c2721c8c01b..db44cbfc58619164773f6427731d166a70941662 100644 (file)
@@ -32,9 +32,9 @@
 
 namespace giada::m
 {
-class MidiChannel;
-class Action;
-} // namespace giada::m
+struct Action;
+}
+
 namespace giada::v
 {
 class gePianoRoll : public geBaseActionEditor
@@ -46,16 +46,14 @@ public:
        static const Pixel CELL_H      = 20;
        static const Pixel CELL_W      = 40;
 
-       gePianoRoll(Pixel x, Pixel y, Pixel w, gdBaseActionEditor*);
+       gePianoRoll(Pixel x, Pixel y, gdBaseActionEditor* b);
 
        void draw() override;
        int  handle(int e) override;
 
        void rebuild(c::actionEditor::Data& d) override;
 
-       Pixel pick;
-
-  private:
+private:
        enum class Notes
        {
                G  = 1,
@@ -72,9 +70,6 @@ public:
                GS = 0
        };
 
-       Fl_Offscreen surface1; // notes, no repeat
-       Fl_Offscreen surface2; // lines, x-repeat
-
        void onAddAction() override;
        void onDeleteAction() override;
        void onMoveAction() override;
@@ -89,13 +84,22 @@ public:
        containing the rest of the piano roll. The latter will then be tiled during
        the ::draw() call. */
 
-       void drawSurface1();
-       void drawSurface2();
+       void drawSurfaceY();
+       void drawSurfaceX();
 
        Pixel snapToY(Pixel p) const;
        int   yToNote(Pixel y) const;
        Pixel noteToY(int n) const;
        Pixel getPianoItemW(Pixel x, const m::Action& a1, const m::Action& a2) const;
+
+       Fl_Offscreen surfaceY; // vertical notes, no x-repeat
+       Fl_Offscreen surfaceX; // lines, x-repeat
+
+       /* m_pick
+       Y-coordinate of the click event when the user clicks on an empty area of the
+       piano roll. Used for right mouse button scrolling. */
+
+       Pixel m_pick;
 };
 } // namespace giada::v
 
index 38f71d0c4b2f46e56c23d2014d458d7d01dc7fed..211ad7ac217f754955a4b243beb0d5a9b6ee0c39 100644 (file)
@@ -25,8 +25,8 @@
  * -------------------------------------------------------------------------- */
 
 #include "sampleAction.h"
-#include "core/action.h"
 #include "core/const.h"
+#include "src/core/actions/action.h"
 #include <FL/fl_draw.H>
 
 namespace giada
index 0ec2eaf67e37bc30846a89cdead133c0f5cacc37..b6066526999242979c15cbc6babb6a1d5f4ae2ec 100644 (file)
 #define GE_SAMPLE_ACTION_H
 
 #include "baseAction.h"
-#include "core/recorder.h"
+#include "src/core/actions/actions.h"
 
-namespace giada
-{
-namespace m
-{
-class SampleChannel;
-}
-namespace v
+namespace giada::v
 {
 class geSampleAction : public geBaseAction
 {
@@ -46,10 +40,9 @@ public:
 
        void draw() override;
 
-  private:
+private:
        bool m_singlePress;
 };
-} // namespace v
-} // namespace giada
+} // namespace giada::v
 
 #endif
index c290abf56bf474de61c86011bf8c131a19ee04d1..89fddb4bfa0c3b8a272a6c9b76c57226d4f2b96f 100644 (file)
  * -------------------------------------------------------------------------- */
 
 #include "sampleActionEditor.h"
-#include "core/action.h"
-#include "core/conf.h"
 #include "core/const.h"
-#include "core/recorder.h"
 #include "glue/actionEditor.h"
 #include "glue/channel.h"
 #include "gui/dialogs/actionEditor/baseActionEditor.h"
+#include "gui/dialogs/actionEditor/sampleActionEditor.h"
 #include "sampleAction.h"
+#include "src/core/actions/action.h"
+#include "src/core/actions/actions.h"
 #include "utils/log.h"
 #include <FL/Fl.H>
 #include <FL/fl_draw.H>
 #include <cassert>
 
-namespace giada
-{
-namespace v
+namespace giada::v
 {
 geSampleActionEditor::geSampleActionEditor(Pixel x, Pixel y, gdBaseActionEditor* b)
-: geBaseActionEditor(x, y, 200, m::conf::conf.sampleActionEditorH, b)
-{
-}
-
-/* -------------------------------------------------------------------------- */
-
-geSampleActionEditor::~geSampleActionEditor()
+: geBaseActionEditor(x, y, 200, 40, b)
 {
-       m::conf::conf.sampleActionEditorH = h();
 }
 
 /* -------------------------------------------------------------------------- */
@@ -101,6 +92,12 @@ void geSampleActionEditor::rebuild(c::actionEditor::Data& d)
 
 void geSampleActionEditor::draw()
 {
+       /* Force height to match its parent's height. This widget belongs to a 
+       geScroll container (see geSplitScroll class in baseActionEditor.h) but
+       there's nothing to scroll here actually. */
+
+       size(w(), parent()->h());
+
        /* Draw basic boundaries (+ beat bars) and hide the unused area. Then draw 
        children (the actions). */
 
@@ -123,7 +120,7 @@ void geSampleActionEditor::draw()
 void geSampleActionEditor::onAddAction()
 {
        Frame f = m_base->pixelToFrame(Fl::event_x() - x());
-       c::actionEditor::recordSampleAction(m_data->channelId, m_base->getActionType(), f);
+       c::actionEditor::recordSampleAction(m_data->channelId, static_cast<gdSampleActionEditor*>(m_base)->getActionType(), f);
 }
 
 /* -------------------------------------------------------------------------- */
@@ -210,5 +207,4 @@ bool geSampleActionEditor::isNoteOffSinglePress(const m::Action& a)
        return m_data->sample->channelMode == SamplePlayerMode::SINGLE_PRESS &&
               a.event.getStatus() == m::MidiEvent::NOTE_OFF;
 }
-} // namespace v
-} // namespace giada
+} // namespace giada::v
\ No newline at end of file
index c1bab27462be5a5309fcade5fda1c8fe1e069cad..fd8a8306ca353bbe35d38d11f29819b3f664f04d 100644 (file)
 
 #include "baseActionEditor.h"
 
-namespace giada
+namespace giada::m
 {
-namespace m
-{
-class SampleChannel;
 struct Action;
-} // namespace m
-namespace v
+}
+
+namespace giada::v
 {
 class geSampleAction;
-
 class geSampleActionEditor : public geBaseActionEditor
 {
 public:
        geSampleActionEditor(Pixel x, Pixel y, gdBaseActionEditor*);
-       ~geSampleActionEditor();
 
        void draw() override;
 
        void rebuild(c::actionEditor::Data& d) override;
 
-  private:
+private:
        void onAddAction() override;
        void onDeleteAction() override;
        void onMoveAction() override;
@@ -59,7 +55,6 @@ public:
 
        bool isNoteOffSinglePress(const m::Action& a);
 };
-} // namespace v
-} // namespace giada
+} // namespace giada::v
 
 #endif
diff --git a/src/gui/elems/actionEditor/splitScroll.cpp b/src/gui/elems/actionEditor/splitScroll.cpp
new file mode 100644 (file)
index 0000000..bcece2a
--- /dev/null
@@ -0,0 +1,105 @@
+/* -----------------------------------------------------------------------------
+*
+* Giada - Your Hardcore Loopmachine
+*
+* ------------------------------------------------------------------------------
+*
+* Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
+*
+* This file is part of Giada - Your Hardcore Loopmachine.
+*
+* Giada - Your Hardcore Loopmachine is free software: you can
+* redistribute it and/or modify it under the terms of the GNU General
+* Public License as published by the Free Software Foundation, either
+* version 3 of the License, or (at your option) any later version.
+*
+* Giada - Your Hardcore Loopmachine is distributed in the hope that it
+* will be useful, but WITHOUT ANY WARRANTY; without even the implied
+* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+* See the GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with Giada - Your Hardcore Loopmachine. If not, see
+* <http://www.gnu.org/licenses/>.
+*
+* --------------------------------------------------------------------------- */
+
+#include "splitScroll.h"
+
+namespace giada::v
+{
+geSplitScroll::geSplitScroll(Pixel x, Pixel y, Pixel w, Pixel h)
+: geSplit(x, y, w, h)
+, m_a(0, 0, 0, 0, Fl_Scroll::VERTICAL_ALWAYS)
+, m_b(0, 0, 0, 0, Direction::HORIZONTAL)
+{
+       m_b.onScrollH = [&a = m_a](Pixel x) {
+               a.scroll_to(x, a.yposition());
+       };
+}
+
+/* -------------------------------------------------------------------------- */
+
+void geSplitScroll::addWidgets(Fl_Widget& wa, Fl_Widget& wb, Pixel topContentH)
+{
+       m_a.add(&wa);
+       m_b.addWidget(&wb);
+
+       init(m_a, m_b);
+
+       if (topContentH != -1)
+               resizePanel(geSplit::Panel::A, topContentH);
+}
+
+/* -------------------------------------------------------------------------- */
+
+Pixel geSplitScroll::getScrollX() const
+{
+       return m_b.xposition();
+}
+
+Pixel geSplitScroll::getScrollY() const
+{
+       return m_a.yposition();
+}
+
+/* -------------------------------------------------------------------------- */
+
+Pixel geSplitScroll::getContentWidth() const
+{
+       if (m_a.countChildren() == 0)
+               return 0;
+       return m_a.child(0)->w();
+}
+
+/* -------------------------------------------------------------------------- */
+
+Pixel geSplitScroll::getTopContentH() const
+{
+       return m_a.h();
+}
+
+/* -------------------------------------------------------------------------- */
+
+geompp::Rect<Pixel> geSplitScroll::getBoundsNoScrollbar() const
+{
+       return {
+           x(), y(),
+           w() - m_a.scrollbar.w() - G_GUI_OUTER_MARGIN,
+           h() - m_b.hscrollbar.h() - G_GUI_OUTER_MARGIN};
+}
+
+/* -------------------------------------------------------------------------- */
+
+void geSplitScroll::setScrollX(Pixel p)
+{
+       p = std::max(0, p);
+       m_a.scroll_to(p, m_a.yposition());
+       m_b.scroll_to(p, m_b.yposition());
+}
+
+void geSplitScroll::setScrollY(Pixel p)
+{
+       m_a.scroll_to(m_a.xposition(), p);
+}
+} // namespace giada::v
\ No newline at end of file
diff --git a/src/gui/elems/actionEditor/splitScroll.h b/src/gui/elems/actionEditor/splitScroll.h
new file mode 100644 (file)
index 0000000..6bfd29c
--- /dev/null
@@ -0,0 +1,59 @@
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+#ifndef GE_SPLITSCROLL_H
+#define GE_SPLITSCROLL_H
+
+#include "core/types.h"
+#include "deps/geompp/src/rect.hpp"
+#include "gui/elems/basics/liquidScroll.h"
+#include "gui/elems/basics/scroll.h"
+#include "gui/elems/basics/split.h"
+
+namespace giada::v
+{
+class geSplitScroll : public geSplit
+{
+public:
+       geSplitScroll(Pixel x, Pixel y, Pixel w, Pixel h);
+
+       Pixel               getScrollX() const;
+       Pixel               getScrollY() const;
+       Pixel               getContentWidth() const;
+       Pixel               getTopContentH() const;
+       geompp::Rect<Pixel> getBoundsNoScrollbar() const;
+
+       void addWidgets(Fl_Widget& a, Fl_Widget& b, Pixel topContentH = -1);
+       void setScrollX(Pixel p);
+       void setScrollY(Pixel p);
+
+private:
+       geScroll       m_a;
+       geLiquidScroll m_b;
+};
+} // namespace giada::v
+
+#endif
index fe01bd454f2e1729fb42ad187eff3894a305eeaf..dd6ddc1fb82d9cf57a25cd42d4a5c61574ec2c1e 100644 (file)
  * -------------------------------------------------------------------------- */
 
 #include "velocityEditor.h"
-#include "core/action.h"
-#include "core/clock.h"
 #include "core/conf.h"
 #include "core/const.h"
 #include "envelopePoint.h"
 #include "glue/actionEditor.h"
 #include "gui/dialogs/actionEditor/baseActionEditor.h"
+#include "src/core/actions/action.h"
 #include "utils/log.h"
 #include "utils/math.h"
 #include <FL/Fl.H>
 #include <FL/fl_draw.H>
 #include <cassert>
 
-namespace giada
-{
-namespace v
+namespace giada::v
 {
 geVelocityEditor::geVelocityEditor(Pixel x, Pixel y, gdBaseActionEditor* b)
-: geBaseActionEditor(x, y, 200, m::conf::conf.velocityEditorH, b)
-{
-}
-
-/* -------------------------------------------------------------------------- */
-
-geVelocityEditor::~geVelocityEditor()
+: geBaseActionEditor(x, y, 200, 40, b)
 {
-       m::conf::conf.velocityEditorH = h();
 }
 
 /* -------------------------------------------------------------------------- */
@@ -60,25 +50,28 @@ void geVelocityEditor::draw()
 {
        baseDraw();
 
+       if (h() < geEnvelopePoint::SIDE)
+               return;
+
        /* Print label. */
 
        fl_color(G_COLOR_GREY_4);
        fl_font(FL_HELVETICA, G_GUI_FONT_SIZE_BASE);
-       fl_draw("Velocity", x() + 4, y(), w(), h(), (Fl_Align)(FL_ALIGN_LEFT));
+       fl_draw("Velocity", x() + 4, y(), w(), h(), FL_ALIGN_LEFT);
 
        if (children() == 0)
                return;
 
-       Pixel side = geEnvelopePoint::SIDE / 2;
+       const Pixel side = geEnvelopePoint::SIDE / 2;
 
        for (int i = 0; i < children(); i++)
        {
                geEnvelopePoint* p = static_cast<geEnvelopePoint*>(child(i));
                if (m_action == nullptr)
                        p->position(p->x(), valueToY(p->a1.event.getVelocity()));
-               Pixel x1 = p->x() + side;
-               Pixel y1 = p->y();
-               Pixel y2 = y() + h();
+               const Pixel x1 = p->x() + side;
+               const Pixel y1 = p->y();
+               const Pixel y2 = y() + h();
                fl_line(x1, y1, x1, y2);
        }
 
@@ -152,5 +145,4 @@ void geVelocityEditor::onRefreshAction()
 
        m_base->rebuild(); // Rebuild pianoRoll as well
 }
-} // namespace v
-} // namespace giada
+} // namespace giada::v
\ No newline at end of file
index 7f83a87e0ee36dca9d84987bef6c20485a0c2d33..cb15f90e928287764496431ac048e4f41c1c71e7 100644 (file)
 
 #include "baseActionEditor.h"
 
-namespace giada
-{
-namespace m
-{
-class MidiChannel;
-}
-namespace v
+namespace giada::v
 {
 class geEnvelopePoint;
-
 class geVelocityEditor : public geBaseActionEditor
 {
 public:
        geVelocityEditor(Pixel x, Pixel y, gdBaseActionEditor*);
-       ~geVelocityEditor();
 
        void draw() override;
 
        void rebuild(c::actionEditor::Data& d) override;
 
-  private:
+private:
        void onMoveAction() override;
        void onRefreshAction() override;
        void onAddAction() override{};
@@ -59,7 +51,6 @@ public:
        Pixel valueToY(int v) const;
        int   yToValue(Pixel y) const;
 };
-} // namespace v
-} // namespace giada
+} // namespace giada::v
 
 #endif
index a9c418238f03e3ab3488575bf3a60be4dbeede4e..acb639ba84929f5faea2633c6044e2e4a3b20e3f 100644 (file)
@@ -86,10 +86,12 @@ ID geChoice::getSelectedId() const
 
 void geChoice::addItem(const std::string& label, ID id)
 {
-       assert(id >= 0);
-
        Fl_Choice::add(label.c_str(), 0, cb_onChange, static_cast<void*>(this));
-       m_ids.push_back(id);
+
+       if (id != -1)
+               m_ids.push_back(id);
+       else // auto-increment
+               m_ids.push_back(m_ids.size() == 0 ? 0 : m_ids.back() + 1);
 }
 
 /* -------------------------------------------------------------------------- */
index ea979747b31dd49a242fc8dfcf14c0d195d85285..be7903cc6cc9dd03f87d4c1a1b77078f12a1301f 100644 (file)
@@ -43,7 +43,11 @@ public:
 
        ID getSelectedId() const;
 
-       void addItem(const std::string& label, ID id);
+       /* addItem
+       Adds a new item with a certain ID. Pass id = -1 to auto-increment it. */
+
+       void addItem(const std::string& label, ID id = -1);
+
        void showItem(const std::string& label);
        void showItem(ID id);
        void clear();
index 0e594454ae8dc5cccc6794da971a934da7e99090..55d884ce6b8cf007f52debbfafe296c78b552e76 100644 (file)
@@ -38,7 +38,7 @@ namespace giada
 namespace v
 {
 /* geGroup
-A group that resizes itself accoring to the content. */
+A group that resizes itself according to the content. */
 
 class geGroup : public Fl_Group
 {
@@ -52,14 +52,18 @@ public:
 
        /* add
     Adds a Fl_Widget 'w' to this group. Coordinates are relative to the group,
-    so origin starts at (0, 0). */
+    so origin starts at (0, 0). As with any other FLTK group, the widget becomes
+       owned by this group: If you add static or automatic (local) variables, then 
+       it is your responsibility to remove (or delete) all such static or automatic
+       child widgets before destroying the group - otherwise the child widgets' 
+       destructors would be called twice! */
 
        void add(Fl_Widget* w);
 
        Fl_Widget* getChild(std::size_t i);
        Fl_Widget* getLastChild();
 
-  private:
+private:
        /* m_widgets 
     The internal Fl_Scroll::array_ is unreliable when inspected with the child()
     method. Let's keep track of widgets that belong to this group manually. */
index 9223e1e294b1b859fd029871d9c735ef735550e9..a639aeb1d4f5f879d4c4778c789b40cfce7efcb8 100644 (file)
 #include "boxtypes.h"
 #include "core/const.h"
 
-geLiquidScroll::geLiquidScroll(int x, int y, int w, int h)
-: geScroll(x, y, w, h, Fl_Scroll::VERTICAL_ALWAYS)
+namespace giada::v
+{
+geLiquidScroll::geLiquidScroll(int x, int y, int w, int h, Direction d)
+: geScroll(x, y, w, h, d == Direction::VERTICAL ? Fl_Scroll::VERTICAL_ALWAYS : Fl_Scroll::HORIZONTAL_ALWAYS)
+, m_direction(d)
 {
 }
 
@@ -42,12 +45,16 @@ geLiquidScroll::geLiquidScroll(int x, int y, int w, int h)
 
 void geLiquidScroll::resize(int X, int Y, int W, int H)
 {
-       int nc = children() - 2; // skip hscrollbar and vscrollbar
-       for (int t = 0; t < nc; t++)
-       { // tell children to resize to our new width
+       const int nc = children() - 2; // skip hscrollbar and vscrollbar
+       for (int t = 0; t < nc; t++)   // tell children to resize to our new width
+       {
                Fl_Widget* c = child(t);
-               c->resize(c->x(), c->y(), W - 24, c->h()); // W-24: leave room for scrollbar
+               if (m_direction == Direction::VERTICAL)
+                       c->resize(c->x(), c->y(), W - 24, c->h()); // -24: leave room for scrollbar
+               else
+                       c->resize(c->x(), c->y(), c->w(), H - 24); // -24: leave room for scrollbar
        }
        init_sizes(); // tell scroll children changed in size
        Fl_Scroll::resize(X, Y, W, H);
 }
+} // namespace giada::v
\ No newline at end of file
index 530249f4113bf6b131b9143965eecd23d241cfb2..04b5983c31b15cf2db5a82a51ce8a6746b2af762 100644 (file)
 #define GE_LIQUID_SCROLL_H
 
 #include "core/const.h"
+#include "gui/types.h"
 #include "scroll.h"
 
+namespace giada::v
+{
 class geLiquidScroll : public geScroll
 {
 public:
-       geLiquidScroll(int x, int y, int w, int h);
+       geLiquidScroll(int x, int y, int w, int h, Direction d);
 
        void resize(int x, int y, int w, int h) override;
 
@@ -61,6 +64,10 @@ public:
 
                return wg;
        }
+
+private:
+       Direction m_direction;
 };
+} // namespace giada::v
 
 #endif
index eaf50c4617532d5ad8de03cd8c2f96aaa53ab9a9..0b6982d37302ff44a23be6989db92d83fd50fd89 100644 (file)
@@ -30,9 +30,7 @@
 #include "pack.h"
 #include "core/const.h"
 
-namespace giada
-{
-namespace v
+namespace giada::v
 {
 gePack::gePack(int x, int y, Direction d, int gutter)
 : geGroup(x, y)
@@ -55,5 +53,4 @@ void gePack::add(Fl_Widget* widget)
 
        geGroup::add(widget);
 }
-} // namespace v
-} // namespace giada
\ No newline at end of file
+} // namespace giada::v
\ No newline at end of file
index 13a60805c5d8dc2c7e18264e370ab11987d04376..723f99c718b30a049e3a9a4b33bdfd3c8550ad75 100644 (file)
 
 #include "core/const.h"
 #include "gui/elems/basics/group.h"
+#include "gui/types.h"
 
-namespace giada
+namespace giada::v
 {
-namespace v
-{
-enum class Direction
-{
-       HORIZONTAL,
-       VERTICAL
-};
-
 /* gePack
 A stack of widgets that resize itself according to its content. */
 
@@ -53,15 +46,18 @@ public:
 
        /* add
     Adds a Fl_Widget 'w' to this pack. Coordinates are relative to the group,
-    so origin starts at (0, 0). */
+    so origin starts at (0, 0). As with any other FLTK group, the widget becomes
+       owned by this group: If you add static or automatic (local) variables, then 
+       it is your responsibility to remove (or delete) all such static or automatic
+       child widgets before destroying the group - otherwise the child widgets' 
+       destructors would be called twice! */
 
        void add(Fl_Widget* w);
 
-  private:
+private:
        Direction m_direction;
        int       m_gutter;
 };
-} // namespace v
-} // namespace giada
+} // namespace giada::v
 
 #endif
index d3c1546174ffba321e40bd6133d302944eee25bb..aa8bc4bf51a2d1c458784aea200123e0c0bbdb6e 100644 (file)
@@ -2,22 +2,6 @@
  *
  * Giada - Your Hardcore Loopmachine
  *
- * geResizerBar
- * 'resizer bar' between widgets Fl_Scroll. Thanks to Greg Ercolano from
- * FLTK dev team. http://seriss.com/people/erco/fltk/
- *
- * Shows a resize cursor when hovered over.
- * Assumes:
- *     - Parent is an Fl_Scroll
- *     - All children of Fl_Scroll are m_vertically arranged
- *     - The widget above us has a bottom edge touching our top edge
- *       ie. (w->y()+w->h() == this->y())
- *
- * When this widget is dragged:
- *     - The widget above us (with a common edge) will be /resized/
- *       m_vertically
- *     - All children below us will be /moved/ m_vertically
- *
  * -----------------------------------------------------------------------------
  *
  * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
 #include "resizerBar.h"
 #include "core/const.h"
 #include <FL/Fl.H>
-#include <FL/Fl_Scroll.H>
+#include <FL/Fl_Group.H>
 #include <FL/fl_draw.H>
+#include <cassert>
+#include <vector>
 
-geResizerBar::geResizerBar(int X, int Y, int W, int H, int minSize, bool type, Fl_Widget* target)
+namespace giada::v
+{
+geResizerBar::geResizerBar(int X, int Y, int W, int H, int minSize, Direction dir, Mode mode)
 : Fl_Box(X, Y, W, H)
-, m_type(type)
+, m_direction(dir)
+, m_mode(mode)
 , m_minSize(minSize)
 , m_lastPos(0)
-, m_initialPos(0)
 , m_hover(false)
-, m_target(target)
 {
-       if (m_type == VERTICAL)
-       {
-               m_origSize = H;
-               labelsize(H);
-       }
-       else
-       {
-               m_origSize = W;
-               labelsize(W);
-       }
-       align(FL_ALIGN_CENTER | FL_ALIGN_INSIDE);
-       labelfont(FL_COURIER);
        visible_focus(0);
 }
 
@@ -74,65 +49,113 @@ geResizerBar::geResizerBar(int X, int Y, int W, int H, int minSize, bool type, F
 
 void geResizerBar::handleDrag(int diff)
 {
-       Fl_Scroll* group = static_cast<Fl_Scroll*>(parent());
+       m_mode == Mode::MOVE ? move(diff) : resize(diff);
+
+       Fl_Group* group = static_cast<Fl_Group*>(parent());
+       group->init_sizes();
+       group->redraw();
+}
 
-       const int top = m_type == VERTICAL ? y() : x();
-       const int bot = m_type == VERTICAL ? y() + h() : x() + w();
+/* -------------------------------------------------------------------------- */
 
-       // First pass: find widget directly above us with common edge
-       // Possibly clamp 'diff' if widget would get too small..
+void geResizerBar::move(int diff)
+{
+       Fl_Widget&              wfirst  = getFirstWidget();
+       std::vector<Fl_Widget*> wothers = findWidgets([this](const Fl_Widget& wd) { return isAfter(wd); });
 
-       for (int t = 0; t < group->children(); t++)
+       if (m_direction == Direction::VERTICAL)
        {
-               Fl_Widget* wd = group->child(t);
-               if (m_type == VERTICAL)
-               {
-                       if ((wd->y() + wd->h()) == top)
-                       { // found widget directly above?
-                               if ((wd->h() + diff) < m_minSize)
-                                       diff = wd->h() - m_minSize;                        // clamp
-                               wd->resize(wd->x(), wd->y(), wd->w(), wd->h() + diff); // change height
-                               break;                                                 // done with first pass
-                       }
-               }
-               else
-               {
-                       if ((wd->x() + wd->w()) == top)
-                       { // found widget directly above?
-                               if ((wd->w() + diff) < m_minSize)
-                                       diff = wd->w() - m_minSize;                        // clamp
-                               wd->resize(wd->x(), wd->y(), wd->w() + diff, wd->h()); // change width
-                               break;                                                 // done with first pass
-                       }
-               }
+               if (wfirst.h() + diff < m_minSize)
+                       diff = 0;
+               wfirst.resize(wfirst.x(), wfirst.y(), wfirst.w(), wfirst.h() + diff);
+               for (Fl_Widget* wd : wothers)
+                       wd->resize(wd->x(), wd->y() + diff, wd->w(), wd->h());
+               resize(x(), y() + diff, w(), h());
+       }
+       else if (m_direction == Direction::HORIZONTAL)
+       {
+               if (wfirst.w() + diff < m_minSize)
+                       diff = 0;
+               wfirst.resize(wfirst.x(), wfirst.y(), wfirst.w() + diff, wfirst.h());
+               for (Fl_Widget* wd : wothers)
+                       wd->resize(wd->x() + diff, wd->y(), wd->w(), wd->h());
+               resize(x() + diff, y(), w(), h());
        }
+}
 
-       // Second pass: find widgets below us, move based on clamped diff
+/* -------------------------------------------------------------------------- */
+
+void geResizerBar::resize(int diff)
+{
+       Fl_Widget& wa = getFirstWidget();
+       Fl_Widget& wb = *findWidgets([this](const Fl_Widget& wd) { return isAfter(wd); }, /*howmany=*/1)[0];
+
+       if (m_direction == Direction::VERTICAL)
+       {
+               if (wa.h() + diff < m_minSize || wb.h() - diff < m_minSize)
+                       diff = 0;
+               wa.resize(wa.x(), wa.y(), wa.w(), wa.h() + diff);
+               wb.resize(wb.x(), wb.y() + diff, wb.w(), wb.h() - diff);
+               resize(x(), y() + diff, w(), h());
+       }
+       else if (m_direction == Direction::HORIZONTAL)
+       {
+               if (wa.w() + diff < m_minSize || wb.w() - diff < m_minSize)
+                       diff = 0;
+               wa.resize(wa.x(), wa.y(), wa.w() + diff, wa.h());
+               wb.resize(wb.x() + diff, wb.y(), wb.w() - diff, wb.h());
+               resize(x() + diff, y(), w(), h());
+       }
+}
+
+/* -------------------------------------------------------------------------- */
+
+bool geResizerBar::isBefore(const Fl_Widget& wd) const
+{
+       const int before = m_direction == Direction::VERTICAL ? y() : x();
+       return (m_direction == Direction::VERTICAL && wd.y() + wd.h() == before) ||
+              (m_direction == Direction::HORIZONTAL && wd.x() + wd.w() == before);
+}
+
+/* -------------------------------------------------------------------------- */
+
+bool geResizerBar::isAfter(const Fl_Widget& wd) const
+{
+       const int after = m_direction == Direction::VERTICAL ? y() + h() : x() + w();
+       return (m_direction == Direction::VERTICAL && wd.y() >= after) ||
+              (m_direction == Direction::HORIZONTAL && wd.x() >= after);
+}
+
+/* -------------------------------------------------------------------------- */
+
+Fl_Widget& geResizerBar::getFirstWidget()
+{
+       return *findWidgets([this](const Fl_Widget& wd) { return isBefore(wd); }, /*howmany=*/1)[0];
+}
+
+/* -------------------------------------------------------------------------- */
+
+std::vector<Fl_Widget*> geResizerBar::findWidgets(std::function<bool(const Fl_Widget&)> f, int howmany) const
+{
+       std::vector<Fl_Widget*> out;
+       Fl_Group*               group = static_cast<Fl_Group*>(parent());
 
        for (int t = 0; t < group->children(); t++)
        {
                Fl_Widget* wd = group->child(t);
-               if (m_type == VERTICAL)
-               {
-                       if (wd->y() >= bot)                                        // found widget below us?
-                               wd->resize(wd->x(), wd->y() + diff, wd->w(), wd->h()); // change position
-               }
-               else
-               {
-                       if (wd->x() >= bot)
-                               wd->resize(wd->x() + diff, wd->y(), wd->w(), wd->h());
-               }
+               if (!f(*wd))
+                       continue;
+               out.push_back(wd);
+               if (howmany != -1 && out.size() == (size_t)howmany)
+                       break;
        }
 
-       // Change our position last
+       /* Make sure it finds the exact number of widgets requested, in case 
+       howmany != -1. */
 
-       if (m_type == VERTICAL)
-               resize(x(), y() + diff, w(), h());
-       else
-               resize(x() + diff, y(), w(), h());
+       assert(howmany == -1 || (howmany != -1 && out.size() == (size_t)howmany));
 
-       group->init_sizes();
-       group->redraw();
+       return out;
 }
 
 /* -------------------------------------------------------------------------- */
@@ -148,7 +171,7 @@ void geResizerBar::draw()
 int geResizerBar::handle(int e)
 {
        int ret        = 0;
-       int currentPos = m_type == VERTICAL ? Fl::event_y_root() : Fl::event_x_root();
+       int currentPos = m_direction == Direction::VERTICAL ? Fl::event_y_root() : Fl::event_x_root();
 
        switch (e)
        {
@@ -157,7 +180,7 @@ int geResizerBar::handle(int e)
                break;
        case FL_ENTER:
                ret = 1;
-               fl_cursor(m_type == VERTICAL ? FL_CURSOR_NS : FL_CURSOR_WE);
+               fl_cursor(m_direction == Direction::VERTICAL ? FL_CURSOR_NS : FL_CURSOR_WE);
                m_hover = true;
                redraw();
                break;
@@ -168,20 +191,19 @@ int geResizerBar::handle(int e)
                redraw();
                break;
        case FL_PUSH:
-               ret          = 1;
-               m_lastPos    = currentPos;
-               m_initialPos = currentPos;
+               ret       = 1;
+               m_lastPos = currentPos;
                break;
        case FL_DRAG:
                handleDrag(currentPos - m_lastPos);
                m_lastPos = currentPos;
                ret       = 1;
                if (onDrag != nullptr)
-                       onDrag(m_target);
+                       onDrag(getFirstWidget());
                break;
        case FL_RELEASE:
-               if (m_initialPos != currentPos && onRelease != nullptr)
-                       onRelease(m_target);
+               if (onRelease != nullptr)
+                       onRelease(getFirstWidget());
                break;
        default:
                break;
@@ -191,17 +213,20 @@ int geResizerBar::handle(int e)
 
 /* -------------------------------------------------------------------------- */
 
-int geResizerBar::getMinSize() const
+void geResizerBar::resize(int X, int Y, int W, int H)
 {
-       return m_minSize;
+       if (m_direction == Direction::VERTICAL)
+               Fl_Box::resize(X, Y, W, h());
+       else
+               Fl_Box::resize(X, Y, w(), H);
 }
 
 /* -------------------------------------------------------------------------- */
 
-void geResizerBar::resize(int x, int y, int w, int h)
+void geResizerBar::moveTo(int p)
 {
-       if (m_type == VERTICAL)
-               Fl_Box::resize(x, y, w, m_origSize); // Height of resizer stays constant size
-       else
-               Fl_Box::resize(x, y, m_origSize, h);
+       const Fl_Widget& wd   = getFirstWidget();
+       const int        curr = m_direction == Direction::VERTICAL ? wd.h() : wd.w();
+       handleDrag(p - curr);
 }
+} // namespace giada::v
\ No newline at end of file
index a860241e66a2d3f9641dab7fb9d525a94f5eff8c..27ef9de8a4f4c159924b45b63a7f0642d44dd4fe 100644 (file)
@@ -2,22 +2,6 @@
  *
  * Giada - Your Hardcore Loopmachine
  *
- * geResizerBar
- * 'resizer bar' between widgets Fl_Scroll. Thanks to Greg Ercolano from
- * FLTK dev team. http://seriss.com/people/erco/fltk/
- *
- * Shows a resize cursor when hovered over.
- * Assumes:
- *     - Parent is an Fl_Scroll
- *     - All children of Fl_Scroll are vertically arranged
- *     - The widget above us has a bottom edge touching our top edge
- *       ie. (w->y()+w->h() == this->y())
- *
- * When this widget is dragged:
- *     - The widget above us (with a common edge) will be /resized/
- *       vertically
- *     - All children below us will be /moved/ vertically
- *
  * -----------------------------------------------------------------------------
  *
  * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
 #include <FL/Fl_Box.H>
 #include <functional>
 
+/* geResizerBar
+A 'resizer bar' between widgets inside a Fl_Scroll. Thanks to Greg Ercolano from
+FLTK dev team (http://seriss.com/people/erco/fltk/). It also shows a resize 
+cursor when hovered over.
+
+Assumes:
+       - Parent is an Fl_Group;
+       - The widget before us has an edge touching our edge;
+         ie. w->y() + w->h() == this->y() if Direction::VERTICAL.
+
+When this widget is dragged:
+       - The widget before us (with a common edge) will be resized;
+       - if Mode == MOVE 
+               All children after us will be moved.
+       - else if Mode == RESIZE
+               The child after us is resized. */
+
+namespace giada::v
+{
 class geResizerBar : public Fl_Box
 {
 public:
-       static const int HORIZONTAL = 0;
-       static const int VERTICAL   = 1;
+       enum class Direction
+       {
+               HORIZONTAL,
+               VERTICAL
+       };
+
+       enum class Mode
+       {
+               MOVE,
+               RESIZE
+       };
 
-       geResizerBar(int x, int y, int w, int h, int minSize, bool type, Fl_Widget* target = nullptr);
+       geResizerBar(int x, int y, int w, int h, int minSize, Direction dir, Mode m = Mode::MOVE);
 
        int  handle(int e) override;
        void draw() override;
        void resize(int x, int y, int w, int h) override;
 
-       int getMinSize() const;
+       void moveTo(int p);
+
+       std::function<void(const Fl_Widget&)> onDrag    = nullptr;
+       std::function<void(const Fl_Widget&)> onRelease = nullptr;
+
+private:
+       /* isBefore
+       True if widget 'w' is before the drag bar. */
 
-       std::function<void(const Fl_Widget*)> onDrag    = nullptr;
-       std::function<void(const Fl_Widget*)> onRelease = nullptr;
+       bool isBefore(const Fl_Widget& w) const;
+
+       /* isBefore
+       True if widget 'w' is after the drag bar. */
+       bool isAfter(const Fl_Widget& w) const;
+
+       /* findWidgets
+       Returns a vector of widgets according to a certain logic specified in the
+       lambda function. Limits the output to 'howmany' widgets if 'howmany' != -1. */
+
+       std::vector<Fl_Widget*> findWidgets(std::function<bool(const Fl_Widget&)> f, int howmany = -1) const;
+
+       /* handleDrag
+       Main entrypoint for the dragging operation. */
 
-  private:
        void handleDrag(int diff);
 
-       bool m_type;
-       int  m_origSize;
-       int  m_minSize;
-       int  m_lastPos;
-       int  m_initialPos;
-       bool m_hover;
+       /* move
+       Resize the first widget and shift all others. */
+
+       void move(int diff);
+
+       /* resize 
+       Resize the first and the second widget, leaving all others untouched. */
+
+       void resize(int diff);
+
+       /* getFirstWidget
+       Returns a ref to the first widget before the drag bar. */
+
+       Fl_Widget& getFirstWidget();
 
-       Fl_Widget* m_target;
+       Direction m_direction;
+       Mode      m_mode;
+       int       m_minSize;
+       int       m_lastPos;
+       bool      m_hover;
 };
+} // namespace giada::v
 
 #endif
index 9746ea2c331a7de898d0fb46381e03c9274cbf53..c6faf354d4c1837b273faf61cd3931b1d015a7c9 100644 (file)
@@ -42,11 +42,51 @@ geScroll::geScroll(int x, int y, int w, int h, int t)
        scrollbar.selection_color(G_COLOR_GREY_4);
        scrollbar.labelcolor(G_COLOR_LIGHT_1);
        scrollbar.slider(G_CUSTOM_BORDER_BOX);
+       scrollbar.callback(cb_onScrollV, this);
 
        hscrollbar.color(G_COLOR_GREY_2);
        hscrollbar.selection_color(G_COLOR_GREY_4);
        hscrollbar.labelcolor(G_COLOR_LIGHT_1);
        hscrollbar.slider(G_CUSTOM_BORDER_BOX);
+       hscrollbar.callback(cb_onScrollH, this);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void geScroll::cb_onScrollV(Fl_Widget* w, void* p)
+{
+       geScroll*     s = static_cast<geScroll*>(w->parent());
+       Fl_Scrollbar* b = static_cast<Fl_Scrollbar*>(w);
+
+       s->scroll_to(s->xposition(), b->value());
+
+       (static_cast<geScroll*>(p))->cb_onScrollV();
+}
+
+void geScroll::cb_onScrollH(Fl_Widget* w, void* p)
+{
+       geScroll*     s = static_cast<geScroll*>(w->parent());
+       Fl_Scrollbar* b = static_cast<Fl_Scrollbar*>(w);
+
+       s->scroll_to(b->value(), s->yposition());
+
+       (static_cast<geScroll*>(p))->cb_onScrollH();
+}
+
+/* -------------------------------------------------------------------------- */
+
+void geScroll::cb_onScrollV()
+{
+       if (onScrollV != nullptr)
+               onScrollV(yposition());
+}
+
+/* -------------------------------------------------------------------------- */
+
+void geScroll::cb_onScrollH()
+{
+       if (onScrollH != nullptr)
+               onScrollH(xposition());
 }
 
 /* -------------------------------------------------------------------------- */
index 5963c5cd57006a997c698019c9fefb7faeba2c2b..6562ec46ea2e5a0b928ac8cca1bd858da944db40 100644 (file)
@@ -31,6 +31,7 @@
 #define GE_SCROLL_H
 
 #include <FL/Fl_Scroll.H>
+#include <functional>
 
 class geScroll : public Fl_Scroll
 {
@@ -38,6 +39,15 @@ public:
        geScroll(int x, int y, int w, int h, int type = Fl_Scroll::BOTH);
 
        int countChildren() const;
+
+       std::function<void(int)> onScrollV{nullptr};
+       std::function<void(int)> onScrollH{nullptr};
+
+private:
+       static void cb_onScrollV(Fl_Widget* w, void* p);
+       static void cb_onScrollH(Fl_Widget* w, void* p);
+       void        cb_onScrollV();
+       void        cb_onScrollH();
 };
 
 #endif
diff --git a/src/gui/elems/basics/split.cpp b/src/gui/elems/basics/split.cpp
new file mode 100644 (file)
index 0000000..658ed7c
--- /dev/null
@@ -0,0 +1,69 @@
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+#include "split.h"
+#include "core/const.h"
+#include "gui/elems/basics/box.h"
+
+namespace giada::v
+{
+geSplit::geSplit(int x, int y, int w, int h)
+: Fl_Group(x, y, w, h)
+, m_a(nullptr)
+, m_b(nullptr)
+, m_bar(0, 0, w, G_GUI_INNER_MARGIN, G_GUI_UNIT, geResizerBar::Direction::VERTICAL, geResizerBar::Mode::RESIZE)
+{
+       end();
+}
+
+/* -------------------------------------------------------------------------- */
+
+void geSplit::init(Fl_Widget& a, Fl_Widget& b)
+{
+       a.resize(x(), y(), w(), (h() / 2) - G_GUI_INNER_MARGIN); // Panel A goes on top
+       a.redraw();
+       m_a = &a;
+
+       m_bar.resize(x(), m_a->y() + m_a->h(), w(), G_GUI_INNER_MARGIN);
+
+       b.resize(x(), m_bar.y() + m_bar.h(), w(), h() / 2); // Panel B goes on bottom
+       b.redraw();
+       m_b = &b;
+
+       Fl_Group::add(m_a);
+       Fl_Group::add(m_bar);
+       Fl_Group::add(m_b);
+
+       resizable(m_a);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void geSplit::resizePanel(Panel p, int s)
+{
+       m_bar.moveTo(p == Panel::A ? s : h() - s);
+}
+} // namespace giada::v
\ No newline at end of file
diff --git a/src/gui/elems/basics/split.h b/src/gui/elems/basics/split.h
new file mode 100644 (file)
index 0000000..f407466
--- /dev/null
@@ -0,0 +1,60 @@
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+#ifndef GE_SPLIT_H
+#define GE_SPLIT_H
+
+#include "gui/elems/basics/resizerBar.h"
+#include <FL/Fl_Group.H>
+
+namespace giada::v
+{
+/* geSplit
+A resizable split-view widget that contains two horizontal panels (A and B). 
+TODO - add vertical mode. */
+
+class geSplit : public Fl_Group
+{
+public:
+       enum class Panel
+       {
+               A,
+               B
+       };
+
+       geSplit(int x, int y, int w, int h);
+
+       void init(Fl_Widget& a, Fl_Widget& b);
+       void resizePanel(Panel p, int s);
+
+private:
+       Fl_Widget*   m_a;
+       Fl_Widget*   m_b;
+       geResizerBar m_bar;
+};
+} // namespace giada::v
+
+#endif
index 0705c4d0fea5b91fc80f740bca84a8b706b4c350..8539215580688c7c074dca79dc4855efba992ed9 100644 (file)
@@ -37,7 +37,8 @@
 
 namespace giada::v
 {
-geTabAudio::geDeviceMenu::geDeviceMenu(int x, int y, int w, int h, const char* l, const std::vector<c::config::AudioDeviceData>& devices)
+geTabAudio::geDeviceMenu::geDeviceMenu(int x, int y, int w, int h, const char* l,
+    const std::vector<c::config::AudioDeviceData>& devices)
 : geChoice(x, y, w, h, l)
 {
        if (devices.size() == 0)
@@ -55,7 +56,8 @@ geTabAudio::geDeviceMenu::geDeviceMenu(int x, int y, int w, int h, const char* l
 /* -------------------------------------------------------------------------- */
 /* -------------------------------------------------------------------------- */
 
-geTabAudio::geChannelMenu::geChannelMenu(int x, int y, int w, int h, const char* l, c::config::AudioDeviceData& data)
+geTabAudio::geChannelMenu::geChannelMenu(int x, int y, int w, int h, const char* l,
+    const c::config::AudioDeviceData& data)
 : geChoice(x, y, w, h, l)
 , m_data(data)
 {
@@ -77,7 +79,7 @@ int geTabAudio::geChannelMenu::getChannelsStart() const
 
 /* -------------------------------------------------------------------------- */
 
-void geTabAudio::geChannelMenu::rebuild(c::config::AudioDeviceData& data)
+void geTabAudio::geChannelMenu::rebuild(const c::config::AudioDeviceData& data)
 {
        m_data = data;
 
index a3591937bd40fb05c5e9ec56e370703d0f3ce5a1..91cf9040bce3c67081ea9c14e11fc6b9c67a057a 100644 (file)
@@ -47,17 +47,17 @@ public:
 
        struct geChannelMenu : public geChoice
        {
-               geChannelMenu(int x, int y, int w, int h, const char* l, c::config::AudioDeviceData&);
+               geChannelMenu(int x, int y, int w, int h, const char* l, const c::config::AudioDeviceData&);
 
                int getChannelsCount() const;
                int getChannelsStart() const;
 
-               void rebuild(c::config::AudioDeviceData&);
+               void rebuild(const c::config::AudioDeviceData&);
 
        private:
                static constexpr int STEREO_OFFSET = 1000;
 
-               c::config::AudioDeviceData& m_data;
+               c::config::AudioDeviceData m_data;
        };
 
        geTabAudio(int x, int y, int w, int h);
index ddcced24692cf6e81a325bbce95d939cf119b738..4178b577eaf62ffec0b2ef12d24fc938158f8e22 100644 (file)
 #include "gui/elems/basics/check.h"
 #include <FL/Fl_Pack.H>
 
-namespace giada
+namespace giada::v
 {
-namespace v
-{
-geTabBehaviors::geTabBehaviors(int X, int Y, int W, int H)
+geTabBehaviors::geTabBehaviors(int X, int Y, int W, int H, m::Conf::Data& c)
 : Fl_Group(X, Y, W, H)
 , m_container(X, Y + G_GUI_OUTER_MARGIN, Direction::VERTICAL, G_GUI_OUTER_MARGIN)
 , m_chansStopOnSeqHalt(0, 0, 280, 30, "Dynamic channels stop immediately when the sequencer\nis halted")
 , m_treatRecsAsLoops(0, 0, 280, 20, "Treat one shot channels with actions as loops")
 , m_inputMonitorDefaultOn(0, 0, 280, 20, "New sample channels have input monitor on by default")
 , m_overdubProtectionDefaultOn(0, 0, 280, 30, "New sample channels have overdub protection on\nby default")
+, m_conf(c)
 {
        end();
 
@@ -56,20 +55,19 @@ geTabBehaviors::geTabBehaviors(int X, int Y, int W, int H)
 
        add(m_container);
 
-       m_chansStopOnSeqHalt.value(m::conf::conf.chansStopOnSeqHalt);
-       m_treatRecsAsLoops.value(m::conf::conf.treatRecsAsLoops);
-       m_inputMonitorDefaultOn.value(m::conf::conf.inputMonitorDefaultOn);
-       m_overdubProtectionDefaultOn.value(m::conf::conf.overdubProtectionDefaultOn);
+       m_chansStopOnSeqHalt.value(m_conf.chansStopOnSeqHalt);
+       m_treatRecsAsLoops.value(m_conf.treatRecsAsLoops);
+       m_inputMonitorDefaultOn.value(m_conf.inputMonitorDefaultOn);
+       m_overdubProtectionDefaultOn.value(m_conf.overdubProtectionDefaultOn);
 }
 
 /* -------------------------------------------------------------------------- */
 
 void geTabBehaviors::save()
 {
-       m::conf::conf.chansStopOnSeqHalt         = m_chansStopOnSeqHalt.value();
-       m::conf::conf.treatRecsAsLoops           = m_treatRecsAsLoops.value();
-       m::conf::conf.inputMonitorDefaultOn      = m_inputMonitorDefaultOn.value();
-       m::conf::conf.overdubProtectionDefaultOn = m_overdubProtectionDefaultOn.value();
+       m_conf.chansStopOnSeqHalt         = m_chansStopOnSeqHalt.value();
+       m_conf.treatRecsAsLoops           = m_treatRecsAsLoops.value();
+       m_conf.inputMonitorDefaultOn      = m_inputMonitorDefaultOn.value();
+       m_conf.overdubProtectionDefaultOn = m_overdubProtectionDefaultOn.value();
 }
-} // namespace v
-} // namespace giada
\ No newline at end of file
+} // namespace giada::v
\ No newline at end of file
index 22785aecb2608a6fc3620fa43384a838d26f5ab1..a481c4665a8c9a0367af4788d7387a75e56e7aa2 100644 (file)
 #ifndef GE_TAB_BEHAVIORS_H
 #define GE_TAB_BEHAVIORS_H
 
+#include "core/conf.h"
 #include "gui/elems/basics/check.h"
 #include "gui/elems/basics/pack.h"
 #include <FL/Fl_Group.H>
 
-namespace giada
-{
-namespace v
+namespace giada::v
 {
 class geTabBehaviors : public Fl_Group
 {
 public:
-       geTabBehaviors(int x, int y, int w, int h);
+       geTabBehaviors(int x, int y, int w, int h, m::Conf::Data&);
 
        void save();
 
-  private:
+private:
        gePack  m_container;
        geCheck m_chansStopOnSeqHalt;
        geCheck m_treatRecsAsLoops;
        geCheck m_inputMonitorDefaultOn;
        geCheck m_overdubProtectionDefaultOn;
+
+       m::Conf::Data& m_conf;
 };
-} // namespace v
-} // namespace giada
+} // namespace giada::v
 
 #endif
index 2fefe2d4db7fde06861ade25b72c3e00bac260c1..df36959115d6f19ee63e1ec12ddae166f4a838e2 100644 (file)
  *
  * -------------------------------------------------------------------------- */
 
-#include "tabMidi.h"
-#include "core/conf.h"
+#include "gui/elems/config/tabMidi.h"
 #include "core/const.h"
-#include "core/kernelMidi.h"
-#include "core/midiMapConf.h"
 #include "gui/elems/basics/box.h"
 #include "gui/elems/basics/check.h"
-#include "gui/elems/basics/choice.h"
 #include "utils/gui.h"
-#include <RtMidi.h>
 #include <string>
 
-namespace giada
+namespace giada::v
 {
-namespace v
+geTabMidi::geMenu::geMenu(int x, int y, int w, int h, const char* l,
+    const std::vector<std::string>& data, const std::string& msgIfNotFound)
+: geChoice(x, y, w, h, l)
 {
-geTabMidi::geTabMidi(int X, int Y, int W, int H)
-: Fl_Group(X, Y, W, H, "MIDI")
-{
-       begin();
-       system  = new geChoice(x() + w() - 250, y() + 9, 250, 20, "System");
-       portOut = new geChoice(x() + w() - 250, system->y() + system->h() + 8, 250, 20, "Output port");
-       portIn  = new geChoice(x() + w() - 250, portOut->y() + portOut->h() + 8, 250, 20, "Input port");
-       midiMap = new geChoice(x() + w() - 250, portIn->y() + portIn->h() + 8, 250, 20, "Output Midi Map");
-       sync    = new geChoice(x() + w() - 250, midiMap->y() + midiMap->h() + 8, 250, 20, "Sync");
-       new geBox(x(), sync->y() + sync->h() + 8, w(), h() - 150, "Restart Giada for the changes to take effect.");
-       end();
-
-       labelsize(G_GUI_FONT_SIZE_BASE);
-       selection_color(G_COLOR_GREY_4);
-
-       system->callback(cb_changeSystem, (void*)this);
-
-       fetchSystems();
-       fetchOutPorts();
-       fetchInPorts();
-       fetchMidiMaps();
-
-       sync->add("(disabled)");
-       sync->add("MIDI Clock (master)");
-       sync->add("MTC (master)");
-       if (m::conf::conf.midiSync == MIDI_SYNC_NONE)
-               sync->value(0);
-       else if (m::conf::conf.midiSync == MIDI_SYNC_CLOCK_M)
-               sync->value(1);
-       else if (m::conf::conf.midiSync == MIDI_SYNC_MTC_M)
-               sync->value(2);
-
-       systemInitValue = system->value();
-}
-
-/* -------------------------------------------------------------------------- */
-
-void geTabMidi::fetchOutPorts()
-{
-       if (m::kernelMidi::countOutPorts() == 0)
+       if (data.size() == 0)
        {
-               portOut->add("-- no ports found --");
-               portOut->value(0);
-               portOut->deactivate();
+               addItem(msgIfNotFound.c_str(), 0);
+               showItem(0);
+               deactivate();
        }
        else
        {
-
-               portOut->add("(disabled)");
-
-               for (unsigned i = 0; i < m::kernelMidi::countOutPorts(); i++)
-                       portOut->add(u::gui::removeFltkChars(m::kernelMidi::getOutPortName(i)).c_str());
-
-               portOut->value(m::conf::conf.midiPortOut + 1); // +1 because midiPortOut=-1 is '(disabled)'
+               for (const std::string& d : data)
+                       addItem(u::gui::removeFltkChars(d).c_str(), -1); // -1: auto-increment ID
        }
 }
 
 /* -------------------------------------------------------------------------- */
-
-void geTabMidi::fetchInPorts()
-{
-       if (m::kernelMidi::countInPorts() == 0)
-       {
-               portIn->add("-- no ports found --");
-               portIn->value(0);
-               portIn->deactivate();
-       }
-       else
-       {
-
-               portIn->add("(disabled)");
-
-               for (unsigned i = 0; i < m::kernelMidi::countInPorts(); i++)
-                       portIn->add(u::gui::removeFltkChars(m::kernelMidi::getInPortName(i)).c_str());
-
-               portIn->value(m::conf::conf.midiPortIn + 1); // +1 because midiPortIn=-1 is '(disabled)'
-       }
-}
-
 /* -------------------------------------------------------------------------- */
-
-void geTabMidi::fetchMidiMaps()
-{
-       if (m::midimap::maps.size() == 0)
-       {
-               midiMap->add("(no MIDI maps available)");
-               midiMap->value(0);
-               midiMap->deactivate();
-               return;
-       }
-
-       for (unsigned i = 0; i < m::midimap::maps.size(); i++)
-       {
-               const char* imap = m::midimap::maps.at(i).c_str();
-               midiMap->add(imap);
-               if (m::conf::conf.midiMapPath == imap)
-                       midiMap->value(i);
-       }
-
-       /* Preselect the 0 m::midimap if nothing is selected but midimaps exist. */
-
-       if (midiMap->value() == -1 && m::midimap::maps.size() > 0)
-               midiMap->value(0);
-}
-
-/* -------------------------------------------------------------------------- */
-
-void geTabMidi::save()
-{
-       std::string text = system->text(system->value());
-
-       if (text == "ALSA")
-               m::conf::conf.midiSystem = RtMidi::LINUX_ALSA;
-       else if (text == "Jack")
-               m::conf::conf.midiSystem = RtMidi::UNIX_JACK;
-       else if (text == "Multimedia MIDI")
-               m::conf::conf.midiSystem = RtMidi::WINDOWS_MM;
-       else if (text == "OSX Core MIDI")
-               m::conf::conf.midiSystem = RtMidi::MACOSX_CORE;
-
-       m::conf::conf.midiPortOut = portOut->value() - 1; // -1 because midiPortOut=-1 is '(disabled)'
-       m::conf::conf.midiPortIn  = portIn->value() - 1;  // -1 because midiPortIn=-1 is '(disabled)'
-       m::conf::conf.midiMapPath = m::midimap::maps.size() == 0 ? "" : midiMap->text(midiMap->value());
-
-       if (sync->value() == 0)
-               m::conf::conf.midiSync = MIDI_SYNC_NONE;
-       else if (sync->value() == 1)
-               m::conf::conf.midiSync = MIDI_SYNC_CLOCK_M;
-       else if (sync->value() == 2)
-               m::conf::conf.midiSync = MIDI_SYNC_MTC_M;
-}
-
 /* -------------------------------------------------------------------------- */
 
-void geTabMidi::fetchSystems()
+geTabMidi::geTabMidi(int X, int Y, int W, int H)
+: Fl_Group(X, Y, W, H, "MIDI")
+, m_data(c::config::getMidiData())
+, m_initialApi(m_data.api)
 {
-#if defined(__linux__)
-
-       if (m::kernelMidi::hasAPI(RtMidi::LINUX_ALSA))
-               system->add("ALSA");
-       if (m::kernelMidi::hasAPI(RtMidi::UNIX_JACK))
-               system->add("Jack");
-
-#elif defined(__FreeBSD__)
-
-       if (m::kernelMidi::hasAPI(RtMidi::UNIX_JACK))
-               system->add("Jack");
-
-#elif defined(_WIN32)
+       begin();
+       system    = new geChoice(x() + w() - 250, y() + 9, 250, 20, "System");
+       portOut   = new geMenu(x() + w() - 250, system->y() + system->h() + 8, 234, 20, "Output port", m_data.outPorts, "-- no ports found --");
+       enableOut = new geCheck(portOut->x() + portOut->w() + 4, portOut->y(), 12, 20);
+       portIn    = new geMenu(x() + w() - 250, portOut->y() + portOut->h() + 8, 234, 20, "Input port", m_data.inPorts, "-- no ports found --");
+       enableIn  = new geCheck(portIn->x() + portIn->w() + 4, portIn->y(), 12, 20);
+       midiMap   = new geMenu(x() + w() - 250, portIn->y() + portIn->h() + 8, 250, 20, "Output Midi Map", m_data.midiMaps, "(no MIDI maps available)");
+       sync      = new geChoice(x() + w() - 250, midiMap->y() + midiMap->h() + 8, 250, 20, "Sync");
+       new geBox(x(), sync->y() + sync->h() + 8, w(), h() - 150, "Restart Giada for the changes to take effect.");
+       end();
 
-       if (m::kernelMidi::hasAPI(RtMidi::WINDOWS_MM))
-               system->add("Multimedia MIDI");
+       labelsize(G_GUI_FONT_SIZE_BASE);
+       selection_color(G_COLOR_GREY_4);
 
-#elif defined(__APPLE__)
+       for (const auto& [key, value] : m_data.apis)
+               system->addItem(value.c_str(), key);
+       system->showItem(m_data.api);
+       system->onChange = [this](ID id) { m_data.api = id; invalidate(); };
 
-       system->add("OSX Core MIDI");
+       portOut->showItem(m_data.outPort);
+       portOut->onChange = [this](ID id) { m_data.outPort = id; };
+       if (m_data.outPort == -1)
+               portOut->deactivate();
 
-#endif
+       portIn->showItem(m_data.inPort);
+       portIn->onChange = [this](ID id) { m_data.inPort = id; };
+       if (m_data.inPort == -1)
+               portIn->deactivate();
 
-       switch (m::conf::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;
-       }
+       enableOut->copy_tooltip("Enable Output port");
+       enableOut->value(m_data.outPort != -1);
+       enableOut->onChange = [this](bool b) {
+               if (b)
+               {
+                       m_data.outPort = portOut->value();
+                       portOut->activate();
+               }
+               else
+               {
+                       m_data.outPort = -1;
+                       portOut->deactivate();
+               }
+       };
+
+       enableIn->copy_tooltip("Enable Input port");
+       enableIn->value(m_data.inPort != -1);
+       enableIn->onChange = [this](bool b) {
+               if (b)
+               {
+                       m_data.inPort = portIn->value();
+                       portIn->activate();
+               }
+               else
+               {
+                       m_data.inPort = -1;
+                       portIn->deactivate();
+               }
+       };
+
+       midiMap->showItem(m_data.midiMap);
+       midiMap->onChange = [this](ID id) { m_data.midiMap = id; };
+
+       for (const auto& [key, value] : m_data.syncModes)
+               sync->addItem(value.c_str(), key);
+       sync->showItem(m_data.syncMode);
+       sync->onChange = [this](ID id) { m_data.syncMode = id; };
 }
 
 /* -------------------------------------------------------------------------- */
 
-void geTabMidi::cb_changeSystem(Fl_Widget* /*w*/, void* p) { ((geTabMidi*)p)->cb_changeSystem(); }
-
-/* -------------------------------------------------------------------------- */
-
-void geTabMidi::cb_changeSystem()
+void geTabMidi::invalidate()
 {
-       /* 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 m::kernelMidi. */
+       /* 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 re-using
+       previous data. */
 
-       if (systemInitValue == system->value())
+       if (m_initialApi == m_data.api && m_initialApi != -1)
        {
-               portOut->clear();
-               fetchOutPorts();
                portOut->activate();
-               portIn->clear();
-               fetchInPorts();
                portIn->activate();
+               enableOut->activate();
+               enableIn->activate();
+               if (m_data.midiMaps.size() > 0)
+                       midiMap->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);
+               enableOut->deactivate();
+               enableIn->deactivate();
+               midiMap->deactivate();
                sync->deactivate();
        }
 }
-} // namespace v
-} // namespace giada
\ No newline at end of file
+
+/* -------------------------------------------------------------------------- */
+
+void geTabMidi::save() const
+{
+       c::config::save(m_data);
+}
+} // namespace giada::v
index 658bc5ac1a7c6729f4d33ab0b3b0d1ff4c9b4b2d..b2374a3d1723ee1e5015b078c2acaef0b8624fa7 100644 (file)
 #ifndef GE_TAB_MIDI_H
 #define GE_TAB_MIDI_H
 
+#include "glue/config.h"
+#include "gui/elems/basics/choice.h"
 #include <FL/Fl_Group.H>
 
 class geCheck;
 
-namespace giada
+namespace giada::v
 {
-namespace v
-{
-class geChoice;
 class geTabMidi : public Fl_Group
 {
 public:
+       struct geMenu : public geChoice
+       {
+               geMenu(int x, int y, int w, int h, const char* l, const std::vector<std::string>&,
+                   const std::string& msgIfNotFound);
+       };
+
        geTabMidi(int x, int y, int w, int h);
 
-       void save();
+       void save() const;
 
        geChoice* system;
-       geChoice* portOut;
-       geChoice* portIn;
-       geChoice* midiMap;
+       geMenu*   portOut;
+       geMenu*   portIn;
+       geCheck*  enableOut;
+       geCheck*  enableIn;
+       geMenu*   midiMap;
        geChoice* sync;
 
-  private:
-       void fetchSystems();
-       void fetchOutPorts();
-       void fetchInPorts();
-       void fetchMidiMaps();
+private:
+       void invalidate();
 
-       static void cb_changeSystem(Fl_Widget* /*w*/, void* p);
-       void        cb_changeSystem();
+       c::config::MidiData m_data;
 
-       int systemInitValue;
+       int m_initialApi;
 };
-} // namespace v
-} // namespace giada
+} // namespace giada::v
 
 #endif
index b6917fa3f34787889181f260263cb63d98c4f047..bcd66ad2921550bec05f892c9f3b0b1436d13e09 100644 (file)
  * -------------------------------------------------------------------------- */
 
 #include "tabMisc.h"
-#include "core/conf.h"
 #include "core/const.h"
-#include <FL/Fl_Tooltip.H>
 
 namespace giada::v
 {
-geTabMisc::geTabMisc(int X, int Y, int W, int H)
+geTabMisc::geTabMisc(int X, int Y, int W)
 : geGroup(X, Y)
+, m_data(c::config::getMiscData())
 , m_debugMsg(W - 230, 9, 230, 20, "Debug messages")
 , m_tooltips(W - 230, 37, 230, 20, "Tooltips")
 {
        add(&m_debugMsg);
        add(&m_tooltips);
 
-       m_debugMsg.add("Disabled");
-       m_debugMsg.add("To standard output");
-       m_debugMsg.add("To file");
+       m_debugMsg.addItem("Disabled");
+       m_debugMsg.addItem("To standard output");
+       m_debugMsg.addItem("To file");
+       m_debugMsg.onChange = [this](ID id) { m_data.logMode = id; };
 
-       m_tooltips.add("Disabled");
-       m_tooltips.add("Enabled");
+       m_tooltips.addItem("Disabled");
+       m_tooltips.addItem("Enabled");
+       m_tooltips.onChange = [this](ID id) { m_data.showTooltips = id; };
 
-       switch (m::conf::conf.logMode)
-       {
-       case LOG_MODE_MUTE:
-               m_debugMsg.value(0);
-               break;
-       case LOG_MODE_STDOUT:
-               m_debugMsg.value(1);
-               break;
-       case LOG_MODE_FILE:
-               m_debugMsg.value(2);
-               break;
-       }
-
-       m_tooltips.value(m::conf::conf.showTooltips);
+       m_debugMsg.showItem(m_data.logMode);
+       m_tooltips.showItem(m_data.showTooltips);
 
        copy_label("Misc");
        labelsize(G_GUI_FONT_SIZE_BASE);
@@ -70,20 +59,6 @@ geTabMisc::geTabMisc(int X, int Y, int W, int H)
 
 void geTabMisc::save()
 {
-       switch (m_debugMsg.value())
-       {
-       case 0:
-               m::conf::conf.logMode = LOG_MODE_MUTE;
-               break;
-       case 1:
-               m::conf::conf.logMode = LOG_MODE_STDOUT;
-               break;
-       case 2:
-               m::conf::conf.logMode = LOG_MODE_FILE;
-               break;
-       }
-
-       m::conf::conf.showTooltips = m_tooltips.value();
-       Fl_Tooltip::enable(m_tooltips.value());
+       c::config::save(m_data);
 }
 } // namespace giada::v
\ No newline at end of file
index 9f0058c1c747339e0db0e850e321b046d9c7b3c0..f854eee742863c8ba737af0b1536b718f54eb8ba 100644 (file)
 #ifndef GE_TAB_MISC_H
 #define GE_TAB_MISC_H
 
+#include "glue/config.h"
 #include "gui/elems/basics/choice.h"
 #include "gui/elems/basics/group.h"
 
 namespace giada::v
 {
-class geChoice;
 class geTabMisc : public geGroup
 {
 public:
-       geTabMisc(int x, int y, int w, int h);
+       geTabMisc(int x, int y, int w);
 
        void save();
 
-  private:
+private:
+       c::config::MiscData m_data;
+
        geChoice m_debugMsg;
        geChoice m_tooltips;
 };
index b2c4fa156e3bb1f23404c6ed59f4dd8296f906ea..49eb2ae4984e3a72db6319a6b7c4415cc5c5d4c7 100644 (file)
 #include "core/conf.h"
 #include "core/const.h"
 #include "core/graphics.h"
-#include "core/plugins/pluginManager.h"
+#include "glue/layout.h"
 #include "glue/plugin.h"
-#include "gui/dialogs/browser/browserDir.h"
-#include "gui/dialogs/mainWindow.h"
 #include "gui/dialogs/window.h"
 #include "gui/elems/basics/box.h"
 #include "gui/elems/basics/button.h"
 #include "gui/elems/basics/check.h"
 #include "gui/elems/basics/input.h"
-#include "utils/fs.h"
 #include "utils/gui.h"
 #include "utils/string.h"
 #include <FL/Fl.H>
 #include <functional>
 
-extern giada::v::gdMainWindow* G_MainWin;
-
-namespace giada
-{
-namespace v
+namespace giada::v
 {
 geTabPlugins::geTabPlugins(int X, int Y, int W, int H)
-: Fl_Group(X, Y, W, H, "Plugins")
+: Fl_Group(X, Y, W, H, "Plug-ins")
+, m_browse(x() + w() - G_GUI_UNIT, y() + 9, G_GUI_UNIT, G_GUI_UNIT, "", zoomInOff_xpm, zoomInOn_xpm)
+, m_folderPath(m_browse.x() - 258, y() + 9, 250, G_GUI_UNIT)
+, m_scanButton(x() + w() - 150, m_folderPath.y() + m_folderPath.h() + 8, 150, G_GUI_UNIT)
+, m_info(x(), m_scanButton.y() + m_scanButton.h() + 8, w(), 240)
 {
-       m_browse     = new geButton(x() + w() - G_GUI_UNIT, y() + 9, G_GUI_UNIT, G_GUI_UNIT, "", zoomInOff_xpm, zoomInOn_xpm);
-       m_folderPath = new geInput(m_browse->x() - 258, y() + 9, 250, G_GUI_UNIT);
-       m_scanButton = new geButton(x() + w() - 150, m_folderPath->y() + m_folderPath->h() + 8, 150, G_GUI_UNIT);
-       m_info       = new geBox(x(), m_scanButton->y() + m_scanButton->h() + 8, w(), 240);
-
        end();
 
        labelsize(G_GUI_FONT_SIZE_BASE);
        selection_color(G_COLOR_GREY_4);
 
-       m_info->label("Scan in progress. Please wait...");
-       m_info->hide();
-
-       m_folderPath->value(m::conf::conf.pluginPath.c_str());
-       m_folderPath->label("Plugins folder");
+       m_info.hide();
 
-       m_browse->callback(cb_browse, (void*)this);
+       m_folderPath.label("Plug-ins folder");
+       m_folderPath.onChange = [this](const std::string& v) {
+               m_data.pluginPath = v;
+       };
 
-       m_scanButton->callback(cb_scan, (void*)this);
+       m_browse.callback(cb_browse, (void*)this);
+       m_scanButton.callback(cb_scan, (void*)this);
 
-       refreshCount();
+       rebuild();
 }
 
 /* -------------------------------------------------------------------------- */
 
-void geTabPlugins::refreshCount()
+void geTabPlugins::rebuild()
 {
-       std::string scanLabel = "Scan (" + std::to_string(m::pluginManager::countAvailablePlugins()) + " found)";
-       m_scanButton->copy_label(scanLabel.c_str());
+       m_data = c::config::getPluginData();
+
+       const std::string scanLabel = "Scan (" + std::to_string(m_data.numAvailablePlugins) + " found)";
+       m_scanButton.copy_label(scanLabel.c_str());
+
+       m_folderPath.value(m_data.pluginPath.c_str());
+       m_folderPath.redraw();
 }
 
 /* -------------------------------------------------------------------------- */
@@ -94,10 +91,7 @@ void geTabPlugins::cb_browse(Fl_Widget* /*w*/, void* p) { ((geTabPlugins*)p)->cb
 
 void geTabPlugins::cb_browse()
 {
-       v::gdBrowserDir* browser = new v::gdBrowserDir("Add plug-ins directory",
-           m::conf::conf.patchPath, c::plugin::setPluginPathCb);
-
-       static_cast<v::gdWindow*>(top_window())->addSubWindow(browser);
+       c::layout::openBrowserForPlugins(*static_cast<v::gdWindow*>(top_window()));
 }
 
 /* -------------------------------------------------------------------------- */
@@ -106,32 +100,22 @@ void geTabPlugins::cb_scan()
 {
        std::function<void(float)> callback = [this](float progress) {
                std::string l = "Scan in progress (" + std::to_string((int)(progress * 100)) + "%). Please wait...";
-               m_info->label(l.c_str());
+               m_info.label(l.c_str());
                Fl::wait();
        };
 
-       m_info->show();
-       m::pluginManager::scanDirs(m_folderPath->value(), callback);
-       m::pluginManager::saveList(u::fs::getHomePath() + G_SLASH + "plugins.xml");
-       m_info->hide();
-       refreshCount();
+       m_info.show();
+       c::config::scanPlugins(m_folderPath.value(), callback);
+       m_info.hide();
+       rebuild();
 }
 
 /* -------------------------------------------------------------------------- */
 
 void geTabPlugins::save()
 {
-       m::conf::conf.pluginPath = m_folderPath->value();
-}
-
-/* -------------------------------------------------------------------------- */
-
-void geTabPlugins::refreshVstPath()
-{
-       m_folderPath->value(m::conf::conf.pluginPath.c_str());
-       m_folderPath->redraw();
+       c::config::save(m_data);
 }
-} // namespace v
-} // namespace giada
+} // namespace giada::v
 
-#endif // WITH_VST
+#endif // WITH_VST
\ No newline at end of file
index f1af634e6ee29c4e0285dc15b6621d58ab580604..aeac23a02555a25f8e342449863bd7381f2b26e8 100644 (file)
 
 #ifdef WITH_VST
 
+#include "glue/config.h"
+#include "gui/elems/basics/box.h"
+#include "gui/elems/basics/button.h"
+#include "gui/elems/basics/input.h"
 #include <FL/Fl_Group.H>
 
-class geInput;
-class geButton;
-class geBox;
-
-namespace giada
-{
-namespace v
+namespace giada::v
 {
 class geTabPlugins : public Fl_Group
 {
@@ -45,23 +43,22 @@ public:
        geTabPlugins(int x, int y, int w, int h);
 
        void save();
-       void refreshVstPath();
+       void rebuild();
 
-  private:
+private:
        static void cb_scan(Fl_Widget* /*w*/, void* p);
        static void cb_browse(Fl_Widget* /*w*/, void* p);
        void        cb_scan();
        void        cb_browse();
 
-       void refreshCount();
+       c::config::PluginData m_data;
 
-       geInput*  m_folderPath;
-       geButton* m_browse;
-       geButton* m_scanButton;
-       geBox*    m_info;
+       geButton m_browse;
+       geInput  m_folderPath;
+       geButton m_scanButton;
+       geBox    m_info;
 };
-} // namespace v
-} // namespace giada
+} // namespace giada::v
 
 #endif // WITH_VST
 
index 9d80c4b27753745af7420a1debd02b68d9b070ea..48eb0947980065aaf8acfc0677a8aab938e5720e 100644 (file)
  * -------------------------------------------------------------------------- */
 
 #include "glue/channel.h"
-#include "channel.h"
-#include "channelButton.h"
-#include "channelStatus.h"
-#include "column.h"
-#include "core/const.h"
 #include "core/graphics.h"
-#include "core/model/model.h"
-#include "core/plugins/pluginHost.h"
 #include "glue/events.h"
-#include "gui/dialogs/mainWindow.h"
-#include "gui/dialogs/pluginList.h"
+#include "glue/layout.h"
 #include "gui/elems/basics/button.h"
 #include "gui/elems/basics/dial.h"
 #include "gui/elems/basics/statusButton.h"
-#include "utils/gui.h"
+#include "gui/elems/mainWindow/keyboard/channel.h"
+#include "gui/elems/mainWindow/keyboard/channelButton.h"
+#include "gui/elems/mainWindow/keyboard/channelStatus.h"
+#include "gui/elems/mainWindow/keyboard/column.h"
+#include "gui/ui.h"
 #include <FL/Fl.H>
 #include <FL/fl_draw.H>
 
-extern giada::v::gdMainWindow* G_MainWin;
+extern giada::v::Ui g_ui;
 
 namespace giada::v
 {
@@ -137,7 +133,7 @@ void geChannel::cb_changeVol()
 #ifdef WITH_VST
 void geChannel::cb_openFxWindow()
 {
-       u::gui::openSubWindow(G_MainWin, new v::gdPluginList(m_channel.id), WID_FX_LIST);
+       c::layout::openChannelPluginListWindow(m_channel.id);
 }
 #endif
 
@@ -152,7 +148,7 @@ int geChannel::getColumnId()
 
 void geChannel::blink()
 {
-       if (u::gui::shouldBlink())
+       if (g_ui.shouldBlink())
                mainButton->setPlayMode();
        else
                mainButton->setDefaultMode();
index ed8dd566979f105c601a920ac48bdea1d5aabb87..e868d636bd0db72ef146b833d5ae49bffb7e3f95 100644 (file)
@@ -28,8 +28,8 @@
 #include "core/channels/channel.h"
 #include "core/const.h"
 #include "core/model/model.h"
-#include "core/recorder.h"
 #include "glue/channel.h"
+#include "src/core/actions/actions.h"
 #include "utils/string.h"
 #include <FL/fl_draw.H>
 
index b0a7351608efe2e3f771468e3c7d424614d95ad4..66e837a4f17a6e0b4babf0a4c81c9dc72e249644 100644 (file)
@@ -38,9 +38,7 @@
 #include <FL/fl_draw.H>
 #include <cassert>
 
-namespace giada
-{
-namespace v
+namespace giada::v
 {
 geChannelMode::geChannelMode(int x, int y, int w, int h, c::channel::Data& d)
 : Fl_Menu_Button(x, y, w, h)
@@ -56,6 +54,7 @@ geChannelMode::geChannelMode(int x, int y, int w, int h, c::channel::Data& d)
        add("Loop . once . bar", 0, cb_changeMode, (void*)SamplePlayerMode::LOOP_ONCE_BAR);
        add("Loop . repeat", 0, cb_changeMode, (void*)SamplePlayerMode::LOOP_REPEAT);
        add("Oneshot . basic", 0, cb_changeMode, (void*)SamplePlayerMode::SINGLE_BASIC);
+       add("Oneshot . basic . pause", 0, cb_changeMode, (void*)SamplePlayerMode::SINGLE_BASIC_PAUSE);
        add("Oneshot . press", 0, cb_changeMode, (void*)SamplePlayerMode::SINGLE_PRESS);
        add("Oneshot . retrig", 0, cb_changeMode, (void*)SamplePlayerMode::SINGLE_RETRIG);
        add("Oneshot . endless", 0, cb_changeMode, (void*)SamplePlayerMode::SINGLE_ENDLESS);
@@ -86,6 +85,9 @@ void geChannelMode::draw()
        case SamplePlayerMode::SINGLE_BASIC:
                fl_draw_pixmap(oneshotBasic_xpm, x() + 1, y() + 1);
                break;
+       case SamplePlayerMode::SINGLE_BASIC_PAUSE:
+               fl_draw_pixmap(oneshotBasicPause_xpm, x() + 1, y() + 1);
+               break;
        case SamplePlayerMode::SINGLE_PRESS:
                fl_draw_pixmap(oneshotPress_xpm, x() + 1, y() + 1);
                break;
@@ -95,6 +97,9 @@ void geChannelMode::draw()
        case SamplePlayerMode::SINGLE_ENDLESS:
                fl_draw_pixmap(oneshotEndless_xpm, x() + 1, y() + 1);
                break;
+       default:
+               assert(false);
+               break;
        }
 }
 
@@ -108,5 +113,4 @@ void geChannelMode::cb_changeMode(int mode)
 {
        c::channel::setSamplePlayerMode(m_channel.id, static_cast<SamplePlayerMode>(mode));
 }
-} // namespace v
-} // namespace giada
+} // namespace giada::v
\ No newline at end of file
index 125d868e57ba3706572c176b52848234d60ac10d..c31d923119d538fe5ee9891963d0d4e822f7ae4f 100644 (file)
@@ -35,6 +35,7 @@ namespace giada::c::channel
 {
 struct Data;
 }
+
 namespace giada::v
 {
 class geChannelMode : public Fl_Menu_Button
@@ -44,7 +45,7 @@ public:
 
        void draw() override;
 
-  private:
+private:
        static void cb_changeMode(Fl_Widget* /*w*/, void* p);
        void        cb_changeMode(int mode);
 
index 6d9c25094c532eab6fb7e75c9974acd8c1752745..cac95155c0de2895888f593376254ee7264abb23 100644 (file)
  *
  * -------------------------------------------------------------------------- */
 
-#include "channelStatus.h"
+#include "gui/elems/mainWindow/keyboard/channelStatus.h"
 #include "core/const.h"
 #include "glue/channel.h"
+#include "utils/math.h"
 #include <FL/fl_draw.H>
 
 namespace giada::v
@@ -44,9 +45,12 @@ void geChannelStatus::draw()
        fl_rect(x(), y(), w(), h(), G_COLOR_GREY_4);                  // reset border
        fl_rectf(x() + 1, y() + 1, w() - 2, h() - 2, G_COLOR_GREY_2); // reset background
 
-       ChannelStatus playStatus = m_channel.getPlayStatus();
-       ChannelStatus recStatus  = m_channel.getRecStatus();
-       Pixel         pos        = 0;
+       const ChannelStatus playStatus = m_channel.getPlayStatus();
+       const ChannelStatus recStatus  = m_channel.getRecStatus();
+       const Frame         tracker    = m_channel.sample->getTracker();
+       const Frame         begin      = m_channel.sample->getBegin();
+       const Frame         end        = m_channel.sample->getEnd();
+       const Pixel         pos        = u::math::map(tracker, begin, end, 0, w());
 
        if (playStatus == ChannelStatus::WAIT ||
            playStatus == ChannelStatus::ENDING ||
@@ -57,18 +61,13 @@ void geChannelStatus::draw()
        }
        else if (playStatus == ChannelStatus::PLAY)
        {
-               /* Equation for the progress bar: 
-               ((chanTracker - chanStart) * w()) / (chanEnd - chanStart). */
-               Frame tracker = m_channel.sample->getTracker();
-               Frame begin   = m_channel.sample->getBegin();
-               Frame end     = m_channel.sample->getEnd();
-               pos           = ((tracker - begin) * (w() - 1)) / ((end - begin));
                fl_rect(x(), y(), w(), h(), G_COLOR_LIGHT_1);
+               fl_rectf(x() + 1, y() + 1, pos, h() - 2, G_COLOR_LIGHT_1);
        }
        else
+       {
                fl_rectf(x() + 1, y() + 1, w() - 2, h() - 2, G_COLOR_GREY_2); // status empty
-
-       if (pos != 0)
-               fl_rectf(x() + 1, y() + 1, pos, h() - 2, G_COLOR_LIGHT_1);
+               fl_rectf(x() + 1, y() + 1, pos, h() - 2, G_COLOR_GREY_4);
+       }
 }
 } // namespace giada::v
\ No newline at end of file
index 1074deed4e8f8161fd0af05de116532838bf87c9..72982b583438e4e70cbe581aeed69a744342be07 100644 (file)
@@ -34,6 +34,7 @@
 #include "midiChannel.h"
 #include "sampleChannel.h"
 #include "utils/fs.h"
+#include "utils/gui.h"
 #include "utils/log.h"
 #include "utils/string.h"
 #include <FL/Fl_Menu_Button.H>
@@ -76,20 +77,21 @@ geChannel* geColumn::addChannel(c::channel::Data d)
                gch = new geMidiChannel(x(), last->y() + last->h() + G_GUI_INNER_MARGIN, w(), d.height, d);
 
        geResizerBar* bar = new geResizerBar(x(), gch->y() + gch->h(), w(),
-           G_GUI_INNER_MARGIN, G_GUI_UNIT, geResizerBar::VERTICAL, gch);
+           G_GUI_INNER_MARGIN, G_GUI_UNIT, geResizerBar::Direction::VERTICAL,
+           geResizerBar::Mode::MOVE);
 
        /* Update the column height while dragging the resizer bar. */
 
-       bar->onDrag = [this](const Fl_Widget* /*w*/) {
+       bar->onDrag = [this](const Fl_Widget& /*w*/) {
                resizable(nullptr);
                size(this->w(), (child(children() - 1)->y() - y()) + G_GUI_INNER_MARGIN);
        };
 
        /* Store the channel height in model when the resizer bar is released. */
 
-       bar->onRelease = [channelId = d.id, this](const Fl_Widget* w) {
+       bar->onRelease = [channelId = d.id, this](const Fl_Widget& w) {
                resizable(this);
-               c::channel::setHeight(channelId, w->h());
+               c::channel::setHeight(channelId, w.h());
        };
 
        m_channels.push_back(gch);
@@ -115,10 +117,10 @@ void geColumn::cb_addChannel()
        u::log::print("[geColumn::cb_addChannel] id = %d\n", id);
 
        Fl_Menu_Item menu[] = {
-           {"Add Sample channel"},
-           {"Add MIDI channel"},
-           {"Remove"},
-           {0}};
+           u::gui::makeMenuItem("Add Sample channel"),
+           u::gui::makeMenuItem("Add MIDI channel"),
+           u::gui::makeMenuItem("Remove"),
+           {}};
 
        if (countChannels() > 0)
                menu[2].deactivate();
index 43ebdf225141d50ba565566568b525ef6f125bb1..2ab96d5bf8994f3fc7d926fd323f0465faaf3f59 100644 (file)
 #include <vector>
 
 class geButton;
-class geResizerBar;
 
 namespace giada::v
 {
+class geResizerBar;
 class geKeyboard;
 class geChannel;
 class geColumn : public Fl_Group
@@ -65,7 +65,7 @@ public:
 
        geResizerBar* resizerBar;
 
-  private:
+private:
        static void cb_addChannel(Fl_Widget* /*w*/, void* p);
        void        cb_addChannel();
 
index c94af88d57f7aea418a48afb4ae9849ecb4aa127..7b304ec13a402abf11ce79bd56f4f44559489f7b 100644 (file)
@@ -33,6 +33,7 @@
 #include "gui/dispatcher.h"
 #include "gui/elems/basics/boxtypes.h"
 #include "gui/elems/basics/resizerBar.h"
+#include "gui/ui.h"
 #include "sampleChannel.h"
 #include "utils/fs.h"
 #include "utils/log.h"
@@ -41,9 +42,9 @@
 #include <FL/fl_draw.H>
 #include <cassert>
 
-namespace giada
-{
-namespace v
+extern giada::v::Ui g_ui;
+
+namespace giada::v
 {
 geKeyboard::geKeyboard(int X, int Y, int W, int H)
 : geScroll(X, Y, W, H, Fl_Scroll::BOTH_ALWAYS)
@@ -140,7 +141,7 @@ int geKeyboard::handle(int e)
        case FL_KEYDOWN:  // Keyboard key pushed
        case FL_KEYUP:
        { // Keyboard key released
-               dispatcher::dispatchKey(e);
+               g_ui.dispatcher.dispatchKey(e);
                return 1;
        }
        case FL_DND_ENTER: // return(1) for these events to 'accept' dnd
@@ -207,12 +208,12 @@ void geKeyboard::addColumn(int width, ID id)
 
        /* Add a new column + a new resizer bar. */
 
-       geResizerBar* bar    = new geResizerBar(colx + width, y(), COLUMN_GAP, h(), G_MIN_COLUMN_WIDTH, geResizerBar::HORIZONTAL);
+       geResizerBar* bar    = new geResizerBar(colx + width, y(), COLUMN_GAP, h(), G_MIN_COLUMN_WIDTH, geResizerBar::Direction::HORIZONTAL);
        geColumn*     column = new geColumn(colx, y(), width, G_GUI_UNIT, m_columnId.generate(id), bar);
 
        /* Store the column width in layout when the resizer bar is released. */
 
-       bar->onRelease = [=](const Fl_Widget* /*w*/) {
+       bar->onRelease = [=](const Fl_Widget& /*w*/) {
                storeLayout();
        };
 
@@ -294,5 +295,4 @@ void geKeyboard::storeLayout()
                layout.push_back({c->id, c->w()});
 }
 
-} // namespace v
-} // namespace giada
+} // namespace giada::v
\ No newline at end of file
index d9326cb97e6af93290c564db89c444691a44ea37..70d131e2ecb6af8c0bd3569b1aaac86253477cc0 100644 (file)
 #include <vector>
 
 class geButton;
-class geResizerBar;
 
-namespace giada
-{
-namespace v
+namespace giada::v
 {
+class geResizerBar;
 class geColumn;
 class geChannel;
 class geKeyboard : public geScroll
@@ -90,11 +88,11 @@ public:
        void forEachColumn(std::function<void(const geColumn& c)> f) const;
 
        /* layout
-       The column layout. Each element is a column with a specific witdh. */
+       The column layout. Each element is a column with a specific width. */
 
        std::vector<ColumnLayout> layout;
 
-  private:
+private:
        static constexpr int COLUMN_GAP = 20;
 
        static void cb_addColumn(Fl_Widget* /*w*/, void* p);
@@ -128,7 +126,6 @@ public:
 
        geButton* m_addColumnBtn;
 };
-} // namespace v
-} // namespace giada
+} // namespace giada::v
 
 #endif
index dcb3307f773afa0693eb5388e70249923989b00a..2a9368f1e690b430b470e130f6f69f17700f46c9 100644 (file)
  *
  * -------------------------------------------------------------------------- */
 
-#include "midiChannel.h"
-#include "column.h"
+#include "gui/elems/mainWindow/keyboard/midiChannel.h"
 #include "core/const.h"
 #include "core/graphics.h"
-#include "core/model/model.h"
-#include "core/recorder.h"
 #include "glue/channel.h"
 #include "glue/io.h"
+#include "glue/layout.h"
 #include "glue/recorder.h"
-#include "gui/dialogs/actionEditor/midiActionEditor.h"
-#include "gui/dialogs/channelNameInput.h"
-#include "gui/dialogs/keyGrabber.h"
-#include "gui/dialogs/mainWindow.h"
-#include "gui/dialogs/midiIO/midiInputChannel.h"
-#include "gui/dialogs/midiIO/midiOutputMidiCh.h"
-#include "gui/dialogs/pluginList.h"
 #include "gui/dialogs/warnings.h"
 #include "gui/dispatcher.h"
 #include "gui/elems/basics/boxtypes.h"
 #include "gui/elems/basics/button.h"
 #include "gui/elems/basics/dial.h"
 #include "gui/elems/basics/statusButton.h"
-#include "midiChannelButton.h"
+#include "gui/elems/mainWindow/keyboard/column.h"
+#include "gui/elems/mainWindow/keyboard/midiChannelButton.h"
 #include "utils/gui.h"
 #include "utils/string.h"
 #include <FL/Fl_Menu_Button.H>
 #include <cassert>
 
-extern giada::v::gdMainWindow* G_MainWin;
-
-namespace giada
-{
-namespace v
+namespace giada::v
 {
 namespace
 {
@@ -87,25 +75,25 @@ void menuCallback(Fl_Widget* w, void* v)
        case Menu::__END_CLEAR_ACTION_SUBMENU__:
                break;
        case Menu::EDIT_ACTIONS:
-               u::gui::openSubWindow(G_MainWin, new v::gdMidiActionEditor(data.id), WID_ACTION_EDITOR);
+               c::layout::openMidiActionEditor(data.id);
                break;
        case Menu::CLEAR_ACTIONS_ALL:
                c::recorder::clearAllActions(data.id);
                break;
        case Menu::SETUP_KEYBOARD_INPUT:
-               u::gui::openSubWindow(G_MainWin, new gdKeyGrabber(data), WID_KEY_GRABBER);
+               c::layout::openKeyGrabberWindow(data);
                break;
        case Menu::SETUP_MIDI_INPUT:
-               u::gui::openSubWindow(G_MainWin, new gdMidiInputChannel(data.id), WID_MIDI_INPUT);
+               c::layout::openChannelMidiInputWindow(data.id);
                break;
        case Menu::SETUP_MIDI_OUTPUT:
-               u::gui::openSubWindow(G_MainWin, new gdMidiOutputMidiCh(data.id), WID_MIDI_OUTPUT);
+               c::layout::openMidiChannelMidiOutputWindow(data.id);
                break;
        case Menu::CLONE_CHANNEL:
                c::channel::cloneChannel(data.id);
                break;
        case Menu::RENAME_CHANNEL:
-               u::gui::openSubWindow(G_MainWin, new gdChannelNameInput(data), WID_SAMPLE_NAME);
+               c::layout::openRenameChannelWindow(data);
                break;
        case Menu::DELETE_CHANNEL:
                c::channel::deleteChannel(data.id);
@@ -189,7 +177,7 @@ void geMidiChannel::cb_openMenu(Fl_Widget* /*w*/, void* p) { ((geMidiChannel*)p)
 
 void geMidiChannel::cb_playButton()
 {
-       v::dispatcher::dispatchTouch(*this, playButton->value());
+       m_channel.viewDispatcher.dispatchTouch(*this, playButton->value());
 }
 
 /* -------------------------------------------------------------------------- */
@@ -197,17 +185,17 @@ void geMidiChannel::cb_playButton()
 void geMidiChannel::cb_openMenu()
 {
        Fl_Menu_Item rclick_menu[] = {
-           {"Edit actions...", 0, menuCallback, (void*)Menu::EDIT_ACTIONS},
-           {"Clear actions", 0, menuCallback, (void*)Menu::CLEAR_ACTIONS, FL_SUBMENU},
-           {"All", 0, menuCallback, (void*)Menu::CLEAR_ACTIONS_ALL},
-           {0},
-           {"Setup keyboard input...", 0, menuCallback, (void*)Menu::SETUP_KEYBOARD_INPUT},
-           {"Setup MIDI input...", 0, menuCallback, (void*)Menu::SETUP_MIDI_INPUT},
-           {"Setup MIDI output...", 0, menuCallback, (void*)Menu::SETUP_MIDI_OUTPUT},
-           {"Rename", 0, menuCallback, (void*)Menu::RENAME_CHANNEL},
-           {"Clone", 0, menuCallback, (void*)Menu::CLONE_CHANNEL},
-           {"Delete", 0, menuCallback, (void*)Menu::DELETE_CHANNEL},
-           {0}};
+           u::gui::makeMenuItem("Edit actions...", menuCallback, (void*)Menu::EDIT_ACTIONS),
+           u::gui::makeMenuItem("Clear actions", menuCallback, (void*)Menu::CLEAR_ACTIONS, FL_SUBMENU),
+           u::gui::makeMenuItem("All", menuCallback, (void*)Menu::CLEAR_ACTIONS_ALL),
+           {},
+           u::gui::makeMenuItem("Setup keyboard input...", menuCallback, (void*)Menu::SETUP_KEYBOARD_INPUT),
+           u::gui::makeMenuItem("Setup MIDI input...", menuCallback, (void*)Menu::SETUP_MIDI_INPUT),
+           u::gui::makeMenuItem("Setup MIDI output...", menuCallback, (void*)Menu::SETUP_MIDI_OUTPUT),
+           u::gui::makeMenuItem("Rename", menuCallback, (void*)Menu::RENAME_CHANNEL),
+           u::gui::makeMenuItem("Clone", menuCallback, (void*)Menu::CLONE_CHANNEL),
+           u::gui::makeMenuItem("Delete", menuCallback, (void*)Menu::DELETE_CHANNEL),
+           {}};
 
        /* No 'clear actions' if there are no actions. */
 
@@ -246,6 +234,4 @@ void geMidiChannel::resize(int X, int Y, int W, int H)
 
        packWidgets();
 }
-
-} // namespace v
-} // namespace giada
+} // namespace giada::v
\ No newline at end of file
index 690220a0cc111cf7dfa1d8e862443c08a29db18a..db9fcd1ad476c5e2dc61ee9a0b75c8a985664dd9 100644 (file)
  *
  * -------------------------------------------------------------------------- */
 
-#include "sampleChannel.h"
-#include "channelMode.h"
-#include "channelStatus.h"
-#include "column.h"
-#include "core/channels/channel.h"
-#include "core/channels/samplePlayer.h"
-#include "core/clock.h"
-#include "core/conf.h"
+#include "gui/elems/mainWindow/keyboard/sampleChannel.h"
 #include "core/graphics.h"
-#include "core/mixer.h"
-#include "core/model/model.h"
-#include "core/recManager.h"
-#include "core/recorder.h"
-#include "core/wave.h"
 #include "glue/channel.h"
 #include "glue/events.h"
 #include "glue/io.h"
+#include "glue/layout.h"
 #include "glue/recorder.h"
 #include "glue/storage.h"
-#include "gui/dialogs/actionEditor/sampleActionEditor.h"
-#include "gui/dialogs/browser/browserLoad.h"
-#include "gui/dialogs/browser/browserSave.h"
-#include "gui/dialogs/channelNameInput.h"
-#include "gui/dialogs/keyGrabber.h"
-#include "gui/dialogs/mainWindow.h"
-#include "gui/dialogs/midiIO/midiInputChannel.h"
-#include "gui/dialogs/midiIO/midiOutputSampleCh.h"
-#include "gui/dialogs/sampleEditor.h"
-#include "gui/dialogs/warnings.h"
 #include "gui/dispatcher.h"
 #include "gui/elems/basics/boxtypes.h"
 #include "gui/elems/basics/button.h"
 #include "gui/elems/basics/dial.h"
 #include "gui/elems/basics/statusButton.h"
-#include "keyboard.h"
-#include "sampleChannelButton.h"
+#include "gui/elems/mainWindow/keyboard/channelMode.h"
+#include "gui/elems/mainWindow/keyboard/channelStatus.h"
+#include "gui/elems/mainWindow/keyboard/column.h"
+#include "gui/elems/mainWindow/keyboard/keyboard.h"
+#include "gui/elems/mainWindow/keyboard/sampleChannelButton.h"
 #include "utils/gui.h"
-#include <cassert>
 
-extern giada::v::gdMainWindow* G_MainWin;
-
-namespace giada
-{
-namespace v
+namespace giada::v
 {
 namespace
 {
@@ -114,46 +91,37 @@ void menuCallback(Fl_Widget* w, void* v)
        }
        case Menu::LOAD_SAMPLE:
        {
-               gdWindow* w = new gdBrowserLoad("Browse sample",
-                   m::conf::conf.samplePath.c_str(), c::storage::loadSample, data.id);
-               u::gui::openSubWindow(G_MainWin, w, WID_FILE_BROWSER);
+               c::layout::openBrowserForSampleLoad(data.id);
                break;
        }
        case Menu::EXPORT_SAMPLE:
        {
-               gdWindow* w = new gdBrowserSave("Save sample",
-                   m::conf::conf.samplePath.c_str(), "", c::storage::saveSample, data.id);
-               u::gui::openSubWindow(G_MainWin, w, WID_FILE_BROWSER);
+               c::layout::openBrowserForSampleSave(data.id);
                break;
        }
        case Menu::SETUP_KEYBOARD_INPUT:
        {
-               u::gui::openSubWindow(G_MainWin, new gdKeyGrabber(data),
-                   WID_KEY_GRABBER);
+               c::layout::openKeyGrabberWindow(data);
                break;
        }
        case Menu::SETUP_MIDI_INPUT:
        {
-               u::gui::openSubWindow(G_MainWin, new gdMidiInputChannel(data.id),
-                   WID_MIDI_INPUT);
+               c::layout::openChannelMidiInputWindow(data.id);
                break;
        }
        case Menu::SETUP_MIDI_OUTPUT:
        {
-               u::gui::openSubWindow(G_MainWin, new gdMidiOutputSampleCh(data.id),
-                   WID_MIDI_OUTPUT);
+               c::layout::openSampleChannelMidiOutputWindow(data.id);
                break;
        }
        case Menu::EDIT_SAMPLE:
        {
-               u::gui::openSubWindow(G_MainWin, new gdSampleEditor(data.id),
-                   WID_SAMPLE_EDITOR);
+               c::layout::openSampleEditor(data.id);
                break;
        }
        case Menu::EDIT_ACTIONS:
        {
-               u::gui::openSubWindow(G_MainWin, new gdSampleActionEditor(data.id),
-                   WID_ACTION_EDITOR);
+               c::layout::openSampleActionEditor(data.id);
                break;
        }
        case Menu::CLEAR_ACTIONS:
@@ -181,8 +149,7 @@ void menuCallback(Fl_Widget* w, void* v)
        }
        case Menu::RENAME_CHANNEL:
        {
-               u::gui::openSubWindow(G_MainWin, new gdChannelNameInput(data),
-                   WID_SAMPLE_NAME);
+               c::layout::openRenameChannelWindow(data);
                break;
        }
        case Menu::FREE_CHANNEL:
@@ -285,7 +252,7 @@ void geSampleChannel::cb_readActions(Fl_Widget* /*w*/, void* p) { ((geSampleChan
 
 void geSampleChannel::cb_playButton()
 {
-       v::dispatcher::dispatchTouch(*this, playButton->value());
+       m_channel.viewDispatcher.dispatchTouch(*this, playButton->value());
 }
 
 /* -------------------------------------------------------------------------- */
@@ -295,31 +262,31 @@ void geSampleChannel::cb_openMenu()
        /* If you're recording (input or actions) no menu is allowed; you can't do
        anything, especially deallocate the channel. */
 
-       if (m::recManager::isRecording())
+       if (m_channel.isRecordingAction() || m_channel.isRecordingInput())
                return;
 
        Fl_Menu_Item rclick_menu[] = {
-           {"Input monitor", 0, menuCallback, (void*)Menu::INPUT_MONITOR,
-               FL_MENU_TOGGLE | (m_channel.sample->getInputMonitor() ? FL_MENU_VALUE : 0)},
-           {"Overdub protection", 0, menuCallback, (void*)Menu::OVERDUB_PROTECTION,
-               FL_MENU_TOGGLE | FL_MENU_DIVIDER | (m_channel.sample->getOverdubProtection() ? FL_MENU_VALUE : 0)},
-           {"Load new sample...", 0, menuCallback, (void*)Menu::LOAD_SAMPLE},
-           {"Export sample to file...", 0, menuCallback, (void*)Menu::EXPORT_SAMPLE},
-           {"Setup keyboard input...", 0, menuCallback, (void*)Menu::SETUP_KEYBOARD_INPUT},
-           {"Setup MIDI input...", 0, menuCallback, (void*)Menu::SETUP_MIDI_INPUT},
-           {"Setup MIDI output...", 0, menuCallback, (void*)Menu::SETUP_MIDI_OUTPUT},
-           {"Edit sample...", 0, menuCallback, (void*)Menu::EDIT_SAMPLE},
-           {"Edit actions...", 0, menuCallback, (void*)Menu::EDIT_ACTIONS},
-           {"Clear actions", 0, menuCallback, (void*)Menu::CLEAR_ACTIONS, FL_SUBMENU},
-           {"All", 0, menuCallback, (void*)Menu::CLEAR_ACTIONS_ALL},
-           {"Volume", 0, menuCallback, (void*)Menu::CLEAR_ACTIONS_VOLUME},
-           {"Start/Stop", 0, menuCallback, (void*)Menu::CLEAR_ACTIONS_START_STOP},
-           {0},
-           {"Rename", 0, menuCallback, (void*)Menu::RENAME_CHANNEL},
-           {"Clone", 0, menuCallback, (void*)Menu::CLONE_CHANNEL},
-           {"Free", 0, menuCallback, (void*)Menu::FREE_CHANNEL},
-           {"Delete", 0, menuCallback, (void*)Menu::DELETE_CHANNEL},
-           {0}};
+           u::gui::makeMenuItem("Input monitor", menuCallback, (void*)Menu::INPUT_MONITOR,
+               FL_MENU_TOGGLE | (m_channel.sample->getInputMonitor() ? FL_MENU_VALUE : 0)),
+           u::gui::makeMenuItem("Overdub protection", menuCallback, (void*)Menu::OVERDUB_PROTECTION,
+               FL_MENU_TOGGLE | FL_MENU_DIVIDER | (m_channel.sample->getOverdubProtection() ? FL_MENU_VALUE : 0)),
+           u::gui::makeMenuItem("Load new sample...", menuCallback, (void*)Menu::LOAD_SAMPLE),
+           u::gui::makeMenuItem("Export sample to file...", menuCallback, (void*)Menu::EXPORT_SAMPLE),
+           u::gui::makeMenuItem("Setup keyboard input...", menuCallback, (void*)Menu::SETUP_KEYBOARD_INPUT),
+           u::gui::makeMenuItem("Setup MIDI input...", menuCallback, (void*)Menu::SETUP_MIDI_INPUT),
+           u::gui::makeMenuItem("Setup MIDI output...", menuCallback, (void*)Menu::SETUP_MIDI_OUTPUT),
+           u::gui::makeMenuItem("Edit sample...", menuCallback, (void*)Menu::EDIT_SAMPLE),
+           u::gui::makeMenuItem("Edit actions...", menuCallback, (void*)Menu::EDIT_ACTIONS),
+           u::gui::makeMenuItem("Clear actions", menuCallback, (void*)Menu::CLEAR_ACTIONS, FL_SUBMENU),
+           u::gui::makeMenuItem("All", menuCallback, (void*)Menu::CLEAR_ACTIONS_ALL),
+           u::gui::makeMenuItem("Volume", menuCallback, (void*)Menu::CLEAR_ACTIONS_VOLUME),
+           u::gui::makeMenuItem("Start/Stop", menuCallback, (void*)Menu::CLEAR_ACTIONS_START_STOP),
+           {},
+           u::gui::makeMenuItem("Rename", menuCallback, (void*)Menu::RENAME_CHANNEL),
+           u::gui::makeMenuItem("Clone", menuCallback, (void*)Menu::CLONE_CHANNEL),
+           u::gui::makeMenuItem("Free", menuCallback, (void*)Menu::FREE_CHANNEL),
+           u::gui::makeMenuItem("Delete", menuCallback, (void*)Menu::DELETE_CHANNEL),
+           {}};
 
        if (m_channel.sample->waveId == 0)
        {
@@ -423,5 +390,4 @@ void geSampleChannel::resize(int X, int Y, int W, int H)
 
        packWidgets();
 }
-} // namespace v
-} // namespace giada
+} // namespace giada::v
\ No newline at end of file
index 3b0ec91fdd6c30e79144a09bda41d2f7472273e6..32cfc992358dff33a7eee11d502cf15bbe6c7d16 100644 (file)
@@ -47,7 +47,7 @@ public:
        geChannelMode*  modeBox;
        geStatusButton* readActions;
 
-  private:
+private:
        static void cb_playButton(Fl_Widget* /*w*/, void* p);
        static void cb_openMenu(Fl_Widget* /*w*/, void* p);
        static void cb_readActions(Fl_Widget* /*w*/, void* p);
index e8c163e9153f9de654489ee99120a205d8be33ba..73f5fea20b18e93d26ef86f12b5bc761da1d14cc 100644 (file)
 #include "utils/string.h"
 #include <FL/Fl.H>
 
-extern giada::v::gdMainWindow* G_MainWin;
-
-namespace giada
-{
-namespace v
+namespace giada::v
 {
 geSampleChannelButton::geSampleChannelButton(int x, int y, int w, int h, const c::channel::Data& d)
 : geChannelButton(x, y, w, h, d)
@@ -91,6 +87,4 @@ int geSampleChannelButton::handle(int e)
        }
        return ret;
 }
-
-} // namespace v
-} // namespace giada
+} // namespace giada::v
index f89f8b334b561c0c531b3c455db1d5110fc45d8a..cc4a4087477f5794458947a50cefe24d11914190 100644 (file)
 *
 * --------------------------------------------------------------------------- */
 
-#include "mainIO.h"
+#include "gui/elems/mainWindow/mainIO.h"
 #include "core/const.h"
 #include "core/graphics.h"
 #include "glue/channel.h"
 #include "glue/events.h"
+#include "glue/layout.h"
 #include "glue/main.h"
-#include "gui/dialogs/mainWindow.h"
-#include "gui/dialogs/pluginList.h"
 #include "gui/elems/basics/dial.h"
 #include "gui/elems/basics/statusButton.h"
 #include "gui/elems/soundMeter.h"
 #include "utils/gui.h"
 
-extern giada::v::gdMainWindow* G_MainWin;
-
-namespace giada
-{
-namespace v
+namespace giada::v
 {
 geMainIO::geMainIO(int x, int y)
 : gePack(x, y, Direction::HORIZONTAL)
@@ -127,12 +122,12 @@ void geMainIO::cb_inToOut()
 
 void geMainIO::cb_masterFxOut()
 {
-       u::gui::openSubWindow(G_MainWin, new v::gdPluginList(m::mixer::MASTER_OUT_CHANNEL_ID), WID_FX_LIST);
+       c::layout::openMasterOutPluginListWindow();
 }
 
 void geMainIO::cb_masterFxIn()
 {
-       u::gui::openSubWindow(G_MainWin, new v::gdPluginList(m::mixer::MASTER_IN_CHANNEL_ID), WID_FX_LIST);
+       c::layout::openMasterInPluginListWindow();
 }
 
 #endif
@@ -169,8 +164,10 @@ void geMainIO::setMasterFxInFull(bool v)
 
 void geMainIO::refresh()
 {
-       outMeter.mixerPeak = m_io.getMasterOutPeak();
-       inMeter.mixerPeak  = m_io.getMasterInPeak();
+       outMeter.peak  = m_io.getMasterOutPeak();
+       outMeter.ready = m_io.isKernelReady();
+       inMeter.peak   = m_io.getMasterInPeak();
+       inMeter.ready  = m_io.isKernelReady();
        outMeter.redraw();
        inMeter.redraw();
 }
@@ -189,5 +186,4 @@ void geMainIO::rebuild()
        inToOut.value(m_io.inToOut);
 #endif
 }
-} // namespace v
-} // namespace giada
+} // namespace giada::v
\ No newline at end of file
index 589bd9938b7ee5a43459ec5577bd23cb9ce37558..3abc4fb86ece8fd8a1759951fb58eab59d718c27 100644 (file)
@@ -36,9 +36,7 @@
 #endif
 #include "glue/main.h"
 
-namespace giada
-{
-namespace v
+namespace giada::v
 {
 class geMainIO : public gePack
 {
@@ -55,7 +53,7 @@ public:
        void setMasterFxInFull(bool v);
 #endif
 
-  private:
+private:
        static void cb_outVol(Fl_Widget* /*w*/, void* p);
        static void cb_inVol(Fl_Widget* /*w*/, void* p);
        static void cb_inToOut(Fl_Widget* /*w*/, void* p);
@@ -81,7 +79,6 @@ public:
        geStatusButton masterFxIn;
 #endif
 };
-} // namespace v
-} // namespace giada
+} // namespace giada::v
 
 #endif
index 501555159a109aef254010ffb8112917b9b5e33d..3d57bd0349224bc1758dd1a5fcab9ed10daae99a 100644 (file)
  *
  * -------------------------------------------------------------------------- */
 
-#include "mainMenu.h"
-#include "core/conf.h"
+#include "gui/elems/mainWindow/mainMenu.h"
 #include "core/const.h"
-#include "core/mixer.h"
-#include "core/mixerHandler.h"
-#include "core/model/model.h"
 #include "core/patch.h"
+#include "glue/layout.h"
 #include "glue/main.h"
-#include "glue/storage.h"
-#include "gui/dialogs/about.h"
-#include "gui/dialogs/browser/browserLoad.h"
-#include "gui/dialogs/browser/browserSave.h"
-#include "gui/dialogs/config.h"
-#include "gui/dialogs/mainWindow.h"
-#include "gui/dialogs/midiIO/midiInputMaster.h"
-#include "gui/dialogs/warnings.h"
 #include "gui/elems/basics/boxtypes.h"
 #include "gui/elems/basics/button.h"
 #include "keyboard/keyboard.h"
 #include "utils/gui.h"
 #include <FL/Fl_Menu_Button.H>
-#include <cassert>
 
-extern giada::v::gdMainWindow* G_MainWin;
-
-namespace giada
-{
-namespace v
+namespace giada::v
 {
 geMainMenu::geMainMenu(int x, int y)
 : gePack(x, y, Direction::HORIZONTAL)
@@ -71,10 +55,10 @@ geMainMenu::geMainMenu(int x, int y)
        edit->callback(cb_edit, (void*)this);
 
        about->callback([](Fl_Widget* /*w*/, void* /*v*/) {
-               u::gui::openSubWindow(G_MainWin, new gdAbout(), WID_ABOUT);
+               c::layout::openAboutWindow();
        });
        config->callback([](Fl_Widget* /*w*/, void* /*v*/) {
-               u::gui::openSubWindow(G_MainWin, new gdConfig(400, 370), WID_CONFIG);
+               c::layout::openConfigWindow();
        });
 }
 
@@ -92,14 +76,14 @@ void geMainMenu::cb_file()
        /* An Fl_Menu_Button is made of many Fl_Menu_Item */
 
        Fl_Menu_Item menu[] = {
-           {"Open project..."},
-           {"Save project..."},
-           {"Close project"},
+           u::gui::makeMenuItem("Open project..."),
+           u::gui::makeMenuItem("Save project..."),
+           u::gui::makeMenuItem("Close project"),
 #ifndef NDEBUG
-           {"Debug stats"},
+           u::gui::makeMenuItem("Debug stats"),
 #endif
-           {"Quit Giada"},
-           {0}};
+           u::gui::makeMenuItem("Quit Giada"),
+           {}};
 
        Fl_Menu_Button b(0, 0, 100, 50);
        b.box(G_CUSTOM_BORDER_BOX);
@@ -113,29 +97,25 @@ void geMainMenu::cb_file()
 
        if (strcmp(m->label(), "Open project...") == 0)
        {
-               gdWindow* childWin = new gdBrowserLoad("Open project",
-                   conf::conf.patchPath, c::storage::loadProject, 0);
-               u::gui::openSubWindow(G_MainWin, childWin, WID_FILE_BROWSER);
+               c::layout::openBrowserForProjectLoad();
        }
        else if (strcmp(m->label(), "Save project...") == 0)
        {
-               gdWindow* childWin = new gdBrowserSave("Save project", conf::conf.patchPath,
-                   patch::patch.name, c::storage::saveProject, 0);
-               u::gui::openSubWindow(G_MainWin, childWin, WID_FILE_BROWSER);
+               c::layout::openBrowserForProjectSave();
        }
        else if (strcmp(m->label(), "Close project") == 0)
        {
                c::main::closeProject();
        }
-#ifndef NDEBUG
+#ifdef G_DEBUG_MODE
        else if (strcmp(m->label(), "Debug stats") == 0)
        {
-               m::model::debug();
+               c::main::printDebugInfo();
        }
 #endif
        else if (strcmp(m->label(), "Quit Giada") == 0)
        {
-               G_MainWin->do_callback();
+               c::main::quitGiada();
        }
 }
 
@@ -143,19 +123,21 @@ void geMainMenu::cb_file()
 
 void geMainMenu::cb_edit()
 {
-       Fl_Menu_Item menu[] = {
-           {"Free all Sample channels"},
-           {"Clear all actions"},
-           {"Setup global MIDI input..."},
-           {0}};
+       c::main::MainMenu menu = c::main::getMainMenu();
 
-       menu[0].deactivate();
-       menu[1].deactivate();
+       Fl_Menu_Item menuItem[] = {
+           u::gui::makeMenuItem("Free all Sample channels"),
+           u::gui::makeMenuItem("Clear all actions"),
+           u::gui::makeMenuItem("Setup global MIDI input..."),
+           {}};
 
-       if (m::mh::hasAudioData())
-               menu[0].activate();
-       if (m::mh::hasActions())
-               menu[1].activate();
+       menuItem[0].deactivate();
+       menuItem[1].deactivate();
+
+       if (menu.hasAudioData)
+               menuItem[0].activate();
+       if (menu.hasActions)
+               menuItem[1].activate();
 
        Fl_Menu_Button b(0, 0, 100, 50);
        b.box(G_CUSTOM_BORDER_BOX);
@@ -163,7 +145,7 @@ void geMainMenu::cb_edit()
        b.textcolor(G_COLOR_LIGHT_2);
        b.color(G_COLOR_GREY_2);
 
-       const Fl_Menu_Item* m = menu->popup(Fl::event_x(), Fl::event_y(), 0, 0, &b);
+       const Fl_Menu_Item* m = menuItem->popup(Fl::event_x(), Fl::event_y(), 0, 0, &b);
        if (!m)
                return;
 
@@ -172,8 +154,6 @@ void geMainMenu::cb_edit()
        else if (strcmp(m->label(), "Clear all actions") == 0)
                c::main::clearAllActions();
        else if (strcmp(m->label(), "Setup global MIDI input...") == 0)
-               u::gui::openSubWindow(G_MainWin, new gdMidiInputMaster(), WID_MIDI_INPUT);
+               c::layout::openMasterMidiInputWindow();
 }
-
-} // namespace v
-} // namespace giada
+} // namespace giada::v
index c6a317ed97ffe1255f9f2e81472fb97f13e77df8..3f14b29fd11c19e2b453fab28a42f18da285b254 100644 (file)
 
 class geButton;
 
-namespace giada
-{
-namespace v
+namespace giada::v
 {
 class geMainMenu : public gePack
 {
 public:
        geMainMenu(int x, int y);
 
-  private:
+private:
        static void cb_file(Fl_Widget* /*w*/, void* p);
        static void cb_edit(Fl_Widget* /*w*/, void* p);
        void        cb_file();
@@ -51,7 +49,6 @@ public:
        geButton* config;
        geButton* about;
 };
-} // namespace v
-} // namespace giada
+} // namespace giada::v
 
 #endif
index 3faef6126929e7bc868e0b72ba3d5ebfb159b608..59552babe9e049c88c08132780c6bf712076edde 100644 (file)
  * -------------------------------------------------------------------------- */
 
 #include "mainTimer.h"
-#include "core/clock.h"
 #include "core/const.h"
 #include "core/graphics.h"
 #include "glue/events.h"
+#include "glue/layout.h"
 #include "glue/main.h"
-#include "gui/dialogs/beatsInput.h"
-#include "gui/dialogs/bpmInput.h"
-#include "gui/dialogs/mainWindow.h"
 #include "gui/elems/basics/button.h"
 #include "gui/elems/basics/choice.h"
 #include "utils/gui.h"
 #include "utils/string.h"
 
-extern giada::v::gdMainWindow* G_MainWin;
-
 namespace giada::v
 {
 geMainTimer::geMainTimer(int x, int y)
@@ -91,14 +86,14 @@ void geMainTimer::cb_divider(Fl_Widget* /*w*/, void* p) { ((geMainTimer*)p)->cb_
 
 void geMainTimer::cb_bpm()
 {
-       u::gui::openSubWindow(G_MainWin, new gdBpmInput(m_bpm.label()), WID_BPM);
+       c::layout::openBpmWindow(m_bpm.label());
 }
 
 /* -------------------------------------------------------------------------- */
 
 void geMainTimer::cb_meter()
 {
-       u::gui::openSubWindow(G_MainWin, new gdBeatsInput(), WID_BEATS);
+       c::layout::openBeatsWindow(m_timer.beats, m_timer.bars);
 }
 
 /* -------------------------------------------------------------------------- */
index 441aa7a7efca7b388ff414ca8b20e3f373282ea5..fef9729b96807e7398ca60bc238509e9d8fcbaad 100644 (file)
  *
  * -------------------------------------------------------------------------- */
 
-#include "mainTransport.h"
-#include "core/clock.h"
+#include "gui/elems/mainWindow/mainTransport.h"
 #include "core/conf.h"
 #include "core/const.h"
 #include "core/graphics.h"
-#include "core/mixer.h"
-#include "core/mixerHandler.h"
-#include "core/recManager.h"
-#include "core/sequencer.h"
 #include "glue/events.h"
 #include "glue/main.h"
-#include "gui/elems/basics/box.h"
-#include "gui/elems/basics/button.h"
-#include "gui/elems/basics/statusButton.h"
 
 namespace giada::v
 {
@@ -110,11 +102,13 @@ geMainTransport::geMainTransport(int x, int y)
 
 void geMainTransport::refresh()
 {
-       m_play.setStatus(m::clock::isRunning());
-       m_recAction.setStatus(m::recManager::isRecordingAction());
-       m_recInput.setStatus(m::recManager::isRecordingInput());
-       m_metronome.setStatus(m::sequencer::isMetronomeOn());
-       m_recTriggerMode.setStatus(m::conf::conf.recTriggerMode == RecTriggerMode::SIGNAL);
-       m_inputRecMode.setStatus(m::conf::conf.inputRecMode == InputRecMode::FREE);
+       c::main::Transport transport = c::main::getTransport();
+
+       m_play.setStatus(transport.isRunning);
+       m_recAction.setStatus(transport.isRecordingAction);
+       m_recInput.setStatus(transport.isRecordingInput);
+       m_metronome.setStatus(transport.isMetronomeOn);
+       m_recTriggerMode.setStatus(transport.recTriggerMode == RecTriggerMode::SIGNAL);
+       m_inputRecMode.setStatus(transport.inputRecMode == InputRecMode::FREE);
 }
 } // namespace giada::v
index 890c9b6cedb9ce078d5aeffc703636d0870de30a..06bb2c0429f38a7a3cb76be0a8ceacaedf25e905 100644 (file)
 
 #ifdef WITH_VST
 
-#include "pluginBrowser.h"
+#include "gui/elems/plugin/pluginBrowser.h"
 #include "core/const.h"
-#include "core/plugins/plugin.h"
-#include "core/plugins/pluginHost.h"
 #include "core/plugins/pluginManager.h"
+#include "glue/plugin.h"
 #include "gui/elems/basics/boxtypes.h"
 #include <FL/fl_draw.H>
 
-namespace giada
-{
-namespace v
+namespace giada::v
 {
 gePluginBrowser::gePluginBrowser(int x, int y, int w, int h)
 : Fl_Browser(x, y, w, h)
+, m_widths{0}
 {
        box(G_CUSTOM_BORDER_BOX);
        textsize(G_GUI_FONT_SIZE_BASE);
@@ -61,7 +59,7 @@ gePluginBrowser::gePluginBrowser(int x, int y, int w, int h)
 
        computeWidths();
 
-       column_widths(widths);
+       column_widths(m_widths);
        column_char('\t'); // tabs as column delimiters
 
        refresh();
@@ -78,18 +76,19 @@ void gePluginBrowser::refresh()
        add("NAME\tMANUFACTURER\tCATEGORY\tFORMAT\tUID");
        add("---\t---\t---\t---\t---");
 
-       for (int i = 0; i < m::pluginManager::countAvailablePlugins(); i++)
+       for (m::PluginManager::PluginInfo pi : c::plugin::getPluginsInfo())
        {
-               m::pluginManager::PluginInfo pi = m::pluginManager::getAvailablePluginInfo(i);
-               std::string                  m  = m::pluginManager::doesPluginExist(pi.uid) ? "" : "@-";
-               std::string                  s  = m + pi.name + "\t" + m + pi.manufacturerName + "\t" + m +
-                               pi.category + "\t" + m + pi.format + "\t" + m + pi.uid;
-               add(s.c_str());
-       }
+               std::string s;
+               if (pi.isKnown)
+               {
+                       std::string m = pi.exists ? "" : "@-";
+
+                       s = m + pi.name + "\t" + m + pi.manufacturerName + "\t" + m +
+                           pi.category + "\t" + m + pi.format + "\t" + m + pi.uid;
+               }
+               else
+                       std::string s = "?\t?\t?\t?\t? " + pi.uid + " ?";
 
-       for (int i = 0; i < m::pluginManager::countUnknownPlugins(); i++)
-       {
-               std::string s = "?\t?\t?\t?\t? " + m::pluginManager::getUnknownPluginInfo(i) + " ?";
                add(s.c_str());
        }
 }
@@ -99,26 +98,24 @@ void gePluginBrowser::refresh()
 void gePluginBrowser::computeWidths()
 {
        int w0, w1, w3;
-       for (int i = 0; i < m::pluginManager::countAvailablePlugins(); i++)
+       for (m::PluginManager::PluginInfo pi : c::plugin::getPluginsInfo())
        {
-               m::pluginManager::PluginInfo pi = m::pluginManager::getAvailablePluginInfo(i);
-               w0                              = (int)fl_width(pi.name.c_str());
-               w1                              = (int)fl_width(pi.manufacturerName.c_str());
-               w3                              = (int)fl_width(pi.format.c_str());
-               if (w0 > widths[0])
-                       widths[0] = w0;
-               if (w1 > widths[1])
-                       widths[1] = w1;
-               if (w3 > widths[3])
-                       widths[3] = w3;
+               w0 = static_cast<int>(fl_width(pi.name.c_str()));
+               w1 = static_cast<int>(fl_width(pi.manufacturerName.c_str()));
+               w3 = static_cast<int>(fl_width(pi.format.c_str()));
+               if (w0 > m_widths[0])
+                       m_widths[0] = w0;
+               if (w1 > m_widths[1])
+                       m_widths[1] = w1;
+               if (w3 > m_widths[3])
+                       m_widths[3] = w3;
        }
-       widths[0] += 60;
-       widths[1] += 60;
-       widths[2] = static_cast<int>(fl_width("CATEGORY") + 60);
-       widths[3] += 60;
-       widths[4] = 0;
+       m_widths[0] += 60;
+       m_widths[1] += 60;
+       m_widths[2] = static_cast<int>(fl_width("CATEGORY") + 60);
+       m_widths[3] += 60;
+       m_widths[4] = 0;
 }
-} // namespace v
-} // namespace giada
+} // namespace giada::v
 
 #endif
index c40fd27a6b3493168c93b6b283ccebb0499459e2..e03972858668af515258b6ae7836afa89f4638e3 100644 (file)
@@ -31,9 +31,7 @@
 
 #include <FL/Fl_Browser.H>
 
-namespace giada
-{
-namespace v
+namespace giada::v
 {
 class gePluginBrowser : public Fl_Browser
 {
@@ -42,13 +40,12 @@ public:
 
        void refresh();
 
-  private:
+private:
        void computeWidths();
 
-       int widths[5] = {0};
+       int m_widths[5];
 };
-} // namespace v
-} // namespace giada
+} // namespace giada::v
 
 #endif
 
index ce8caa472574ce0afa3db9eb93f381f4118c105c..dd05266decfd6c9d917db7e45894430a07db03b0 100644 (file)
@@ -159,24 +159,25 @@ void gePluginElement::cb_openPluginWindow()
        /* The new pluginWindow has id = id_plugin + 1, because id=0 is reserved for 
        the parent window 'add plugin'. */
 
-       int pwid = m_plugin.id + 1;
+       const int pwid = m_plugin.id + 1;
 
        gdWindow* parent = static_cast<gdWindow*>(window());
        gdWindow* child  = parent->getChild(pwid);
 
+       /* If Plug-in window is already opened, just raise it on top and quit. */
+
        if (child != nullptr)
        {
-               child->show(); // Raise it to top
+               child->show();
+               return;
        }
+
+       if (m_plugin.hasEditor)
+               child = new gdPluginWindowGUI(m_plugin);
        else
-       {
-               if (m_plugin.hasEditor)
-                       child = new gdPluginWindowGUI(m_plugin);
-               else
-                       child = new gdPluginWindow(m_plugin);
-               child->setId(pwid);
-               parent->addSubWindow(child);
-       }
+               child = new gdPluginWindow(m_plugin);
+       child->setId(pwid);
+       parent->addSubWindow(child);
 }
 
 /* -------------------------------------------------------------------------- */
index 023f5575faaef15c745345bbb2e316ff053249e9..bfeb6d26fd7096f827b0a82263168ea3e2302a44 100644 (file)
@@ -26,7 +26,6 @@
  * -------------------------------------------------------------------------- */
 
 #include "pitchTool.h"
-#include "core/clock.h"
 #include "core/const.h"
 #include "core/graphics.h"
 #include "core/model/model.h"
@@ -40,9 +39,7 @@
 #include "utils/string.h"
 #include <FL/Fl.H>
 
-namespace giada
-{
-namespace v
+namespace giada::v
 {
 gePitchTool::gePitchTool(const c::sampleEditor::Data& d, int x, int y)
 : gePack(x, y, Direction::HORIZONTAL)
@@ -141,7 +138,7 @@ void gePitchTool::cb_setPitchDouble()
 
 void gePitchTool::cb_setPitchToBar()
 {
-       c::events::setChannelPitch(m_data->channelId, m_data->end / (float)m::clock::getFramesInBar(),
+       c::events::setChannelPitch(m_data->channelId, m_data->end / (float)m_data->getFramesInBar(),
            Thread::MAIN);
 }
 
@@ -149,7 +146,7 @@ void gePitchTool::cb_setPitchToBar()
 
 void gePitchTool::cb_setPitchToSong()
 {
-       c::events::setChannelPitch(m_data->channelId, m_data->end / (float)m::clock::getFramesInLoop(),
+       c::events::setChannelPitch(m_data->channelId, m_data->end / (float)m_data->getFramesInLoop(),
            Thread::MAIN);
 }
 
@@ -159,5 +156,4 @@ void gePitchTool::cb_resetPitch()
 {
        c::events::setChannelPitch(m_data->channelId, G_DEFAULT_PITCH, Thread::MAIN);
 }
-} // namespace v
-} // namespace giada
+} // namespace giada::v
\ No newline at end of file
index 9a0da80179aa0990baaa993f4f59941534c8959b..e410fd81e9f4f2366c4e637bb59ecc3ca0172ca0 100644 (file)
@@ -37,6 +37,7 @@ namespace giada::c::sampleEditor
 {
 struct Data;
 }
+
 namespace giada::v
 {
 class gePitchTool : public gePack
@@ -47,7 +48,7 @@ public:
        void rebuild(const c::sampleEditor::Data& d);
        void update(float v, bool isDial = false);
 
-  private:
+private:
        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);
index 4f06c068a3ef83ff83799ec64efcdc5c76131a37..607eb5189feaa004dc06643c68fe6c56f23eacd5 100644 (file)
@@ -31,6 +31,7 @@
 #include "glue/sampleEditor.h"
 #include "gui/dialogs/sampleEditor.h"
 #include "gui/elems/basics/boxtypes.h"
+#include "utils/gui.h"
 #include "waveform.h"
 #include <FL/Fl_Menu_Button.H>
 #include <FL/Fl_Menu_Item.H>
@@ -116,7 +117,7 @@ void menuCallback_(Fl_Widget* w, void* v)
 /* -------------------------------------------------------------------------- */
 /* -------------------------------------------------------------------------- */
 
-geWaveTools::geWaveTools(int x, int y, int w, int h)
+geWaveTools::geWaveTools(int x, int y, int w, int h, bool gridEnabled, int gridVal)
 : Fl_Scroll(x, y, w, h, nullptr)
 , m_data(nullptr)
 {
@@ -126,7 +127,7 @@ geWaveTools::geWaveTools(int x, int y, int w, int h)
        hscrollbar.labelcolor(G_COLOR_LIGHT_1);
        hscrollbar.slider(G_CUSTOM_BORDER_BOX);
 
-       waveform = new v::geWaveform(x, y, w, h - 24);
+       waveform = new v::geWaveform(x, y, w, h - 24, gridEnabled, gridVal);
 }
 
 /* -------------------------------------------------------------------------- */
@@ -197,19 +198,19 @@ int geWaveTools::handle(int e)
 void geWaveTools::openMenu()
 {
        Fl_Menu_Item menu[] = {
-           {"Cut", 0, menuCallback_, (void*)Menu::CUT, 0, 0, 0, 0, 0},
-           {"Copy", 0, menuCallback_, (void*)Menu::COPY, 0, 0, 0, 0, 0},
-           {"Paste", 0, menuCallback_, (void*)Menu::PASTE, 0, 0, 0, 0, 0},
-           {"Trim", 0, menuCallback_, (void*)Menu::TRIM, 0, 0, 0, 0, 0},
-           {"Silence", 0, menuCallback_, (void*)Menu::SILENCE, 0, 0, 0, 0, 0},
-           {"Reverse", 0, menuCallback_, (void*)Menu::REVERSE, 0, 0, 0, 0, 0},
-           {"Normalize", 0, menuCallback_, (void*)Menu::NORMALIZE, 0, 0, 0, 0, 0},
-           {"Fade in", 0, menuCallback_, (void*)Menu::FADE_IN, 0, 0, 0, 0, 0},
-           {"Fade out", 0, menuCallback_, (void*)Menu::FADE_OUT, 0, 0, 0, 0, 0},
-           {"Smooth edges", 0, menuCallback_, (void*)Menu::SMOOTH_EDGES, 0, 0, 0, 0, 0},
-           {"Set begin/end here", 0, menuCallback_, (void*)Menu::SET_BEGIN_END, 0, 0, 0, 0, 0},
-           {"Copy to new channel", 0, menuCallback_, (void*)Menu::TO_NEW_CHANNEL, 0, 0, 0, 0, 0},
-           {0}};
+           u::gui::makeMenuItem("Cut", menuCallback_, (void*)Menu::CUT),
+           u::gui::makeMenuItem("Copy", menuCallback_, (void*)Menu::COPY),
+           u::gui::makeMenuItem("Paste", menuCallback_, (void*)Menu::PASTE),
+           u::gui::makeMenuItem("Trim", menuCallback_, (void*)Menu::TRIM),
+           u::gui::makeMenuItem("Silence", menuCallback_, (void*)Menu::SILENCE),
+           u::gui::makeMenuItem("Reverse", menuCallback_, (void*)Menu::REVERSE),
+           u::gui::makeMenuItem("Normalize", menuCallback_, (void*)Menu::NORMALIZE),
+           u::gui::makeMenuItem("Fade in", menuCallback_, (void*)Menu::FADE_IN),
+           u::gui::makeMenuItem("Fade out", menuCallback_, (void*)Menu::FADE_OUT),
+           u::gui::makeMenuItem("Smooth edges", menuCallback_, (void*)Menu::SMOOTH_EDGES),
+           u::gui::makeMenuItem("Set begin/end here", menuCallback_, (void*)Menu::SET_BEGIN_END),
+           u::gui::makeMenuItem("Copy to new channel", menuCallback_, (void*)Menu::TO_NEW_CHANNEL),
+           {}};
 
        if (!waveform->isSelected())
        {
index 53387fda7e84e954fb859bd1fdb9d16750f6dcd5..f17a8a79a3b2d87d4265dd475c528d6ed2ca8427 100644 (file)
@@ -33,13 +33,14 @@ namespace giada::c::sampleEditor
 {
 struct Data;
 }
+
 namespace giada::v
 {
 class geWaveform;
 class geWaveTools : public Fl_Scroll
 {
 public:
-       geWaveTools(int x, int y, int w, int h);
+       geWaveTools(int x, int y, int w, int h, bool gridEnabled, int gridVal);
 
        void resize(int x, int y, int w, int h) override;
        int  handle(int e) override;
@@ -61,7 +62,7 @@ public:
 
        v::geWaveform* waveform;
 
-  private:
+private:
        void openMenu();
 
        const c::sampleEditor::Data* m_data;
index c92f48cce620b97dae1c9034231e817652292757..98b0e2ae907c462d14ffe027521aaf058ece222f 100644 (file)
@@ -24,8 +24,7 @@
  *
  * -------------------------------------------------------------------------- */
 
-#include "waveform.h"
-#include "core/conf.h"
+#include "gui/elems/sampleEditor/waveform.h"
 #include "core/const.h"
 #include "core/mixer.h"
 #include "core/model/model.h"
 #include <cassert>
 #include <cmath>
 
-namespace giada
+namespace giada::v
 {
-namespace v
-{
-geWaveform::geWaveform(int x, int y, int w, int h)
+geWaveform::geWaveform(int x, int y, int w, int h, bool gridEnabled, int gridVal)
 : Fl_Widget(x, y, w, h, nullptr)
 , m_selection{}
 , m_data(nullptr)
@@ -62,8 +59,8 @@ geWaveform::geWaveform(int x, int y, int w, int h)
 {
        m_waveform.size = w;
 
-       m_grid.snap  = m::conf::conf.sampleEditorGridOn;
-       m_grid.level = m::conf::conf.sampleEditorGridVal;
+       m_grid.snap  = gridEnabled;
+       m_grid.level = gridVal;
 }
 
 /* -------------------------------------------------------------------------- */
@@ -290,7 +287,7 @@ void geWaveform::draw()
        fl_rectf(x(), y(), w(), h(), G_COLOR_GREY_2); // blank canvas
 
        /* Draw things from 'from' (offset driven by the scrollbar) to 'to' (width of 
-       parent window). We don't draw the entire waveform, only the visibile part. */
+       parent window). We don't draw the entire waveform, only the visible part. */
 
        int from = abs(x() - parent()->x());
        int to   = from + parent()->w();
@@ -583,6 +580,10 @@ void geWaveform::clearSelection()
 
 /* -------------------------------------------------------------------------- */
 
+#ifdef G_OS_WINDOWS
+#undef IN
+#endif
+
 void geWaveform::setZoom(Zoom z)
 {
        if (!alloc(z == Zoom::IN ? m_waveform.size * G_GUI_ZOOM_FACTOR : m_waveform.size / G_GUI_ZOOM_FACTOR))
@@ -679,5 +680,4 @@ void geWaveform::selectAll()
        m_selection.b = m_data->waveSize - 1;
        redraw();
 }
-} // namespace v
-} // namespace giada
+} // namespace giada::v
\ No newline at end of file
index b184747c49eeed5618010ed1831f7c01d0f20cdf..392f809c9590bdb7d78a183c7fdeeec54275329d 100644 (file)
@@ -36,6 +36,7 @@ namespace giada::c::sampleEditor
 {
 struct Data;
 }
+
 namespace giada::v
 {
 class geWaveform : public Fl_Widget
@@ -52,7 +53,7 @@ public:
                OUT
        };
 
-       geWaveform(int x, int y, int w, int h);
+       geWaveform(int x, int y, int w, int h, bool gridEnabled, int gridVal);
 
        void draw() override;
        int  handle(int e) override;
@@ -105,7 +106,7 @@ public:
 
        void setWaveId(ID /*id*/){/* TODO m_waveId = id;*/};
 
-  private:
+private:
        static const int FLAG_WIDTH  = 20;
        static const int FLAG_HEIGHT = 20;
        static const int BORDER      = 8; // window border <-> widget border
index 05777afebc1e045f8a661f55dcf15606bc991181..a1cc9050d809a40de4b07c7fb4d5fdd3ad98b124 100644 (file)
@@ -28,6 +28,7 @@
 #include "core/const.h"
 #include "core/kernelAudio.h"
 #include "core/types.h"
+#include "gui/drawing.h"
 #include "utils/math.h"
 #include <FL/fl_draw.H>
 #include <algorithm>
@@ -48,10 +49,26 @@ Pixel dbToPx_(float db, Pixel max)
 /* -------------------------------------------------------------------------- */
 /* -------------------------------------------------------------------------- */
 
+float geSoundMeter::Meter::compute(float peak)
+{
+       /*  dBFS (full scale) calculation, plus decay of -2dB per call. */
+
+       float dbLevelCur = u::math::linearToDB(std::fabs(peak));
+
+       if (dbLevelCur < m_dbLevelOld && m_dbLevelOld > -G_MIN_DB_SCALE)
+               dbLevelCur = m_dbLevelOld - 2.0f;
+
+       m_dbLevelOld = dbLevelCur;
+
+       return dbLevelCur;
+}
+
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+
 geSoundMeter::geSoundMeter(int x, int y, int w, int h, const char* l)
 : Fl_Box(x, y, w, h, l)
-, mixerPeak(0.0f)
-, m_dbLevelOld(0.0f)
 {
 }
 
@@ -59,26 +76,28 @@ geSoundMeter::geSoundMeter(int x, int y, int w, int h, const char* l)
 
 void geSoundMeter::draw()
 {
-       fl_rect(x(), y(), w(), h(), G_COLOR_GREY_4);
-
-       /* Compute peak level on 0.0 -> 1.0 scale. 1.0 is considered clip. */
+       const geompp::Rect outline(x(), y(), w(), h());
+       const geompp::Rect body(outline.reduced(1));
 
-       const bool clip = std::fabs(mixerPeak) >= 1.0f ? true : false;
+       drawRect(outline, G_COLOR_GREY_4);
 
-       /*  dBFS (full scale) calculation, plus decay of -2dB per frame. */
+       if (!ready)
+       {
+               drawRectf(body, G_COLOR_BLUE);
+               return;
+       }
 
-       float dbLevelCur = u::math::linearToDB(std::fabs(mixerPeak));
-
-       if (dbLevelCur < m_dbLevelOld && m_dbLevelOld > -G_MIN_DB_SCALE)
-               dbLevelCur = m_dbLevelOld - 2.0f;
-
-       m_dbLevelOld = dbLevelCur;
+       drawRectf(body, G_COLOR_GREY_2); // Cleanup
 
-       /* Paint the meter on screen. */
+       const float dbL    = m_left.compute(peak.left);
+       const float dbR    = m_right.compute(peak.right);
+       const int   colorL = std::fabs(peak.left) > 1.0f ? G_COLOR_BLUE : G_COLOR_GREY_4;
+       const int   colorR = std::fabs(peak.right) > 1.0f ? G_COLOR_BLUE : G_COLOR_GREY_4;
 
-       const int bodyCol = clip || !m::kernelAudio::isReady() ? G_COLOR_RED_ALERT : G_COLOR_GREY_4;
+       const geompp::Rect bodyL(body.withTrimmedBottom(h() / 2));
+       const geompp::Rect bodyR(body.withTrimmedTop(h() / 2));
 
-       fl_rectf(x() + 1, y() + 1, w() - 2, h() - 2, G_COLOR_GREY_2);
-       fl_rectf(x() + 1, y() + 1, dbToPx_(dbLevelCur, w()), h() - 2, bodyCol);
+       drawRectf(bodyL.withW(dbToPx_(dbL, w() - 2)), colorL);
+       drawRectf(bodyR.withW(dbToPx_(dbR, w() - 2)), colorR);
 }
 } // namespace giada::v
\ No newline at end of file
index 4669326ebe2865cf922e755f01a263a881eaa0c4..29498351c024e8be6597b6c7041bebd44085494b 100644 (file)
@@ -27,6 +27,7 @@
 #ifndef GE_SOUND_METER_H
 #define GE_SOUND_METER_H
 
+#include "core/types.h"
 #include <FL/Fl_Box.H>
 
 namespace giada::v
@@ -38,10 +39,21 @@ public:
 
        void draw() override;
 
-       float mixerPeak; // peak from mixer
+       Peak peak;  // Peak from Mixer
+       bool ready; // Kernel state
 
 private:
-       float m_dbLevelOld;
+       class Meter
+       {
+       public:
+               float compute(float peak);
+
+       private:
+               float m_dbLevelOld = 0.0f;
+       };
+
+       Meter m_left;
+       Meter m_right;
 };
 } // namespace giada::v
 
diff --git a/src/gui/model.cpp b/src/gui/model.cpp
deleted file mode 100644 (file)
index ba21638..0000000
+++ /dev/null
@@ -1,54 +0,0 @@
-/* -----------------------------------------------------------------------------
- *
- * Giada - Your Hardcore Loopmachine
- *
- * -----------------------------------------------------------------------------
- *
- * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
- *
- * This file is part of Giada - Your Hardcore Loopmachine.
- *
- * Giada - Your Hardcore Loopmachine is free software: you can
- * redistribute it and/or modify it under the terms of the GNU General
- * Public License as published by the Free Software Foundation, either
- * version 3 of the License, or (at your option) any later version.
- *
- * Giada - Your Hardcore Loopmachine is distributed in the hope that it
- * will be useful, but WITHOUT ANY WARRANTY; without even the implied
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Giada - Your Hardcore Loopmachine. If not, see
- * <http://www.gnu.org/licenses/>.
- *
- * -------------------------------------------------------------------------- */
-
-#include "model.h"
-#include "core/patch.h"
-#include "gui/dialogs/mainWindow.h"
-#include "gui/elems/mainWindow/keyboard/channel.h"
-#include "gui/elems/mainWindow/keyboard/column.h"
-#include "gui/elems/mainWindow/keyboard/keyboard.h"
-#include "utils/log.h"
-
-extern giada::v::gdMainWindow* G_MainWin;
-
-namespace giada::v::model
-{
-void store(m::patch::Patch& patch)
-{
-       G_MainWin->keyboard->forEachColumn([&](const geColumn& c) {
-               patch.columns.push_back({c.id, c.w()});
-       });
-}
-
-/* -------------------------------------------------------------------------- */
-
-void load(const m::patch::Patch& patch)
-{
-       G_MainWin->keyboard->layout.clear();
-       for (const m::patch::Column& col : patch.columns)
-               G_MainWin->keyboard->layout.push_back({col.id, col.width});
-}
-} // namespace giada::v::model
diff --git a/src/gui/model.h b/src/gui/model.h
deleted file mode 100644 (file)
index 97e4986..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-/* -----------------------------------------------------------------------------
- *
- * Giada - Your Hardcore Loopmachine
- *
- * -----------------------------------------------------------------------------
- *
- * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
- *
- * This file is part of Giada - Your Hardcore Loopmachine.
- *
- * Giada - Your Hardcore Loopmachine is free software: you can
- * redistribute it and/or modify it under the terms of the GNU General
- * Public License as published by the Free Software Foundation, either
- * version 3 of the License, or (at your option) any later version.
- *
- * Giada - Your Hardcore Loopmachine is distributed in the hope that it
- * will be useful, but WITHOUT ANY WARRANTY; without even the implied
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Giada - Your Hardcore Loopmachine. If not, see
- * <http://www.gnu.org/licenses/>.
- *
- * -------------------------------------------------------------------------- */
-
-#ifndef G_V_MODEL_H
-#define G_V_MODEL_H
-
-namespace giada::m::patch
-{
-struct Patch;
-}
-namespace giada::v::model
-{
-void store(m::patch::Patch& patch);
-void load(const m::patch::Patch& patch);
-} // namespace giada::v::model
-
-#endif
\ No newline at end of file
diff --git a/src/gui/types.h b/src/gui/types.h
new file mode 100644 (file)
index 0000000..4662088
--- /dev/null
@@ -0,0 +1,39 @@
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+#ifndef G_V_TYPES_H
+#define G_V_TYPES_H
+
+namespace giada::v
+{
+enum class Direction
+{
+       HORIZONTAL,
+       VERTICAL
+};
+} // namespace giada::v
+
+#endif
\ No newline at end of file
diff --git a/src/gui/ui.cpp b/src/gui/ui.cpp
new file mode 100644 (file)
index 0000000..e7d2897
--- /dev/null
@@ -0,0 +1,260 @@
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+#include "gui/ui.h"
+#include "core/const.h"
+#include "core/engine.h"
+#include "core/recorder.h"
+#include "gui/elems/mainWindow/keyboard/column.h"
+#include "gui/elems/mainWindow/keyboard/keyboard.h"
+#include "gui/elems/mainWindow/mainIO.h"
+#include "gui/elems/mainWindow/mainTimer.h"
+#include "gui/updater.h"
+#include "utils/gui.h"
+#include "utils/log.h"
+#ifdef WITH_VST
+#include <FL/Fl.H>
+#if defined(G_OS_LINUX) || defined(G_OS_FREEBSD)
+#include <X11/Xlib.h> // For XInitThreads
+#endif
+#endif
+
+namespace giada::v
+{
+Ui::Ui(m::Recorder& recorder)
+: m_updater(*this)
+, m_blinker(0)
+{
+       dispatcher.onEventOccured = [&recorder]() {
+               recorder.startActionRecOnCallback();
+       };
+}
+
+/* -------------------------------------------------------------------------- */
+
+bool Ui::shouldBlink() const
+{
+       return m_blinker > 6; // TODO magic numbers
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Ui::load(const m::Patch::Data& patch)
+{
+       reset();
+       mainWindow->keyboard->layout.clear();
+       for (const m::Patch::Column& col : patch.columns)
+               mainWindow->keyboard->layout.push_back({col.id, col.width});
+       mainWindow->keyboard->rebuild();
+       setMainWindowTitle(patch.name);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Ui::store(m::Patch::Data& patch)
+{
+       patch.columns.clear();
+       mainWindow->keyboard->forEachColumn([&](const geColumn& c) {
+               patch.columns.push_back({c.id, c.w()});
+       });
+       setMainWindowTitle(patch.name);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Ui::init(int argc, char** argv, m::Engine& engine)
+{
+       /* This is of paramount importance on Linux with VST enabled, otherwise many
+       plug-ins go nuts and crash hard. It seems that some plug-ins on our Juce-based
+       PluginHost use Xlib concurrently. */
+
+#if (defined(G_OS_LINUX) || defined(G_OS_FREEBSD)) && defined(WITH_VST)
+       XInitThreads();
+#endif
+
+       mainWindow = std::make_unique<gdMainWindow>(G_MIN_GUI_WIDTH, G_MIN_GUI_HEIGHT, "", argc, argv, engine.conf.data);
+       mainWindow->resize(engine.conf.data.mainWindowX, engine.conf.data.mainWindowY, engine.conf.data.mainWindowW,
+           engine.conf.data.mainWindowH);
+
+       setMainWindowTitle(engine.patch.data.name == "" ? G_DEFAULT_PATCH_NAME : engine.patch.data.name);
+
+       m_updater.init(engine.model);
+
+       if (engine.kernelAudio.isReady())
+               rebuildStaticWidgets();
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Ui::reset()
+{
+       setMainWindowTitle(G_DEFAULT_PATCH_NAME);
+       rebuildStaticWidgets();
+       closeAllSubwindows();
+       mainWindow->clearKeyboard();
+       mainWindow->rebuild();
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Ui::shutdown()
+{
+       mainWindow.reset();
+       m_updater.close();
+
+       u::log::print("[ui] All windows closed\n");
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Ui::refresh()
+{
+       /* Update dynamic elements inside main window: in and out meters, beat meter
+       and each channel. */
+
+       mainWindow->refresh();
+
+       /* Compute timer for blinker. */
+
+       m_blinker = (m_blinker + 1) % 12; // TODO magic numbers
+
+       /* Refresh Sample Editor and Action Editor for dynamic playhead. */
+
+       refreshSubWindow(WID_SAMPLE_EDITOR);
+       refreshSubWindow(WID_ACTION_EDITOR);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Ui::rebuild()
+{
+       mainWindow->rebuild();
+       rebuildSubWindow(WID_FX_LIST);
+       rebuildSubWindow(WID_SAMPLE_EDITOR);
+       rebuildSubWindow(WID_ACTION_EDITOR);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Ui::rebuildSubWindow(int wid)
+{
+       v::gdWindow* w = getSubwindow(*mainWindow.get(), wid);
+       if (w != nullptr) // If its open
+               w->rebuild();
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Ui::refreshSubWindow(int wid)
+{
+       v::gdWindow* w = getSubwindow(*mainWindow.get(), wid);
+       if (w != nullptr) // If its open
+               w->refresh();
+}
+
+/* -------------------------------------------------------------------------- */
+
+v::gdWindow* Ui::getSubwindow(v::gdWindow& parent, int wid)
+{
+       return parent.hasWindow(wid) ? parent.getChild(wid) : nullptr;
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Ui::openSubWindow(v::gdWindow& parent, v::gdWindow* child, int wid)
+{
+       if (parent.hasWindow(wid))
+       {
+               u::log::print("[GU] parent has subwindow with id=%d, deleting\n", wid);
+               parent.delSubWindow(wid);
+       }
+       child->setId(wid);
+       parent.addSubWindow(child);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Ui::closeSubWindow(int wid)
+{
+       mainWindow->delSubWindow(wid);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Ui::closeAllSubwindows()
+{
+       mainWindow->delSubWindow(WID_ACTION_EDITOR);
+       mainWindow->delSubWindow(WID_SAMPLE_EDITOR);
+       mainWindow->delSubWindow(WID_FX_LIST);
+       mainWindow->delSubWindow(WID_FX);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Ui::setMainWindowTitle(const std::string& s)
+{
+       std::string out = std::string(G_APP_NAME) + " - " + s;
+       mainWindow->copy_label(out.c_str());
+}
+
+/* -------------------------------------------------------------------------- */
+
+#ifdef WITH_VST
+
+void Ui::startJuceDispatchLoop()
+{
+       Fl::add_timeout(G_GUI_PLUGIN_RATE, juceDispatchLoop);
+}
+
+void Ui::stopJuceDispatchLoop()
+{
+       Fl::remove_timeout(juceDispatchLoop);
+}
+
+#endif
+
+/* -------------------------------------------------------------------------- */
+
+void Ui::rebuildStaticWidgets()
+{
+       mainWindow->mainIO->rebuild();
+       mainWindow->mainTimer->rebuild();
+}
+
+/* -------------------------------------------------------------------------- */
+
+#ifdef WITH_VST
+
+void Ui::juceDispatchLoop(void*)
+{
+       juce::MessageManager* mm = juce::MessageManager::getInstanceWithoutCreating();
+       assert(mm != nullptr);
+       mm->runDispatchLoopUntil(1);
+       Fl::add_timeout(G_GUI_PLUGIN_RATE, juceDispatchLoop);
+}
+
+#endif
+} // namespace giada::v
diff --git a/src/gui/ui.h b/src/gui/ui.h
new file mode 100644 (file)
index 0000000..a23a5c1
--- /dev/null
@@ -0,0 +1,143 @@
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+#ifndef G_V_UI_H
+#define G_V_UI_H
+
+#include "core/patch.h"
+#include "gui/dialogs/mainWindow.h"
+#include "gui/dispatcher.h"
+#include "gui/updater.h"
+#include <memory>
+#include <string>
+
+namespace giada::m
+{
+class Recorder;
+class Engine;
+} // namespace giada::m
+
+namespace giada::v
+{
+class Ui final
+{
+public:
+       Ui(m::Recorder&);
+
+       /* shouldBlink
+       Return whether is time to blink something or not. This is used to make 
+       widgets blink. */
+
+       bool shouldBlink() const;
+
+       /* load
+       Reads UI information from a Patch when a new project has been loaded. */
+
+       void load(const m::Patch::Data&);
+
+       /* store
+       Writes UI information to a patch when a project needs to be saved. */
+
+       void store(m::Patch::Data& patch);
+
+       void init(int argc, char** argv, m::Engine&);
+       void reset();
+       void shutdown();
+
+       /* refresh
+       Repaints dynamic GUI elements. */
+
+       void refresh();
+
+       /* rebuild
+       Rebuilds the UI from scratch. Used when the model has changed. */
+
+       void rebuild();
+
+       /* [rebuild|refresh]SubWindow 
+       Rebuilds or refreshes subwindow with ID 'wid' if it exists, i.e. if it's open. */
+
+       void rebuildSubWindow(int wid);
+       void refreshSubWindow(int wid);
+
+       /* getSubwindow
+       Returns a pointer to an open subwindow, otherwise nullptr. */
+
+       v::gdWindow* getSubwindow(v::gdWindow& parent, int wid);
+
+       /* openSubWindow
+       Opens a new sub-window as a child of parent and assigns 'wid' to child. */
+
+       void openSubWindow(v::gdWindow& parent, v::gdWindow* child, int wid);
+
+       /* closeSubWindow
+       Closes a sun-window currently attached to the main one. */
+
+       void closeSubWindow(int wid);
+
+       /* closeAllSubwindows
+       Closes all subwindows attached to the main one. */
+
+       void closeAllSubwindows();
+
+       /* setMainWindowTitle
+    Updates the title of the main window, usually visible on top of it. */
+
+       void setMainWindowTitle(const std::string&);
+
+#ifdef WITH_VST
+
+       /* [start|stop]JuceDispatchLoop
+       Starts and stops the JUCE dispatch loop from its MessageManager component.
+       This is needed for plugin-ins to wake up their UI editor and let it react
+       to UI events. */
+
+       void startJuceDispatchLoop();
+       void stopJuceDispatchLoop();
+
+#endif
+
+       std::unique_ptr<gdMainWindow> mainWindow;
+       Dispatcher                    dispatcher;
+
+private:
+#ifdef WITH_VST
+       static void juceDispatchLoop(void*);
+#endif
+
+       /* rebuildStaticWidgets
+    Updates attributes of static widgets, i.e. those elements that don't get
+    automatically refreshed during the UI update loop. Useful when loading a new 
+    patch. */
+
+       void rebuildStaticWidgets();
+
+       Updater m_updater;
+       int     m_blinker;
+};
+} // namespace giada::v
+
+#endif
\ No newline at end of file
index 3ecdc767da0cb591f7150673102448ffd0e9e873..dbb95b868eb101a0f1d97a47d422b12490bb1163 100644 (file)
  *
  * -------------------------------------------------------------------------- */
 
-#include "updater.h"
+#include "gui/updater.h"
 #include "core/const.h"
 #include "core/model/model.h"
-#include "utils/gui.h"
+#include "gui/ui.h"
 #include <FL/Fl.H>
 
-namespace giada::v::updater
+namespace giada::v
 {
-void init()
+Updater::Updater(Ui& ui)
+: m_ui(ui)
 {
-       m::model::onSwap([](m::model::SwapType type) {
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Updater::init(m::model::Model& model)
+{
+       model.onSwap = [this](m::model::SwapType type) {
                if (type == m::model::SwapType::NONE)
                        return;
 
@@ -42,25 +49,29 @@ void init()
                synchronization with the main one. */
 
                Fl::lock();
-               type == m::model::SwapType::HARD ? u::gui::rebuild() : u::gui::refresh();
+               type == m::model::SwapType::HARD ? m_ui.rebuild() : m_ui.refresh();
                Fl::unlock();
-       });
+       };
 
-       Fl::add_timeout(G_GUI_REFRESH_RATE, update, nullptr);
+       Fl::add_timeout(G_GUI_REFRESH_RATE, update, this);
 }
 
 /* -------------------------------------------------------------------------- */
 
-void update(void* /*p*/)
+void Updater::update(void* p) { static_cast<Updater*>(p)->update(); }
+
+/* -------------------------------------------------------------------------- */
+
+void Updater::update()
 {
-       u::gui::refresh();
-       Fl::add_timeout(G_GUI_REFRESH_RATE, update, nullptr);
+       m_ui.refresh();
+       Fl::add_timeout(G_GUI_REFRESH_RATE, update, this);
 }
 
 /* -------------------------------------------------------------------------- */
 
-void close()
+void Updater::close()
 {
        Fl::remove_timeout(update);
 }
-} // namespace giada::v::updater
+} // namespace giada::v
index d2dca2c8d61d55ce38127320bd353f137108f5d0..e53de90d8618f2bd77ed03143c32d72d0d9dc06e 100644 (file)
 #ifndef G_V_UPDATER_H
 #define G_V_UPDATER_H
 
-namespace giada::v::updater
+namespace giada::m::model
 {
-void init();
-void update(void* p);
-void close();
-} // namespace giada::v::updater
+class Model;
+}
+
+namespace giada::v
+{
+class Ui;
+class Updater final
+{
+public:
+       Updater(Ui& ui);
+
+       void init(m::model::Model&);
+       void close();
+
+private:
+       static void update(void*);
+       void        update();
+
+       Ui& m_ui;
+};
+} // namespace giada::v
 
 #endif
\ No newline at end of file
index b7bf05b404461d83e54b53d5a5ebf0643e6f3e16..8584d81a115c4898cc023aa47b245d47d655f6f8 100644 (file)
  *
  * -------------------------------------------------------------------------- */
 
-#include "core/init.h"
-#include "gui/dialogs/mainWindow.h"
-#include <FL/Fl.H>
-#ifdef WITH_TESTS
-#define CATCH_CONFIG_RUNNER
-#include "tests/audioBuffer.cpp"
-#include "tests/recorder.cpp"
-#include "tests/utils.cpp"
-#include "tests/wave.cpp"
-#include "tests/waveFx.cpp"
-#include "tests/waveManager.cpp"
-#include <catch2/catch.hpp>
-#include <string>
-#include <vector>
-#endif
+#include "core/engine.h"
+#include "gui/ui.h"
 
-class giada::v::gdMainWindow* G_MainWin = nullptr;
+giada::m::Engine g_engine;
+giada::v::Ui     g_ui(g_engine.recorder);
 
 int main(int argc, char** argv)
 {
-#ifdef WITH_TESTS
-       std::vector<char*> args(argv, argv + argc);
-       if (args.size() > 1 && strcmp(args[1], "--run-tests") == 0)
-               return Catch::Session().run(args.size() - 1, &args[1]);
-#endif
-
+       if (int ret = giada::m::init::tests(argc, argv); ret != -1)
+               return ret;
        giada::m::init::startup(argc, argv);
-
-       Fl::lock(); // Enable multithreading in FLTK
-       int ret = Fl::run();
-
-       giada::m::init::shutdown();
-
-       return ret;
+       return giada::m::init::run();
 }
\ No newline at end of file
index 6d6b31eda84a9e0bd88c446932247cb8583a2066..1d15cc59c0575a3a1db4f0f1757e780ac1b2039f 100644 (file)
 #elif defined(__linux__) || defined(__FreeBSD__)
 #include <X11/xpm.h>
 #endif
-#include "core/clock.h"
 #include "core/conf.h"
 #include "core/graphics.h"
 #include "core/mixer.h"
 #include "core/mixerHandler.h"
 #include "core/plugins/pluginHost.h"
+#include "core/sequencer.h"
 #include "gui.h"
 #include "gui/dialogs/actionEditor/baseActionEditor.h"
-#include "gui/dialogs/mainWindow.h"
 #include "gui/dialogs/sampleEditor.h"
 #include "gui/dialogs/warnings.h"
 #include "gui/dialogs/window.h"
-#include "gui/elems/mainWindow/sequencer.h"
 #include "gui/elems/mainWindow/keyboard/channel.h"
 #include "gui/elems/mainWindow/keyboard/keyboard.h"
 #include "gui/elems/mainWindow/mainIO.h"
 #include "gui/elems/mainWindow/mainTimer.h"
 #include "gui/elems/mainWindow/mainTransport.h"
+#include "gui/elems/mainWindow/sequencer.h"
 #include "gui/elems/sampleEditor/waveTools.h"
 #include "log.h"
 #include "string.h"
 
-extern giada::v::gdMainWindow* G_MainWin;
-
-namespace giada
-{
-namespace u
-{
-namespace gui
-{
-namespace
-{
-int blinker_ = 0;
-} // namespace
-
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-
-void rebuildSubWindow(int wid)
-{
-       v::gdWindow* w = getSubwindow(G_MainWin, wid);
-       if (w != nullptr) // If its open
-               w->rebuild();
-}
-
-/* -------------------------------------------------------------------------- */
-
-void refreshSubWindow(int wid)
-{
-       v::gdWindow* w = getSubwindow(G_MainWin, wid);
-       if (w != nullptr) // If its open
-               w->refresh();
-}
-
-/* -------------------------------------------------------------------------- */
-
-void refresh()
-{
-       /* Update dynamic elements inside main window: in and out meters, beat meter
-       and each channel. */
-
-       G_MainWin->refresh();
-
-       /* Compute timer for blinker. */
-
-       blinker_ = (blinker_ + 1) % 12;
-
-       /* Refresh Sample Editor (if open) for dynamic play head. */
-
-       refreshSubWindow(WID_SAMPLE_EDITOR);
-}
-
-/* -------------------------------------------------------------------------- */
-
-void rebuild()
-{
-       G_MainWin->rebuild();
-       rebuildSubWindow(WID_FX_LIST);
-       rebuildSubWindow(WID_SAMPLE_EDITOR);
-       rebuildSubWindow(WID_ACTION_EDITOR);
-}
-
-/* -------------------------------------------------------------------------- */
-
-bool shouldBlink()
-{
-       return blinker_ > 6;
-}
-
-/* -------------------------------------------------------------------------- */
-
-void updateStaticWidgets()
-{
-       using namespace giada::m;
-
-       G_MainWin->mainIO->setOutVol(mh::getOutVol());
-       G_MainWin->mainIO->setInVol(mh::getInVol());
-
-#ifdef WITH_VST
-
-       //      G_MainWin->mainIO->setMasterFxOutFull(pluginHost::getStack(pluginHost::StackType::MASTER_OUT).plugins.size() > 0);
-       //      G_MainWin->mainIO->setMasterFxInFull(pluginHost::getStack(pluginHost::StackType::MASTER_IN).plugins.size() > 0);
-
-#endif
-
-       G_MainWin->mainTimer->setMeter(clock::getBeats(), clock::getBars());
-       G_MainWin->mainTimer->setBpm(clock::getBpm());
-       G_MainWin->mainTimer->setQuantizer(clock::getQuantizerValue());
-}
-
-/* -------------------------------------------------------------------------- */
-
-void updateMainWinLabel(const std::string& s)
+namespace giada::u::gui
 {
-       std::string out = std::string(G_APP_NAME) + " - " + s;
-       G_MainWin->copy_label(out.c_str());
-}
-
-/* -------------------------------------------------------------------------- */
-
 void setFavicon(v::gdWindow* w)
 {
 #if defined(__linux__) || defined(__FreeBSD__)
@@ -172,51 +74,6 @@ void setFavicon(v::gdWindow* w)
 
 /* -------------------------------------------------------------------------- */
 
-void openSubWindow(v::gdWindow* parent, v::gdWindow* child, int id)
-{
-       if (parent->hasWindow(id))
-       {
-               u::log::print("[GU] parent has subwindow with id=%d, deleting\n", id);
-               parent->delSubWindow(id);
-       }
-       child->setId(id);
-       parent->addSubWindow(child);
-}
-
-/* -------------------------------------------------------------------------- */
-
-void refreshActionEditor()
-{
-       v::gdBaseActionEditor* ae = static_cast<v::gdBaseActionEditor*>(G_MainWin->getChild(WID_ACTION_EDITOR));
-       if (ae != nullptr)
-               ae->rebuild();
-}
-
-/* -------------------------------------------------------------------------- */
-
-v::gdWindow* getSubwindow(v::gdWindow* parent, int id)
-{
-       if (parent->hasWindow(id))
-               return parent->getChild(id);
-       else
-               return nullptr;
-}
-
-/* -------------------------------------------------------------------------- */
-
-void closeAllSubwindows()
-{
-       /* don't close WID_FILE_BROWSER, because it's the caller of this
-        * function */
-
-       G_MainWin->delSubWindow(WID_ACTION_EDITOR);
-       G_MainWin->delSubWindow(WID_SAMPLE_EDITOR);
-       G_MainWin->delSubWindow(WID_FX_LIST);
-       G_MainWin->delSubWindow(WID_FX);
-}
-
-/* -------------------------------------------------------------------------- */
-
 int getStringWidth(const std::string& s)
 {
        int w = 0;
@@ -267,6 +124,4 @@ int centerWindowY(int h)
 {
        return (Fl::h() / 2) - (h / 2);
 }
-} // namespace gui
-} // namespace u
-} // namespace giada
+} // namespace giada::u::gui
index 36dc54465769012e76a1911a7dddff20d28ebce7..afe1927cc04eae4b9752845d2f0d9fbd5a898468 100644 (file)
 #define G_UTILS_GUI_H
 
 #include "core/types.h"
+#include <FL/Fl_Menu_Item.H>
 #include <string>
 
-namespace giada
-{
-namespace v
+namespace giada::v
 {
 class gdWindow;
 }
-namespace u
-{
-namespace gui
-{
-/* refresh
-Repaints some dynamic GUI elements. */
-
-void refresh();
-
-/* rebuild
-Rebuilds the UI from scratch. Used when the model has changed. */
-
-void rebuild();
-
-/* [rebuild|refresh]SubWindow 
-Rebuilds or refreshes subwindow with ID 'wid' if it exists. i.e. if its open. */
-
-void rebuildSubWindow(int wid);
-void refreshSubWindow(int wid);
-
-/* shouldBlink
-Return whether is time to blink something or not. This is used to make widgets 
-blink. */
-
-bool shouldBlink();
-
-/* updateStaticWidgets
-Updates attributes of static widgets, i.e. those elements that don't get
-automatically refreshed during the UI update loop. Useful when loading a new 
-patch. */
-
-void updateStaticWidgets();
-
-/* updateMainWinLabel
-Updates the name of the main window */
-
-void updateMainWinLabel(const std::string& s);
 
+namespace giada::u::gui
+{
 void setFavicon(v::gdWindow* w);
 
-void openSubWindow(v::gdWindow* parent, v::gdWindow* child, int id);
-
-// TODO closeSubWindow(...)
-
-/* refreshActionEditor
-Reloads 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 refreshActionEditor();
-
-/* closeAllSubwindows
-Closes all subwindows attached to mainWin. */
-
-void closeAllSubwindows();
-
-/* getSubwindow
-Returns a pointer to an open subwindow, otherwise nullptr. */
-
-v::gdWindow* getSubwindow(v::gdWindow* parent, int id);
-
 /* removeFltkChars
 Strips special chars used by FLTK to split menus into sub-menus. */
 
 std::string removeFltkChars(const std::string& s);
 
+/* getStringWidth
+Returns the width in pixels of a string 's'. */
+
 int getStringWidth(const std::string& s);
 
 /* truncate
@@ -111,8 +58,14 @@ std::string truncate(const std::string& s, Pixel width);
 int centerWindowX(int w);
 int centerWindowY(int h);
 
-} // namespace gui
-} // namespace u
-} // namespace giada
+/* makeMenuItem
+Makes a new Fl_Menu_Item at compile time. Used to initialize pop-up menus. */
+
+constexpr Fl_Menu_Item makeMenuItem(const char* text, Fl_Callback* callback = nullptr,
+    void* data = nullptr, int flags = 0)
+{
+       return Fl_Menu_Item{text, 0, callback, data, flags, 0, 0, 0, 0};
+}
+} // namespace giada::u::gui
 
 #endif
index 906b2f08710845dc37c179babfea9b9295809411..a7c1b9641b8e7aa1e9267b8bd8ec952951d6b985 100644 (file)
@@ -27,6 +27,7 @@
 #ifndef G_UTILS_MATH_H
 #define G_UTILS_MATH_H
 
+#include <cassert>
 #include <type_traits>
 
 namespace giada::u::math
@@ -47,6 +48,8 @@ TO map(TI x, TI a, TI b, TO w, TO z)
        static_assert(std::is_arithmetic_v<TI>);
        static_assert(std::is_arithmetic_v<TO>);
 
+       if (a == b) // Prevents division by zero (undefined behavior)
+               return x;
        return (((x - a) / (double)(b - a)) * (z - w)) + w;
 }
 
index acfe6c61787c77304680e8fcd9a7512a6ad9f203..b05959ae56e75c7cd91fe523b2a14987169ec53e 100644 (file)
@@ -76,6 +76,14 @@ std::vector<T> cast(const I& i)
 {
        return {i.begin(), i.end()};
 }
+
+/* -------------------------------------------------------------------------- */
+
+template <typename Vector, typename Default>
+auto atOr(const Vector& v, int index, Default d)
+{
+       return index >= 0 && static_cast<size_t>(index) < v.size() ? v[index] : d;
+}
 } // namespace giada::u::vector
 
 #endif
diff --git a/tests/actionRecorder.cpp b/tests/actionRecorder.cpp
new file mode 100644 (file)
index 0000000..e0f884c
--- /dev/null
@@ -0,0 +1,69 @@
+#include "src/core/actions/actionRecorder.h"
+#include "src/core/actions/action.h"
+#include "src/core/actions/actions.h"
+#include "src/core/const.h"
+#include "src/core/model/model.h"
+#include "src/core/types.h"
+#include <catch2/catch.hpp>
+
+TEST_CASE("ActionRecorder")
+{
+       using namespace giada;
+       using namespace giada::m;
+
+       model::Model   model;
+       ActionRecorder ar(model);
+
+       REQUIRE(ar.hasActions(/*ch=*/0) == false);
+
+       SECTION("Test record")
+       {
+               const int       ch = 0;
+               const Frame     f1 = 10;
+               const Frame     f2 = 70;
+               const MidiEvent e1 = MidiEvent(MidiEvent::NOTE_ON, 0x00, 0x00);
+               const MidiEvent e2 = MidiEvent(MidiEvent::NOTE_OFF, 0x00, 0x00);
+
+               const Action a1 = ar.rec(ch, f1, e1);
+               const Action a2 = ar.rec(ch, f2, e2);
+
+               REQUIRE(ar.hasActions(ch) == true);
+               REQUIRE(a1.frame == f1);
+               REQUIRE(a2.frame == f2);
+               REQUIRE(a1.prevId == 0);
+               REQUIRE(a1.nextId == 0);
+               REQUIRE(a2.prevId == 0);
+               REQUIRE(a2.nextId == 0);
+
+               SECTION("Test clear actions by channel")
+               {
+                       const int       ch = 1;
+                       const Frame     f1 = 100;
+                       const Frame     f2 = 200;
+                       const MidiEvent e1 = MidiEvent(MidiEvent::NOTE_ON, 0x00, 0x00);
+                       const MidiEvent e2 = MidiEvent(MidiEvent::NOTE_OFF, 0x00, 0x00);
+
+                       ar.rec(ch, f1, e1);
+                       ar.rec(ch, f2, e2);
+
+                       ar.clearChannel(/*channel=*/0);
+
+                       REQUIRE(ar.hasActions(/*channel=*/0) == false);
+                       REQUIRE(ar.hasActions(/*channel=*/1) == true);
+               }
+
+               SECTION("Test clear actions by type")
+               {
+                       ar.clearActions(/*channel=*/0, MidiEvent::NOTE_ON);
+                       ar.clearActions(/*channel=*/0, MidiEvent::NOTE_OFF);
+
+                       REQUIRE(ar.hasActions(/*channel=*/0) == false);
+               }
+
+               SECTION("Test clear all")
+               {
+                       ar.clearAllActions();
+                       REQUIRE(ar.hasActions(/*channel=*/0) == false);
+               }
+       }
+}
diff --git a/tests/audioBuffer.cpp b/tests/audioBuffer.cpp
deleted file mode 100644 (file)
index 28b1f60..0000000
+++ /dev/null
@@ -1,83 +0,0 @@
-#include "../src/core/audioBuffer.h"
-#include <catch2/catch.hpp>
-#include <memory>
-
-TEST_CASE("AudioBuffer")
-{
-       using namespace giada::m;
-
-       static const int BUFFER_SIZE = 4096;
-
-       /* Each SECTION the TEST_CASE is executed from the start. Any code between 
-       this comment and the first SECTION macro is exectuted before each SECTION. */
-
-       AudioBuffer buffer;
-       buffer.alloc(BUFFER_SIZE, 2);
-
-       SECTION("test allocation")
-       {
-               SECTION("test mono")
-               {
-                       buffer.alloc(BUFFER_SIZE, 1);
-                       REQUIRE(buffer.countFrames() == BUFFER_SIZE);
-                       REQUIRE(buffer.countSamples() == BUFFER_SIZE);
-                       REQUIRE(buffer.countChannels() == 1);
-               }
-
-               SECTION("test stereo")
-               {
-                       REQUIRE(buffer.countFrames() == BUFFER_SIZE);
-                       REQUIRE(buffer.countSamples() == BUFFER_SIZE * 2);
-                       REQUIRE(buffer.countChannels() == 2);
-               }
-
-               buffer.free();
-
-               REQUIRE(buffer.countFrames() == 0);
-               REQUIRE(buffer.countSamples() == 0);
-               REQUIRE(buffer.countChannels() == 0);
-       }
-
-       SECTION("test clear all")
-       {
-               buffer.clear();
-               for (int i = 0; i < buffer.countFrames(); i++)
-                       for (int k = 0; k < buffer.countChannels(); k++)
-                               REQUIRE(buffer[i][k] == 0.0f);
-               buffer.free();
-       }
-
-       SECTION("test clear range")
-       {
-               for (int i = 0; i < buffer.countFrames(); i++)
-                       for (int k = 0; k < buffer.countChannels(); k++)
-                               buffer[i][k] = 1.0f;
-
-               buffer.clear(5, 6);
-
-               for (int k = 0; k < buffer.countChannels(); k++)
-                       REQUIRE(buffer[5][k] == 0.0f);
-
-               buffer.free();
-       }
-
-       SECTION("test copy")
-       {
-               AudioBuffer other(BUFFER_SIZE, 2);
-
-               for (int i = 0; i < other.countFrames(); i++)
-                       for (int k = 0; k < other.countChannels(); k++)
-                               other[i][k] = (float)i;
-
-               SECTION("test full copy")
-               {
-                       buffer.set(other, 1.0f);
-
-                       REQUIRE(buffer[0][0] == 0.0f);
-                       REQUIRE(buffer[16][0] == 16.0f);
-                       REQUIRE(buffer[128][0] == 128.0f);
-                       REQUIRE(buffer[1024][0] == 1024.0f);
-                       REQUIRE(buffer[BUFFER_SIZE - 1][0] == (float)BUFFER_SIZE - 1);
-               }
-       }
-}
index 14ea358404630826a3c1053c8e9613c4c2b719eb..b76f5ca25cd87875a2b89668317642b9aa03637d 100644 (file)
@@ -1,8 +1,3 @@
 #define CATCH_CONFIG_MAIN
 #define CATCH_CONFIG_FAST_COMPILE
-#include <catch2/catch.hpp>
-
-/* There's no main.cpp in the test suite and the following global var is 
-unfortunately defined there. Let's fake it. */
-
-class gdMainWindow* G_MainWin;
\ No newline at end of file
+#include <catch2/catch.hpp>
\ No newline at end of file
diff --git a/tests/midiLighter.cpp b/tests/midiLighter.cpp
new file mode 100644 (file)
index 0000000..d951a8a
--- /dev/null
@@ -0,0 +1,82 @@
+#include "../src/core/channels/midiLighter.h"
+#include "mocks/kernelMidiMock.h"
+#include <catch2/catch.hpp>
+#include <memory>
+
+TEST_CASE("MidiMapper")
+{
+       using namespace giada;
+
+       m::KernelMidiMock                kernelMidi;
+       m::MidiMapper<m::KernelMidiMock> midiMapper(kernelMidi);
+       m::MidiLighter                   midiLighter(midiMapper);
+
+       midiMapper.currentMap = {
+           "test-brand",
+           "test-device",
+           {{0, "0x000000", 0, 0x000000}}, // init commands
+           {0, "0x000001", 0, 0x000001},   // mute on
+           {0, "0x000002", 0, 0x000002},   // mute off
+           {0, "0x000003", 0, 0x000003},   // solo on
+           {0, "0x000004", 0, 0x000004},   // solo off
+           {0, "0x000005", 0, 0x000005},   // waiting
+           {0, "0x000006", 0, 0x000006},   // playing
+           {0, "0x000007", 0, 0x000007},   // stopping
+           {0, "0x000008", 0, 0x000008},   // stopped
+           {0, "0x000009", 0, 0x000009},   // playingInaudible
+       };
+
+       midiLighter.playing = {0x000010, 0};
+       midiLighter.mute    = {0x000011, 0};
+       midiLighter.solo    = {0x000012, 0};
+
+       SECTION("Test initialization")
+       {
+               REQUIRE(midiLighter.enabled == false);
+       }
+
+       SECTION("Test send OFF status")
+       {
+               midiLighter.sendStatus(ChannelStatus::OFF, /*audible=*/true);
+               REQUIRE(kernelMidi.sent.back() == 0x000008); // Stopped
+       }
+
+       SECTION("Test send WAIT status")
+       {
+               midiLighter.sendStatus(ChannelStatus::WAIT, /*audible=*/true);
+               REQUIRE(kernelMidi.sent.back() == 0x000005); // Waiting
+       }
+
+       SECTION("Test send ENDING status")
+       {
+               midiLighter.sendStatus(ChannelStatus::ENDING, /*audible=*/true);
+               REQUIRE(kernelMidi.sent.back() == 0x000007); // Stopping
+       }
+
+       SECTION("Test send PLAY status")
+       {
+               midiLighter.sendStatus(ChannelStatus::PLAY, /*audible=*/true);
+               REQUIRE(kernelMidi.sent.back() == 0x000006); // Playing
+
+               midiLighter.sendStatus(ChannelStatus::PLAY, /*audible=*/false);
+               REQUIRE(kernelMidi.sent.back() == 0x000009); // Playing inaudible
+       }
+
+       SECTION("Test send mute")
+       {
+               midiLighter.sendMute(/*isMuted=*/true);
+               REQUIRE(kernelMidi.sent.back() == 0x000001); // Mute on
+
+               midiLighter.sendMute(/*isMuted=*/false);
+               REQUIRE(kernelMidi.sent.back() == 0x000002); // Mute off
+       }
+
+       SECTION("Test send solo")
+       {
+               midiLighter.sendSolo(/*isSoloed=*/true);
+               REQUIRE(kernelMidi.sent.back() == 0x000003); // Solo on
+
+               midiLighter.sendSolo(/*isSoloed=*/false);
+               REQUIRE(kernelMidi.sent.back() == 0x000004); // Solo off
+       }
+}
diff --git a/tests/mocks/kernelMidiMock.h b/tests/mocks/kernelMidiMock.h
new file mode 100644 (file)
index 0000000..8929b1a
--- /dev/null
@@ -0,0 +1,18 @@
+#ifndef G_TESTS_KERNELMIDI_MOCK_H
+#define G_TESTS_KERNELMIDI_MOCK_H
+
+namespace giada::m
+{
+class KernelMidiMock
+{
+public:
+       void send(uint32_t s)
+       {
+               sent.push_back(s);
+       }
+
+       std::vector<uint32_t> sent;
+};
+} // namespace giada::m
+
+#endif
\ No newline at end of file
diff --git a/tests/recorder.cpp b/tests/recorder.cpp
deleted file mode 100644 (file)
index 8f6ec2f..0000000
+++ /dev/null
@@ -1,66 +0,0 @@
-#include "../src/core/recorder.h"
-#include "../src/core/action.h"
-#include "../src/core/const.h"
-#include "../src/core/types.h"
-#include <catch2/catch.hpp>
-
-TEST_CASE("recorder")
-{
-       using namespace giada;
-       using namespace giada::m;
-
-       recorder::init();
-
-       REQUIRE(recorder::hasActions(/*ch=*/0) == false);
-
-       SECTION("Test record")
-       {
-               const int       ch = 0;
-               const Frame     f1 = 10;
-               const Frame     f2 = 70;
-               const MidiEvent e1 = MidiEvent(MidiEvent::NOTE_ON, 0x00, 0x00);
-               const MidiEvent e2 = MidiEvent(MidiEvent::NOTE_OFF, 0x00, 0x00);
-
-               const Action a1 = recorder::rec(ch, f1, e1);
-               const Action a2 = recorder::rec(ch, f2, e2);
-
-               REQUIRE(recorder::hasActions(ch) == true);
-               REQUIRE(a1.frame == f1);
-               REQUIRE(a2.frame == f2);
-               REQUIRE(a1.prevId == 0);
-               REQUIRE(a1.nextId == 0);
-               REQUIRE(a2.prevId == 0);
-               REQUIRE(a2.nextId == 0);
-
-               SECTION("Test clear actions by channel")
-               {
-                       const int       ch = 1;
-                       const Frame     f1 = 100;
-                       const Frame     f2 = 200;
-                       const MidiEvent e1 = MidiEvent(MidiEvent::NOTE_ON, 0x00, 0x00);
-                       const MidiEvent e2 = MidiEvent(MidiEvent::NOTE_OFF, 0x00, 0x00);
-
-                       recorder::rec(ch, f1, e1);
-                       recorder::rec(ch, f2, e2);
-
-                       recorder::clearChannel(/*channel=*/0);
-
-                       REQUIRE(recorder::hasActions(/*channel=*/0) == false);
-                       REQUIRE(recorder::hasActions(/*channel=*/1) == true);
-               }
-
-               SECTION("Test clear actions by type")
-               {
-                       recorder::clearActions(/*channel=*/0, MidiEvent::NOTE_ON);
-                       recorder::clearActions(/*channel=*/0, MidiEvent::NOTE_OFF);
-
-                       REQUIRE(recorder::hasActions(/*channel=*/0) == false);
-               }
-
-               SECTION("Test clear all")
-               {
-                       recorder::clearAll();
-                       REQUIRE(recorder::hasActions(/*channel=*/0) == false);
-               }
-       }
-}
index 94c3270f2cd4d0ac539916534ef913ef0e0240b7..bd327319cf2f63d4854c116d09ac360fef37181f 100644 (file)
@@ -15,11 +15,13 @@ using namespace giada::m;
 TEST_CASE("waveManager")
 {
        /* Each SECTION the TEST_CASE is executed from the start. Any code between 
-       this comment and the first SECTION macro is exectuted before each SECTION. */
+       this comment and the first SECTION macro is executed before each SECTION. */
+
+       WaveManager waveManager;
 
        SECTION("test creation")
        {
-               waveManager::Result res = waveManager::createFromFile(TEST_RESOURCES_DIR "test.wav",
+               WaveManager::Result res = waveManager.createFromFile(TEST_RESOURCES_DIR "test.wav",
                    /*ID=*/0, /*sampleRate=*/G_SAMPLE_RATE, /*quality=*/SRC_LINEAR);
 
                REQUIRE(res.status == G_RES_OK);
@@ -31,7 +33,7 @@ TEST_CASE("waveManager")
 
        SECTION("test recording")
        {
-               std::unique_ptr<Wave> wave = waveManager::createEmpty(G_BUFFER_SIZE,
+               std::unique_ptr<Wave> wave = waveManager.createEmpty(G_BUFFER_SIZE,
                    G_MAX_IO_CHANS, G_SAMPLE_RATE, "test.wav");
 
                REQUIRE(wave->getRate() == G_SAMPLE_RATE);
@@ -43,11 +45,11 @@ TEST_CASE("waveManager")
 
        SECTION("test resampling")
        {
-               waveManager::Result res = waveManager::createFromFile(TEST_RESOURCES_DIR "test.wav",
+               WaveManager::Result res = waveManager.createFromFile(TEST_RESOURCES_DIR "test.wav",
                    /*ID=*/0, /*sampleRate=*/G_SAMPLE_RATE, /*quality=*/SRC_LINEAR);
 
                int oldSize = res.wave->getBuffer().countFrames();
-               waveManager::resample(*res.wave.get(), 1, G_SAMPLE_RATE * 2);
+               waveManager.resample(*res.wave.get(), 1, G_SAMPLE_RATE * 2);
 
                REQUIRE(res.wave->getRate() == G_SAMPLE_RATE * 2);
                REQUIRE(res.wave->getBuffer().countFrames() == oldSize * 2);